开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

java如何将时间戳转为秒

发表于 2021-04-11

如何将时间戳转为秒

通常的做法
1
2
3
java复制代码public static long toSecondMethod1(long timestamp) {
return timestamp / 1000;
}
直接使用TimeUnit工具箱中的方法

一个更友好点的方法

1
2
3
java复制代码 public static long toSecondMethod2(long timestamp) {
return TimeUnit.MILLISECONDS.toSeconds(timestamp);
}
测试代码
1
2
3
4
5
6
7
8
java复制代码 public static void main(String[] args) {
long timestamp = System.currentTimeMillis();
long method1 = toSecondMethod1(timestamp);
long method2= toSecondMethod2(timestamp);
System.out.println("timestamp:" + timestamp);
System.out.println("method1:" + method1);
System.out.println("method2:" + method2);
}
输出结果
1
2
3
java复制代码timestamp:1618100030482
method1:1618100030
method2:1618100030

可以看到结果是一样,我们可以进入TimeUnit(java.util.concurrent)中,看一下具体的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
java复制代码
/**
* Time unit representing one thousandth of a second
*/
MILLISECONDS {
public long toNanos(long d) { return x(d, C2/C0, MAX/(C2/C0)); }
public long toMicros(long d) { return x(d, C2/C1, MAX/(C2/C1)); }
public long toMillis(long d) { return d; }
public long toSeconds(long d) { return d/(C3/C2); }
public long toMinutes(long d) { return d/(C4/C2); }
public long toHours(long d) { return d/(C5/C2); }
public long toDays(long d) { return d/(C6/C2); }
public long convert(long d, TimeUnit u) { return u.toMillis(d); }
int excessNanos(long d, long m) { return 0; }
},


// Handy constants for conversion methods
static final long C0 = 1L;
static final long C1 = C0 * 1000L;
static final long C2 = C1 * 1000L;
static final long C3 = C2 * 1000L;
static final long C4 = C3 * 60L;
static final long C5 = C4 * 60L;
static final long C6 = C5 * 24L;

通过以上的两段代码,可以看出,内部实现和我们的实现一样的。

使用TimeUnit有什么好处呢?
  1. 如果我们除了将时间戳转为秒的需求,还可能将时间戳转为小时,转为分钟等,那该工具箱提供了toHours等方法使用。
  2. 如果我们的源头不止时间戳,可能是将秒转为小时等,同样的也会有TimeUnit.SECONDS对应的方法使用。
1
2
3
4
5
6
7
8
9
10
11
java复制代码  SECONDS {
public long toNanos(long d) { return x(d, C3/C0, MAX/(C3/C0)); }
public long toMicros(long d) { return x(d, C3/C1, MAX/(C3/C1)); }
public long toMillis(long d) { return x(d, C3/C2, MAX/(C3/C2)); }
public long toSeconds(long d) { return d; }
public long toMinutes(long d) { return d/(C4/C3); }
public long toHours(long d) { return d/(C5/C3); }
public long toDays(long d) { return d/(C6/C3); }
public long convert(long d, TimeUnit u) { return u.toSeconds(d); }
int excessNanos(long d, long m) { return 0; }
}

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

体验一下 Go Selenium Go主题月

发表于 2021-04-10

写爬虫的朋友一定了解过浏览器自动化,比如 Selenium 或者 Puppeteer,这其中我用的比较多的是 Selenium,Selenium 是一个用于 Web 应用程序测试的工具。Selenium 测试直接运行在浏览器中,就像真正的用户在操作一样。所以我们会使用 Selenium 进行模仿用户进行操作浏览器爬取数据。

之前使用的开发语言是 Python,今天我们来试试 Go selenium 吧。

安装

目前我正在使用的一个依赖库是 github.com/tebeka/sele…,功能较完整且处于维护中。

1
bash复制代码go get -t -d github.com/tebeka/selenium

另外,我们需要对应不同类型的浏览器进行安装 WebDriver,Google Chrome 需要安装 ChromeDriver,Firefox 则需要安装 geckodriver。

案例

这里我们使用的 Google Chrome,我们首先要指定 ChromeDriver 的位置并启动一个 WebDriver server,然后就可以开始操作浏览器了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
go复制代码package main

import (
"fmt"
"os"
"strings"
"time"

"github.com/tebeka/selenium"
)

const (
chromeDriverPath = "/path/to/chromedriver"
port = 8080
)

func main() {
// Start a WebDriver server instance
opts := []selenium.ServiceOption{
selenium.Output(os.Stderr), // Output debug information to STDERR.
}
selenium.SetDebug(true)
service, err := selenium.NewChromeDriverService(chromeDriverPath, port, opts...)
if err != nil {
panic(err) // panic is used only as an example and is not otherwise recommended.
}
defer service.Stop()

// Connect to the WebDriver instance running locally.
caps := selenium.Capabilities{"browserName": "chrome"}
wd, err := selenium.NewRemote(caps, fmt.Sprintf("http://localhost:%d/wd/hub", port))
if err != nil {
panic(err)
}
defer wd.Quit()

// Navigate to the simple playground interface.
if err := wd.Get("http://play.golang.org/?simple=1"); err != nil {
panic(err)
}

// Get a reference to the text box containing code.
elem, err := wd.FindElement(selenium.ByCSSSelector, "#code")
if err != nil {
panic(err)
}
// Remove the boilerplate code already in the text box.
if err := elem.Clear(); err != nil {
panic(err)
}

// Enter some new code in text box.
err = elem.SendKeys(`
package main
import "fmt"
func main() {
fmt.Println("Hello WebDriver!")
}
`)
if err != nil {
panic(err)
}

// Click the run button.
btn, err := wd.FindElement(selenium.ByCSSSelector, "#run")
if err != nil {
panic(err)
}
if err := btn.Click(); err != nil {
panic(err)
}

// Wait for the program to finish running and get the output.
outputDiv, err := wd.FindElement(selenium.ByCSSSelector, "#output")
if err != nil {
panic(err)
}

var output string
for {
output, err = outputDiv.Text()
if err != nil {
panic(err)
}
if output != "Waiting for remote server..." {
break
}
time.Sleep(time.Millisecond * 100)
}

fmt.Printf("%s", strings.Replace(output, "\n\n", "\n", -1))

// Example Output:
// Hello WebDriver!
//
// Program exited.
}

总结

使用起来并不是很复杂,但是感觉 Go Selenium 并不是很流行,github.com/tebeka/sele… 在 GitHub 上的 Star 数只有 1k+。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Dubbo两小时快速上手教程(直接代码、Spring、Spr

发表于 2021-04-10

听说微信搜索《Java鱼仔》会变更强!

本文收录于JavaStarter ,里面有我完整的Java系列文章,学习或面试都可以看看哦

最近项目中需要用到dubbo,虽然我知道dubbo是一个RPC框架,但是没有去详细了解这个框架。既然项目要用,那就先把Dubbo的应用给学会,等熟练使用之后,再去了解Dubbo内部的原理。如果想要项目代码,直接联系我即可。如果想要demo代码,直接联系我即可。

(一)什么是Dubbo

目前来说,Dubbo是最有名的RPC服务调用框架,他是阿里开源的一个SOA服务治理框架,功能较为完善,支持多种传输和序列化方案。Dubbo最常用的应用就是远程调用。

Dubbo中服务端最核心的对象有四个:

ApplicationConfig:配置当前应用信息

ProtocolConfig:配置提供服务的协议信息

RegistryConfig:配置注册相关信息

ServiceConfig:配置暴露的服务信息

Dubbo客户端中核心的对象有两个:

ApplicationConfig:配置当前应用信息

ReferenceConfig:配置引用的服务信息

(二)Dubbo实战

接下来通过三种方式入门Dubbo。首先会通过代码直接展示dubbo的直连和注册中心实现方式,接着使用Spring和SpringBoot的方式分别展示如何使用Dubbo。

在写dubbo相关代码前,我们首先要定义一个公共的客户端服务,这个服务里存放的是service接口。服务提供者引入这个工程,写实现类,提供dubbo接口;服务消费者引入这个工程,通过这个工程的service接口调用。

因此新建这样一个模块,命名为dubbo-client,整体代码结构如下,只需要写一个service接口即可:

在这里插入图片描述

User类:

1
2
3
4
5
6
7
java复制代码@Data
public class User implements Serializable {
private static final long serialVersionUID = -9206514891359830486L;
private Long id;
private String name;
private String sex;
}

UserService:

1
2
3
java复制代码public interface UserService {
User getUser(Long id);
}

2.1 直接代码

接下来通过直接代码的方式生成一个dubbo服务,并且用另外一个类去调用这个dubbo服务:

2.1.1 引入依赖

核心依赖就两个,一个dubbo的依赖,另外一个上面的公共接口方法

1
2
3
4
5
6
7
8
9
10
11
12
java复制代码<dependencies>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>2.7.4.1</version>
</dependency>
<dependency>
<artifactId>dubbo-client</artifactId>
<groupId>com.javayz</groupId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>

2.1.2 编写服务提供者

服务提供者主要配置以下几个属性:

1、application:设置应用的名称等信息

2、protocol :设置服务的协议

3、register:设置服务的连接方式

4、service:将需要暴露的服务注册出来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
java复制代码public class DubboProvider {
public static void main(String[] args) throws IOException {
//暴露UserService服务
//1、application
ApplicationConfig applicationConfig=new ApplicationConfig("sample-provider");
//2、protocol -dubbo协议
ProtocolConfig protocolConfig = new ProtocolConfig();
protocolConfig.setName("dubbo");
protocolConfig.setPort(20880);
//3、register
//直连的方式,不暴露到注册中心
RegistryConfig registryConfig=new RegistryConfig(RegistryConfig.NO_AVAILABLE);
//4、service
ServiceConfig serviceConfig=new ServiceConfig();
serviceConfig.setInterface(UserService.class);
serviceConfig.setRef(new UserServiceImpl());
//5、将application、protocol、register注册到service
serviceConfig.setRegistry(registryConfig);
serviceConfig.setProtocol(protocolConfig);
serviceConfig.setApplication(applicationConfig);
serviceConfig.export();

System.out.println("服务已经暴露");
System.in.read();
}
}

2.1.3 编写服务消费者

消费者的实现主要就三步:

1、配置application:设置应用的名称等信息

2、配置reference:主要配置要引用的信息

3、获取到接口,调用服务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
java复制代码public class DubboConsumer {
public static void main(String[] args) {
//1、application
ApplicationConfig applicationConfig=new ApplicationConfig("sample-consumer");
//2、配置reference
ReferenceConfig referenceConfig=new ReferenceConfig();
referenceConfig.setApplication(applicationConfig);
referenceConfig.setInterface(UserService.class);
referenceConfig.setUrl("dubbo://172.18.2.49:20880/com.javayz.client.service.UserService?anyhost=true&application=sample&bind.ip=172.18.2.49&bind.port=20880&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=com.javayz.client.service.UserService&methods=getUser&pid=5936&release=2.7.4.1&side=provider&timestamp=1618036935244");
UserService userService = (UserService) referenceConfig.get();
User user = userService.getUser(1L);
System.out.println(user);
}
}

先启动提供者,再启动消费者,如果user信息打印出来了就说明调用成功。

这里的Register使用的是直连的方式,我们也可以使用注册中心,这里以zookeeper为例。首先在项目中引入zookeeper相关依赖:

1
2
3
4
5
6
7
8
9
10
11
java复制代码<!-- zk客户端依赖:curator -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.13.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.13.0</version>
</dependency>

服务提供者修改一处地方,将RegistryConfig修改为zookeeper的连接方式

1
2
3
4
5
java复制代码//register
//直连的方式,不暴露到注册中心
//RegistryConfig registryConfig=new RegistryConfig(RegistryConfig.NO_AVAILABLE);
//通过注册中心暴露dubbo
RegistryConfig registryConfig=new RegistryConfig("zookeeper://192.168.78.128:2181");

消费者同样修改一处位置,将referenceConfig中的setUrl方法替换为zookeeper:

1
2
3
4
5
6
java复制代码RegistryConfig registryConfig=new RegistryConfig("zookeeper://192.168.78.128:2181");
ReferenceConfig referenceConfig=new ReferenceConfig();
referenceConfig.setRegistry(registryConfig);
referenceConfig.setApplication(applicationConfig);
referenceConfig.setInterface(UserService.class);
//referenceConfig.setUrl("dubbo://172.18.2.49:20880/com.javayz.client.service.UserService?anyhost=true&application=sample&bind.ip=172.18.2.49&bind.port=20880&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=com.javayz.client.service.UserService&methods=getUser&pid=5936&release=2.7.4.1&side=provider&timestamp=1618036935244");

2.2 通过Spring

通过Spring的方式只不过是把上面写在Java中的代码拿到配置文件中去,并把接口注入到Bean容器中,在resource文件夹下新建两个配置文件:
provider.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
xml复制代码<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">

<!-- 提供方应用信息,用于计算依赖关系 -->
<dubbo:application name="sample-provider" />

<!-- 使用zookeeper广播注册中心暴露服务地址 -->
<dubbo:registry address="zookeeper://192.168.78.128:2181" />

<!-- 用dubbo协议在20880端口暴露服务 -->
<dubbo:protocol name="dubbo" port="20880" />

<!-- 声明需要暴露的服务接口 -->
<dubbo:service interface="com.javayz.client.service.UserService" ref="userService" />

<!-- 和本地bean一样实现服务 -->
<bean id="userService" class="com.javayz.example1.service.impl.UserServiceImpl" />
</beans>

consumer.xml

1
2
3
4
5
6
7
8
9
10
xml复制代码<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">

<dubbo:application name="sample-consumer" />
<dubbo:registry address="zookeeper://192.168.78.128:2181" />
<dubbo:reference id="userService" interface="com.javayz.client.service.UserService" />
</beans>

这里的配置文件和上方的代码均一一对应。接着是服务的提供者和消费者:
SpringDubboProvider

1
2
3
4
5
6
7
java复制代码public class SpringDubboProvider {
public static void main(String[] args) throws IOException {
ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext("provider.xml");
System.out.println("服务已经暴露");
System.in.read();
}
}

SpringDubboConsumer

1
2
3
4
5
6
7
java复制代码public class SpringDubboConsumer {
public static void main(String[] args) {
ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext("consumer.xml");
UserService bean = context.getBean(UserService.class);
System.out.println(bean.getUser(1L));
}
}

2.3 通过SpringBoot的方式

新建两个SpringBoot项目,一个是服务提供者,一个是服务消费者,引入dubbo的核心依赖

1
2
3
4
5
xml复制代码<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.4.1</version>
</dependency>

这里的配置都写在application.properties中,首先是服务提供者:

1
2
3
4
java复制代码dubbo.application.name=dubbo-provider
dubbo.registry.address=zookeeper://192.168.78.128:2181
dubbo.protocol.name=dubbo
dubbo.protocol.port=20880

服务提供者需要写服务的实现类,这里需要注意@Service注解采用的是dubbo包下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
java复制代码import com.javayz.client.entity.User;
import com.javayz.client.service.UserService;
import org.apache.dubbo.config.annotation.Service;
import org.springframework.stereotype.Component;

@Service
@Component
public class UserServiceImpl implements UserService {
@Override
public User getUser(Long id) {
User user=new User();
user.setId(id);
user.setName("javayz");
user.setSex("man");
return user;
}
}

接着在启动类上添加一个@EnableDubbo注解即可。

服务的消费者同样是先写一下配置文件:

1
2
3
java复制代码server.port=8081
dubbo.application.name=dubbo-consumer
dubbo.registry.address=zookeeper://192.168.78.128:2181

接着通过@Reference注解将service对象引进来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
java复制代码@SpringBootApplication
public class SpringbootconsumerApplication {

@Reference
UserService userService;

public static void main(String[] args) {
SpringApplication.run(SpringbootconsumerApplication.class, args);
}

@Bean
public ApplicationRunner getBean(){
return args -> {
System.out.println(userService.getUser(1L));
};
}
}

(三)dubbo的常用配置

1
2
3
4
5
6
7
java复制代码<dubbo:application/> 用于配置当前应用信息
<dubbo:register/> 用于配置连接注册相关信息
<dubbo:protocol/> 用于配置提供服务的协议信息,提供者指定协议,消费者被动接受
<dubbo:service/> 用于暴露一个服务,一个服务可以用多个协议暴露,一个服务也可以注册到多个注册中心
<dubbo:provider/> 当ProtocolConfig和ServiceConfig某属性没有配置时,采用此缺省值
<dubbo:consumer/> 当ReferenceConfig某属性没有配置时,采用此缺省值
<dubbo:reference/> 用于创建一个远程服务代理

更加具体的配置信息我在官网中找到了,大家可参考:

dubbo.apache.org/zh/docs/v2.…

(四)企业中如何通过dubbo实现分布式调用

在企业中,如果消费者直接通过RPC去调用提供者,理论上需要把提供者的整个Jar包引入到项目中。但是这样的话服务提供这种的其他无关代码也会被引入其中,导致代码污染。

因此实际开发过程中,服务提供者和调用者之间会增加一层Client模块。这个Client中主要写的是Service的接口定义,接口的返回实例对象以及接口的请求实例对象。简单来讲,所有的定义都在Client中完成。

使用时,服务提供者引入这个Client,然后写实现方法,服务消费者引入这个Client,然后通过dubbo直接调用即可。

另外企业开发中,可能会出现多个接口实现,这种情况下可以给Service设定group、version等进行区分。

(五)总结

Dubbo的基本使用就这些,Dubbo毕竟只是一个RPC的工具,我们可以用它很方便地暴露、消费服务。但是两个小时也只是会上手使用,它内部的一些配置,一些理念以及最重要的原理都是需要我们自己去深耕的。我是鱼仔,我们下期再见!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

入门Go语言学习路线图,必须跨过的误区 Go主题月 1、

发表于 2021-04-10

1、大厂认可,岗位需求火热

最近,可以看到的招聘趋势已经在大厂中占据了重要的地位,无论是职位的数量和薪资待遇,已经和java持平,甚至略微高出一点点。Go 语言具有入门快、程序库多、运行迅速等特点,也是完美平衡了开发效率和执行效率,是各大编程语言中的佼佼者。

企业对于 Go 语言的使用情况正逐年上升。自2009年发布以来,Go 语言就深受明星大厂的喜爱,包括 腾讯、B站、滴滴、今日头条、小米、奇虎 360、京东等明星公司了,业界甚至一度看好 Go 语言有取代 Java 王者地位的潜力。
Go语言是谷歌公司推出的新一代的开发语言,出身名门的 Go 语言,你还不赶快上手,提前学习一下。

2、学习路线 & 基础知识一键获取

入门 Go 语言的同学基础不一,具体可以分成下面几类。

  • 零基础的同学:可能正准备入行或对编程刚开始感兴趣,对计算机、操作系统和网络方面的知识不太了解。
  • 无编程经验或者编程经验较少的同学:可能正在从事其他的技术相关工作,也许可以熟练编写脚本,但是对程序设计的通用知识和技巧还不太了解。
  • 有其他语言编程经验的同学:可能已经是程序员或软件工程师,可以用其他编程语言熟练编写程序,但对 Go 语言还不太了解。
  • 有一定 Go 语言编程经验的同学:已有 Go 语言编程基础,写过一些 Go 语言程序,急需进阶却看不清途径。
    基于以上分类,我制定了一份 Go 语言学习路径和基础知识图。不论你属于上述哪一类,都可以按照此路径去学习深造。

3、进阶高级知识点逐个击破

除了基础知识之外,如果你想要进阶高级 Go 语言工程师,还需要通关以下的知识点。

数据类型方面:

  • 基于底层数组的切片;
  • 为了传递数据的通道;
  • 作为一等公民的函数;
  • 为实现面向对象的结构体;
  • Go语言特色-无侵入实现的接口。

在语法方面:

  • 异步编程大杀器go语句;
  • 函数的最后防线defer语句;
  • 可做类型判断的switch语句;
  • 多通道操作利器select语句;
  • 非常有特色的异常处理函数panic和recover。

Go语言自带的程序测试套件,相关的概念和工具包括:

  • 专用的testing代码包;
  • 以及功能强大的go test命令。

Go 语言的同步工具:

  • 经典的互斥锁、读写锁、条件变量和原子操作;
  • 一次性次执行小助手 sync.Once、临时对象池 sync.Pool、sync.WaitGroup 和 context.Context;
    如果你理解了上述知识点,就掌握了 Go 语言编程的精髓。在这之后,再研读 Go 语言标准库和优秀的第三方库,就会事半功倍;使用 Go 语言编写软件时,就会游刃有余。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

python 判断变量是否是 None 的三种写法

发表于 2021-04-10

代码中经常会有变量是否为None的判断,有三种主要的写法:

  • 第一种是if x is None;
  • 第二种是 if not x:;
  • 第三种是if not x is None(这句这样理解更清晰if not (x is None)) 。

如果你觉得这样写没啥区别,那么你可就要小心了,这里面有一个坑。先来看一下代码:

1
2
3
4
5
6
7
8
9
10
11
12
python复制代码>>> x = 1
>>> not x
False
>>> x = [1]
>>> not x
False
>>> x = 0
>>> not x
True
>>> x = [0]
>>> not x
False

在python中 None, False, 空字符串””, 0, 空列表[], 空字典{}, 空元组()都相当于False ,即:

1
python复制代码not None == not False == not '' == not 0 == not [] == not {} == not ()

因此在使用列表的时候,如果你想区分x==[]和x==None两种情况的话, 此时if not x:将会出现问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
python复制代码#Python学习交流群:531509025

>>> x = []
>>> y = None
>>>
>>> x is None
False
>>> y is None
True
>>>
>>>
>>> not x
True
>>> not y
True
>>>
>>>
>>> not x is None
>>> True
>>> not y is None
False
>>>

也许你是想判断x是否为None,但是却把x==[]的情况也判断进来了,此种情况下将无法区分。

对于习惯于使用if not x这种写法的pythoner,必须清楚x等于None, False, 空字符串””, 0, 空列表[], 空字典{}, 空元组()时对你的判断没有影响才行。

而对于if x is not None和if not x is None写法,很明显前者更清晰,而后者有可能使读者误解为if (not x) is None,因此推荐前者,同时这也是谷歌推荐的风格

结论:

if x is not None是最好的写法,清晰,不会出现错误,以后坚持使用这种写法。

使用if not x这种写法的前提是:必须清楚x等于None, False, 空字符串””, 0, 空列表[], 空字典{}, 空元组()时对你的判断没有影响才行。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

从零开始学Dubbo-基础篇-远程调用使用

发表于 2021-04-10

本篇主要讲述了dubbo的远程调用如何使用。将会通过一个案例来讲解。

一共分为四步:

  • 1.创建maven工程,定义jar包版本
  • 2.创建api模块,规范接口
  • 3.创建provider模块,引入api模块,注册到注册中心供消费者使用,作为服务的提供者
  • 4.创建consumer模块,作为服务的消费者,引入注册中心,调用服务提供者

1.创建maven工程

首先创建基本的maven工程
image.png
然后配置配置groupId以及artifactId
image.png
然后在pom.xml引入依赖.dependencyManagement作用是子模块会引用父模块的版本号。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
xml复制代码<properties>
<dubbo.version>2.7.1</dubbo.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>${dubbo.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-common</artifactId>
<version>${dubbo.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-zookeeper</artifactId>
<version>${dubbo.version}</version>
<exclusions>
<exclusion>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-remoting-api</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-common</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-nacos</artifactId>
<version>${dubbo.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-rpc-dubbo</artifactId>
<version>${dubbo.version}</version>
<exclusions>
<exclusion>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-remoting-api</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-common</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-remoting-netty4</artifactId>
<version>${dubbo.version}</version>
<exclusions>
<exclusion>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-remoting-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-serialization-hessian2</artifactId>
<version>${dubbo.version}</version>
<exclusions>
<exclusion>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-common</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</dependencyManagement>

这边dubbo我选择了2.7.1的版本,因为使用2.7.5+jdk11我这边出现了配置无法解析的错误
image.png

2.创建api模块

这里主要就是创建接口,做好规定

image.png

1
2
3
4
5
6
7
8
typescript复制代码/**
* @author Davon ding
* @Description: 接口协定
* @date 2021/4/10 1:40 下午
*/
public interface HelloDubbo {
String sayHello(String name);
}

3.创建provider模块

首先pom.xml引入所需依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
xml复制代码<dependencies>
<dependency>
<groupId>com.study</groupId>
<artifactId>dubbo-service-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-zookeeper</artifactId>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-rpc-dubbo</artifactId>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-remoting-netty4</artifactId>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-serialization-hessian2</artifactId>
</dependency>
</dependencies>

1.实现api接口

1
2
3
4
5
6
7
8
typescript复制代码//这个service是dubbo的service
@Service
public class HellowDubboImpl implements HelloDubbo {
@Override
public String sayHello(String name) {
return "provider ack:"+name;
}
}

2.设置配置类,以及启动类

这边直接写在一起

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
java复制代码public class DubboPureMain {

public static void main(String[] args) throws IOException {
//加载配置类
AnnotationConfigApplicationContext context=new AnnotationConfigApplicationContext(ProviderConfiguration.class);
//启动容器
context.start();
//保证程序一直运行
System.in.read();
}

//标记是配置类
@Configuration
//扫包 类似于componetscan
@EnableDubbo(scanBasePackages = "com.study.service")
//读取配置文件
@PropertySource("classpath:/dubbo-provider.properties")
static class ProviderConfiguration{
@Bean
public RegistryConfig registryConfig(){
RegistryConfig registryConfig=new RegistryConfig();
//设置zk地址 注册中心地址
registryConfig.setAddress("zookeeper://10.0.9.173:2181");
return registryConfig;
}
}
}

接下来看一下配置文件信息

1
2
3
4
5
6
ini复制代码#服务名称
dubbo.application.name=dubbo-service-provider
#使用的协议
dubbo.protocol.name=dubbo
#端口号
dubbo.protocol.port=20881

4.创建consumer模块

1.实现对api接口的调用

1
2
3
4
5
6
7
8
9
10
11
typescript复制代码@Component
public class ConsumerCompent {
//dubbo的注解,加载对象时会生成代理存入
@Reference
private HelloDubbo helloDubbo;

public String sayHello(String name){
return helloDubbo.sayHello(name);
}

}

2.设置配置类、启动类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
java复制代码public class AnnotationConsumerMain {

public static void main(String[] args) throws IOException {
AnnotationConfigApplicationContext context=new AnnotationConfigApplicationContext(ConsumerConfiguation.class);
context.start();
//获取对象
final ConsumerCompent bean = context.getBean(ConsumerCompent.class);
//测试
while (true){
System.in.read();
String hello=bean.sayHello("test");
System.out.println("consumer post:"+hello);
}
}

@Configuration
@PropertySource("classpath:/dubbo-consumer.properties")
//spring扫描
@ComponentScan(basePackages = "com.study.bean")
@EnableDubbo
static class ConsumerConfiguation{

}
}

看一下消费者的配置文件

1
2
3
4
ini复制代码//服务名
dubbo.application.name=dubbo-service-consumer
//dubbo注册中心地址
dubbo.registry.address=zookeeper://10.0.9.173:2181

测试

上面四步就是简易的远程调用实例,接下来测试一下效果,要先启动provider再启动consumer。
输入123回车,则会调用4次,结果如下:

image.png

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

盘点 SpringBoot 自动装配

发表于 2021-04-10

总文档 :文章目录

Github : github.com/black-ant

一 . 前言

无意中把 自动装配的相关源码补齐了, 所以终于能开启这个系列了.

这个系列我们来过一下自动装配的流程 , 整个流程从一年前就开始学习 , 一直没有找到合适的表达形式 , 最近灵感迸发 , 正好试试这个方式.

二 . 背景知识点

2.1 基础背景

Spring Boot 自动配置尝试根据添加的 jar 依赖项自动配置 Spring 应用程序 , Spring 是允许对自动装配进行替代和自行配置 , 扩展了Spring 的灵活性.

当然 , 配置是不可能平白无故产生 , 打开 Spring-boot-autoconfiture 包就可以看到对应的几个配置文件 :

  • spring.factories : 自动装配类 , Spring 启动的时候会用到 ,我们后面分析
  • spring-autoconfigure-metadata.properties : 配置信息
  • spring-configure-metadata.json : 配置元数据 , 业务中会通过该文件进行属性映射和处理
    • 例如 : spring.jpa.hibernate.ddl-auto 提供的多种模式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
java复制代码{
"name": "spring.jpa.hibernate.ddl-auto",
"values": [
{
"value": "none",
"description": "Disable DDL handling."
},
{
"value": "validate",
"description": "Validate the schema, make no changes to the database."
},
{
"value": "update",
"description": "Update the schema if necessary."
},
{
"value": "create",
"description": "Create the schema and destroy previous data."
},
{
"value": "create-drop",
"description": "Create and then destroy the schema at the end of the session."
}
]
}

自动装配和自动配置

  • 自动配置:是 Spring Boot 提供的,实现通过 jar 包的依赖,能够自动配置应用程序。
    • 例如说:我们引入 spring-boot-starter-web 之后,就自动引入了 Spring MVC 相关的 jar 包,从而自动配置 Spring MVC 。
  • 自动装配:是 Spring 提供的 IoC 注入方式,具体看看 《Spring 教程 —— Beans 自动装配》 文档。

2.2 基础使用

我们来看一下基础的使用 :

1
2
3
4
5
6
7
8
9
java复制代码@Configuration
@EnableAutoConfiguration
public class BeanAnnotationConfig {

@Bean
public BeanAnnotationTestService getBeanAnnotationTestService() {
return new BeanAnnotationTestService();
}
}

实现基础 : 自动装配基于 @SpringBootApplication 注解实现

1
2
3
4
5
6
java复制代码F- SpringBootApplication
|- @SpringBootConfiguration: 标记这是一个 Spring Boot 配置类
|- @EnableAutoConfiguration : 用于开启自动配置功能
|- @AutoConfigurationPackage : 获取主程序类所在的包路径,并将包路径(包括子包)下的所有组件注册到 Spring IOC 容器中
|- @Import : 导入资源(并且由AutoConfigurationImportSelector)
|- @ComponentScan(内部为excludeFilters )

三. 源码分析

来尝试一种新的方式来解析源码 :

3.1 扫描的起点

3.2 业务扫描关系

我们先看下基本逻辑 :

1. 由 ConfigurationClassPostProcessor 开始

2. 在 ConfigurationClassPostProcessor 的 doProcessConfigurationClass 中获取所有的 config 类信息 , 其中就包括 @Bean 标注的Bean

3. 最终在 ConfigurationClassBeanDefinitionReader 的 loadBeanDefinitionsForBeanMethod 中进行加载

C01- ConfigurationClassPostProcessor : 处理 processConfigBeanDefinitions 类型

M101M102M102 doWhile调用do-while parse 循环 处理ConfigurationClassParser 处理ConfigurationClassBeanDefinitionReader 处理判断已处理的和生成新得未处理candidates 为空 ,处理完成将ImportRegistry注册为一个bean清除外部提供的MetadataReaderFactory中的缓存M101M102M102 doWhile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
java复制代码C01- ConfigurationClassPostProcessor
M101- postProcessBeanDefinitionRegistry
- System.identityHashCode(registry) 获取一个 registryId
- 将 id 加入 Set<Integer>
- processConfigBeanDefinitions(registry) : 详看 M102
M102- processConfigBeanDefinitions
- 从 BeanDefinitionRegistry 获取一个 List<BeanDefinitionHolder> 集合
?- 通过每个 BeanDefinition 的 Attribute是否为 configurationClass , 加入集合
?- 此处是将 BeanDefinitionRegistry 获取的所有类过滤后加入
- 通过 Order 进行一次排序
- 获取相关 Generator ,并且通过这些 Generator 获取一个 ConfigurationClassParser
- List 封装为一个 Set<BeanDefinitionHolder> , 保证其唯一性
FOR- 循环所有的 BeanDefinitionHolder
- 通过 ConfigurationClassParser 处理该集合
- 通过 ConfigurationClassBeanDefinitionReader load configClasses


// M01 伪代码
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
int registryId = System.identityHashCode(registry);
//.....
this.registriesPostProcessed.add(registryId);
processConfigBeanDefinitions(registry);
}

// M02 伪代码
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {

// Step 1 : 获取 configurationClass 集合
List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
String[] candidateNames = registry.getBeanDefinitionNames();
// .... 此处做了相关判断
configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));

// Step 2 : 排序
configCandidates.sort((bd1, bd2) -> {
int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
return Integer.compare(i1, i2);
});

// 省略 : 生成 ConfigurationClassParser 的必要参数

// Step 3 : 准备一个 ConfigurationClassParser
ConfigurationClassParser parser = new ConfigurationClassParser(
this.metadataReaderFactory, this.problemReporter, this.environment,
this.resourceLoader, this.componentScanBeanNameGenerator, registry);

// 准备所有的 ConfigClass 的 BeanDefinitionHolder 集合
Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
do {
// Step 4 : parse 处理
parser.parse(candidates);
parser.validate();

// 准备 ConfigurationClass 并且去掉已经处理的
Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
configClasses.removeAll(alreadyParsed);

// Step 5 : reader 处理
if (this.reader == null) {
this.reader = new ConfigurationClassBeanDefinitionReader(
registry, this.sourceExtractor, this.resourceLoader, this.environment,
this.importBeanNameGenerator, parser.getImportRegistry());
}
this.reader.loadBeanDefinitions(configClasses);
alreadyParsed.addAll(configClasses);

}while (!candidates.isEmpty());

// 将ImportRegistry注册为一个bean,以便支持importtaware @Configuration类
if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
}

if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
// 清除外部提供的MetadataReaderFactory中的缓存
((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();
}
}

C02- ConfigurationClassParser : 主处理逻辑

ConfigurationClassParser 主要用于解析单个配置类 . 但是因为 @Import 注解的原因 , 可能会产生多个ConfigurationClass


M101M102M202M203M204调用FOR 循环处理 SourceClass调用 processConfigurationClass调用 doProcessConfigurationClass循环处理 PropertySources循环处理 ComponentScans递归处理 @Bean method递归处理 @Import获取超类返回超类或者null递归处理超类将处理类加入 Map 集合M101M102M202M203M204

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
java复制代码C02- ConfigurationClassParser
M201- parse(Set<BeanDefinitionHolder> configCandidates)
- 循环 Set<BeanDefinitionHolder> , 根据其不同类型调用不同的parse
- this.deferredImportSelectorHandler.process();
?- 调用内部类 DeferredImportSelectorHandler 处理相关的逻辑
M202- processImports
- this.importStack.push(configClass) :
// 从这里之后开始核心的 Class 类处理逻辑 , 会分别判断 SourceClass 的类型
FOR- Collection<SourceClass> : for 循环传入的集合 (Collection - Group.Entry.getImportClassName) , 获取 SourceClass
1- TODO
2- TODO
3- 除了以上2种外
- processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter) : M203
M203- processConfigurationClass
- asSourceClass(configClass, filter) : 获取 SourceClass
- sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter) : M204
- TODO 待完善
M204- doProcessConfigurationClass
- 如果类注解是 Component , 调用 processMemberClasses(configClass, sourceClass, filter)
- For 循环 其上 PropertySources 注解 , 并调用 processPropertySource(propertySource)
- For 循环 其上 ComponentScans 注解
- this.componentScanParser.parse 获取 Set<BeanDefinitionHolder>
FOR- Set<BeanDefinitionHolder>
1- 获取 BeanDefinition
2- 如果检查给定的bean定义是配置类的候选 , 调用 parse(bdCand.getBeanClassName(), holder.getBeanName())
?- 这里可以看成是一个递归处理
- 调用 processImports 处理 @Import 注解对应的类
?- 这其实也是一个递归处理
- 获取 ImportResource 对应的属性
IF- 如果对应属性不为空
- importResource.getStringArray("locations") : 获取其中的 locations 属性 String[]
- 获取 BeanDefinitionReader
FOR- 循环数组
- configClass.addImportedResource(resolvedResource, readerClass)
- retrieveBeanMethodMetadata(sourceClass) : 获取其中 @Bean 对应Method
FOR- 循环上面的 Set<MethodMetadata>
- configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
- processInterfaces(configClass, sourceClass) : 处理其中 interface default 方法
- 获取父类型 , 再将其添加到 Map<String, ConfigurationClass> 中
- return sourceClass.getSuperClass() : 找到超类,返回其注释元数据并递归
M05- processMemberClasses

C03- DeferredImportSelectorHandler

1
2
3
4
java复制代码C03- DeferredImportSelectorHandler
M301- process() :
- 将 List<DeferredImportSelectorHolder> 排序后注入到 DeferredImportSelectorGroupingHandler 中
- 调用 handler.processGroupImports() 处理 所有的 Group

C04- DeferredImportSelectorGroupingHandler

M301M401M501获取所有Group 后调用 processGroupImports 处理 Group获取 Group process 处理总逻辑 中 DeferredImportSelectorHolder执行 Group process 处理总逻辑M301M401M501

1
2
3
4
5
6
7
8
9
java复制代码C04- DeferredImportSelectorGroupingHandler
M401- processGroupImports
- 先循环 DeferredImportSelectorGrouping
- grouping.getImports() : 返回 Iterable<Group.Entry>
- 获取 Group 中 DeferredImportSelectorHolder
- DeferredImportSelector.Group 处理 (process) 每一个 DeferredImportSelectorHolder (C05 逻辑)
FOR- 其次循环每个 DeferredImportSelectorGrouping 的 Imports
- this.configurationClasses.get(entry.getMetadata()) : 获取当前 ConfigurationClass
- processImports 处理 , 详见 processImports 方法逻辑 (M202)

C05- AutoConfigurationImportSelector

从上一步中 , 其中一个 DeferredImportSelectorHolder 就是 AutoConfigurationImportSelector

M401M501M502M503调用通过 AnnotationMetadata 获取 AutoConfigurationEntry获取所有的 configurations 列表过滤重复及filter 过滤需要跳过的ConfigClass处理 listener 和 event返回一个 AutoConfigurationEntryAutoConfigurationEntry 加入 Map<String, AnnotationMetadata>M401M501M502M503

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
java复制代码
C05- AutoConfigurationImportSelector
M502- getAutoConfigurationEntry
- getAttributes(annotationMetadata) : 获取 AnnotationMetadata 中属性
- getCandidateConfigurations(annotationMetadata, attributes) : 获取所有的 configurations 列表
- removeDuplicates : 移除重复的配置类
- checkExcludedClasses + removeAll : 移除 getExclusions(annotationMetadata, attributes) 获取的类
- getConfigurationClassFilter().filter(configurations)
- filter.match(candidates, this.autoConfigurationMetadata) : filter 过滤是否跳过
- fireAutoConfigurationImportEvents(configurations, exclusions) : 详见 fireAutoConfigurationImportEvents
- new AutoConfigurationEntry(configurations, exclusions) : 返回一个 AutoConfigurationEntry
M503- fireAutoConfigurationImportEvents
- getAutoConfigurationImportListeners() : 获取AutoConfigurationImportListener集合
- listener.onAutoConfigurationImportEvent(event) :分别通过每个 listern , 处理自动配置导入事件


C05PSC01- AutoConfigurationGroup
?- 该类为内部静态类
M501- process :
- AutoConfigurationImportSelector.getAutoConfigurationEntry(annotationMetadata) : 获取一个 AutoConfigurationEntry 对象
?- 详见 getAutoConfigurationEntry 方法
- this.autoConfigurationEntries.add(autoConfigurationEntry) : 添加到 AutoConfigurationEntry 集合中
FOR- List<AutoConfigurationEntry> : For 循环整个 集合
- this.entries.putIfAbsent(importClassName, annotationMetadata) : 放入 Map<String, AnnotationMetadata>
?- 这里意味着将所有的 AutoConfigurationEntry 重新处理成了 Map



// M501 伪代码
@Override
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
AutoConfigurationImportSelector.class.getSimpleName(),
deferredImportSelector.getClass().getName()));
// 调用 M502 生成 AutoConfigurationEntry
AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector).getAutoConfigurationEntry(annotationMetadata);
this.autoConfigurationEntries.add(autoConfigurationEntry);
// 循环处理加入 放入 Map<String, AnnotationMetadata>
for (String importClassName : autoConfigurationEntry.getConfigurations()) {
this.entries.putIfAbsent(importClassName, annotationMetadata);
}
}

// M502 核心伪代码
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);

// 移除和过滤
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = getConfigurationClassFilter().filter(configurations);

// 调用处理 Listener
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}

C06- ConfigurationClass

从 C02-M204 中 ,我们可以看到 , 类上注解以及类中 Bean 都被配置到了 ConfigurationClass , 我们看看相关逻辑是什么样的

1
2
java复制代码C06- ConfigurationClass
- getBeanMethods() : 观察后发现其被调用的地方是 ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForConfigurationClass

C07- ConfigurationClassBeanDefinitionReader

C06M701M702M703加载 BeanDefinitions循环所有的的 @Bean 注解Method如果是 Imported , 调用 702 注册For 循环 loadBeanDefinitionsForBeanMethod判断@Bean @Lazy 等注解处理 autowire 等方法this.registry.registerBeanDefinition 注册C06M701M702M703

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
JAVA复制代码C07- ConfigurationClassBeanDefinitionReader
M- loadBeanDefinitions
M701- loadBeanDefinitionsForConfigurationClass
- 判断当前 configClass 是否应该跳过
true : 从相关对象中移除
- this.registry.removeBeanDefinition(beanName)
- this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName())
- 如果是 Imported , 调用 registerBeanDefinitionForImportedConfigurationClass (M702)
FOR- 循环处理 configClass.getBeanMethods() 获得的Bean 方法
- loadBeanDefinitionsForBeanMethod(beanMethod) :M703

M702- registerBeanDefinitionForImportedConfigurationClass
- configClass.getMetadata() : 获取 AnnotationMetadata
- 通过 AnnotationMetadata 生成 AnnotatedGenericBeanDefinition
- 通过 AnnotatedGenericBeanDefinition 生成 ScopeMetadata , 并且为 AnnotatedGenericBeanDefinition 设置 Scope
- 通过 BeanNameGenerator 生成一个 Config BeanName
- 创建一个 BeanDefinitionHolder , 并且 this.registry.registerBeanDefinition 注册
- configClass.setBeanName(configBeanName) : 为 Class 命名
M703- loadBeanDefinitionsForBeanMethod(beanMethod) : 加载 Bean 主逻辑 , 此处已经到了方法处理
- beanMethod.getConfigurationClass() : 获取当前的 ConfigurationClass
- beanMethod.getMetadata() : 获取 MethodMetadata , 该元数据包含当前方法的信息
- 如果当前方法应该跳过 , 则加入到 configClass.skippedBeanMethods , 并且返回
- AnnotationConfigUtils.attributesFor(metadata, Bean.class) : 获取当前方法上 Bean 注解的属性
- bean.getStringArray("name") 获取名称集合 , 取第一个为Bean 名 , 其他的作为别名
- ????
- 通过 configClass 和 metadata 构建一个 ConfigurationClassBeanDefinition
- 为 ConfigurationClassBeanDefinition 设置 extractSource
?- configClass.getResource()
IF- 判断是否为标注 @Bean 的 静态方法
true:
false: 设置 FactoryBeanName 和 UniqueFactoryMethodName
- beanDef.setFactoryBeanName(configClass.getBeanName()) : 设置了当前 config bean 的名称
- beanDef.setUniqueFactoryMethodName(methodName) : 当前 method 的名称
- beanDef.setAutowireMode :设置注入的模式 , 此处是 AUTOWIRE_CONSTRUCTOR
?- AUTOWIRE_NO / AUTOWIRE_BY_NAME / AUTOWIRE_BY_TYPE / AUTOWIRE_CONSTRUCTOR / AUTOWIRE_AUTODETECT
- beanDef.setAttribute : 设置 skipRequiredCheck 为 true
- AnnotationConfigUtils.processCommonDefinitionAnnotations : 设置加载类型
?- 包括 Lazy , Primary , DependsOn , Role , Description
- bean.getEnum("autowire") + beanDef.setAutowireMode(autowire.value())
- bean.getBoolean("autowireCandidate") + setAutowireCandidate
- bean.getString("initMethod") + beanDef.setInitMethodName(initMethodName)
- bean.getString("destroyMethod") + beanDef.setDestroyMethodName(destroyMethodName)
- AnnotationConfigUtils.attributesFor(metadata, Scope.class) : 获取 + beanDef.setScope 配置 Scope 属性
- attributes.getEnum("proxyMode") : 获取 proxyMode
- ScopedProxyCreator.createScopedProxy : 通过获取的 proxyMode + registry + beanDef 等构建 BeanDefinitionHolder
- 通过 BeanDefinitionHolder + configClass + metadata 构建 ConfigurationClassBeanDefinition
- this.registry.registerBeanDefinition(beanName, beanDefToRegister) : 最后注册 Bean
?- registry 是一个 BeanDefinitionRegistry

总结

这是一篇测试型的文档 , 想测试一下该以什么样的形式来展现源码流程 , 也不知道这篇文档有没有达到效果.

后续还会对整篇文章继续处理 , 争取找到更合适的模式.

自动装配属于笔记里面比较早期的文章 , 本地还有很多 IOC , Cloud 的文章 , 找到合适的模式后应该能很好的展现了!!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

万字长文 - 史上最全ConstraintLayout(约束

发表于 2021-04-09

前言

ConstraintLayout 是一个使用“相对定位”灵活地确定微件的位置和大小的一个布局,在 2016 年 Google I/O 中面世,它的出现是为了解决开发中过于复杂的页面层级嵌套过多的问题——层级过深会增加绘制界面需要的时间,影响用户体验,以灵活的方式定位和调整小部件。从 Android Studio 2.3起,创建layout文件就已经是默认ConstraintLayout了,但是尽管Google如此大力推这项技术,但在当时很少有人使用,近些年逐渐被大家拿起来,啊真香!(此处无图胜有图)。目前ConstraintLayout正式版已经更新至2.0.4,本文将带领大家熟悉ConstraintLayout全部内容。

一. 布局的使用

1.1 位置约束

ConstraintLayout采用方向约束的方式对控件进行定位,至少要保证水平和垂直方向都至少有一个约束才能确定控件的位置

1.1.1 基本方向约束

比如我们想实现这个位置,顶部和界面顶部对齐,左部和界面左部对齐:

1.png

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
xml复制代码<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#DAF3FE"
tools:context=".MainActivity">

<TextView
android:layout_width="100dp"
android:layout_height="60dp"
android:background="@drawable/tv_bg"
android:gravity="center"
android:text="A"
android:textColor="@color/black"
android:textSize="25sp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="HardcodedText" />

</androidx.constraintlayout.widget.ConstraintLayout>

核心代码是这两行:

1
2
xml复制代码app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"

这两行代码的意思就是,控件的开始方向与父容器的开始方向对齐,控件的顶部方向与父容器的顶部方向对齐,其实layout_constraintStart_toStartOf也可以使用layout_constraintLeft_toLeftOf,但是使用start和end来表示左和右是为了考虑别的国家的习惯,有的国家开始方向是右,所以使用start和end可以兼容这种情况。到这里就可以看到该控件使用layout_constraintStart_toStartOf和layout_constraintTop_toTopOf两条约束确定了自己的位置,这里有一个使用技巧,就是,该控件的??方向在哪个控件的??方向,记住这一点就可以了。那么下面就介绍下全部的约束属性:

1
2
3
4
5
6
7
8
9
10
11
12
xml复制代码<!-- 基本方向约束 -->
<!-- 我的什么位置在谁的什么位置 -->
app:layout_constraintTop_toTopOf="" 我的顶部和谁的顶部对齐
app:layout_constraintBottom_toBottomOf="" 我的底部和谁的底部对齐
app:layout_constraintLeft_toLeftOf="" 我的左边和谁的左边对齐
app:layout_constraintRight_toRightOf="" 我的右边和谁的右边对齐
app:layout_constraintStart_toStartOf="" 我的开始位置和谁的开始位置对齐
app:layout_constraintEnd_toEndOf="" 我的结束位置和谁的结束位置对齐

app:layout_constraintTop_toBottomOf="" 我的顶部位置在谁的底部位置
app:layout_constraintStart_toEndOf="" 我的开始位置在谁的结束为止
<!-- ...以此类推 -->

那么ConstraintLayout就是使用这些属性来确定控件的位置,虽然比较多,但是有规律可循,没有任何记忆压力

1.1.2 基线对齐

我们看一个场景:

2.png

我们有时候需要写这样的需求:两个文本是基线对齐的,那就可以用到我们的一个属性layout_constraintBaseline_toBaselineOf来实现,它的意思就是这个控件的基线与谁的基线对齐,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
xml复制代码<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#DAF3FE"
tools:context=".MainActivity"
tools:ignore="HardcodedText">

<TextView
android:id="@+id/tv1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="20"
android:textColor="@color/black"
android:textSize="50sp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<TextView
android:id="@+id/tv2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="¥"
android:textColor="@color/black"
android:textSize="20sp"
app:layout_constraintBaseline_toBaselineOf="@id/tv1"
app:layout_constraintStart_toEndOf="@id/tv1" />

</androidx.constraintlayout.widget.ConstraintLayout>

通过layout_constraintBaseline_toBaselineOf我们就可以让两个不同大小的文案基线对齐

1.1.3 角度约束

有些时候我们需要一个控件在某个控件的某个角度的位置,那么通过其他的布局其实是不太好实现的,但是ConstraintLayout为我们提供了角度位置相关的属性

1
2
3
xml复制代码app:layout_constraintCircle=""         目标控件id
app:layout_constraintCircleAngle="" 对于目标的角度(0-360)
app:layout_constraintCircleRadius="" 到目标中心的距离

我们来实现一下下图的UI,jetpack图标在android图标的45度方向,距离为60dp

3.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
xml复制代码<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#DAF3FE"
tools:context=".MainActivity"
tools:ignore="HardcodedText">

<ImageView
android:id="@+id/android"
android:layout_width="100dp"
android:layout_height="100dp"
android:src="@drawable/android"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<ImageView
android:id="@+id/jetpack"
android:layout_width="60dp"
android:layout_height="60dp"
android:src="@drawable/jetpack"
app:layout_constraintCircle="@+id/android"
app:layout_constraintCircleAngle="45"
app:layout_constraintCircleRadius="70dp" />

</androidx.constraintlayout.widget.ConstraintLayout>

1.1.4 百分比偏移

有的时候我们需要让控件在父布局的水平方向或垂直方向的百分之多少的位置,可以使用如下属性:

1
2
xml复制代码app:layout_constraintHorizontal_bias=""   水平偏移 取值范围是0-1的小数
app:layout_constraintVertical_bias="" 垂直偏移 取值范围是0-1的小数

示例:控件A在父布局水平方向偏移0.3(30%),垂直方向偏移0.8(80%)

注意:在使用百分比偏移时,需要指定对应位置的约束条件

4.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
xml复制代码<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#DAF3FE"
tools:context=".MainActivity"
tools:ignore="HardcodedText">

<TextView
android:layout_width="100dp"
android:layout_height="60dp"
android:background="@drawable/tv_bg"
android:gravity="center"
android:text="A"
android:textColor="@color/black"
android:textSize="25sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.3"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.8" />

</androidx.constraintlayout.widget.ConstraintLayout>

1.2 控件内边距、外边距、GONE Margin

ConstraintLayout的内边距和外边距的使用方式其实是和其他布局一致的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ini复制代码<!--  外边距  -->
android:layout_margin="0dp"
android:layout_marginStart="0dp"
android:layout_marginLeft="0dp"
android:layout_marginTop="0dp"
android:layout_marginEnd="0dp"
android:layout_marginRight="0dp"
android:layout_marginBottom="0dp"

<!-- 内边距 -->
android:padding="0dp"
android:paddingStart="0dp"
android:paddingLeft="0dp"
android:paddingTop="0dp"
android:paddingEnd="0dp"
android:paddingRight="0dp"
android:paddingBottom="0dp"

ConstraintLayout除此之外还有GONE Margin,当依赖的目标view隐藏时会生效的属性,例如B被A依赖约束,当B隐藏时B会缩成一个点,自身的margin效果失效,A设置的GONE Margin就会生效,属性如下:

1
2
3
4
5
6
7
ini复制代码<!--  GONE Margin  -->
app:layout_goneMarginBottom="0dp"
app:layout_goneMarginEnd="0dp"
app:layout_goneMarginLeft="0dp"
app:layout_goneMarginRight="0dp"
app:layout_goneMarginStart="0dp"
app:layout_goneMarginTop="0dp"

示例:当目标控件是显示的时候GONE Margin不会生效

5.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
xml复制代码<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#DAF3FE"
tools:context=".MainActivity"
tools:ignore="HardcodedText">

<TextView
android:id="@+id/A"
android:layout_width="100dp"
android:layout_height="60dp"
android:layout_marginStart="100dp"
android:layout_marginTop="100dp"
android:background="@drawable/tv_bg"
android:gravity="center"
android:text="A"
android:textColor="@color/black"
android:textSize="25sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<!-- 该控件设置了 layout_goneMarginStart="100dp" 当A控件隐藏时才会生效 -->
<TextView
android:id="@+id/B"
android:layout_width="60dp"
android:layout_height="40dp"
android:background="@drawable/tv_bg"
android:gravity="center"
android:text="B"
android:textColor="@color/black"
android:textSize="25sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="@id/A"
app:layout_constraintStart_toEndOf="@id/A"
app:layout_constraintTop_toTopOf="@id/A"
app:layout_goneMarginStart="100dp" />

</androidx.constraintlayout.widget.ConstraintLayout>

当目标A控件隐藏时,B的GONE Margin就会生效

6.png

1.3 控件尺寸

1.3.1 尺寸限制

在ConstraintLayout中提供了一些尺寸限制的属性,可以用来限制最大、最小宽高度,这些属性只有在给出的宽度或高度为wrap_content时才会生效,比如想给宽度设置最小或最大值,那宽度就必须设置为wrap_content,这个比较简单就不放示例代码了,具体的属性如下:

1
2
3
4
ini复制代码android:minWidth=""   设置view的最小宽度
android:minHeight="" 设置view的最小高度
android:maxWidth="" 设置view的最大宽度
android:maxHeight="" 设置view的最大高度

1.3.2 0dp(MATCH_CONSTRAINT)

设置view的大小除了传统的wrap_content、指定尺寸、match_parent外,ConstraintLayout还可以设置为0dp(MATCH_CONSTRAINT),并且0dp的作用会根据设置的类型而产生不同的作用,进行设置类型的属性是layout_constraintWidth_default和layout_constraintHeight_default,取值可为spread、percent、wrap。具体的属性及示例如下:

1
2
ini复制代码app:layout_constraintWidth_default="spread|percent|wrap"
app:layout_constraintHeight_default="spread|percent|wrap"
  • spread(默认):占用所有的符合约束的空间

7.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
xml复制代码<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#DAF3FE"
tools:context=".MainActivity"
tools:ignore="HardcodedText">

<TextView
android:id="@+id/A"
android:layout_width="0dp"
android:layout_height="60dp"
android:layout_marginStart="50dp"
android:layout_marginTop="50dp"
android:layout_marginEnd="50dp"
android:background="@drawable/tv_bg"
android:gravity="center"
android:text="A"
android:textColor="@color/black"
android:textSize="25sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_default="spread" />

</androidx.constraintlayout.widget.ConstraintLayout>

可以看到,view的宽度适应了所有有效的约束空间,左右留出了margin的设置值50dp,这种效果就就是:自身view的大小充满可以配置的剩余空间,因为左右约束的都是父布局,所以view可配置的空间是整个父布局的宽度,又因为设置了margin,所以会留出margin的大小,因为spread是默认值,所以可以不写 app:layout_constraintWidth_default="spread"。

  • percent:按照父布局的百分比设置

8.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
xml复制代码<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#DAF3FE"
tools:context=".MainActivity"
tools:ignore="HardcodedText">

<TextView
android:id="@+id/A"
android:layout_width="0dp"
android:layout_height="60dp"
android:layout_marginTop="50dp"
android:background="@drawable/tv_bg"
android:gravity="center"
android:text="A"
android:textColor="@color/black"
android:textSize="25sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_default="percent"
app:layout_constraintWidth_percent="0.5" />

</androidx.constraintlayout.widget.ConstraintLayout>

percent模式的意思是自身view的尺寸是父布局尺寸的一定比例,上图所展示的是宽度是父布局宽度的0.5(50%,取值是0-1的小数),该模式需要配合layout_constraintWidth_percent使用,但是写了layout_constraintWidth_percent后,layout_constraintWidth_default="percent"其实就可以省略掉了。

  • wrap:匹配内容大小但不超过约束限制

9.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
xml复制代码<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#DAF3FE"
tools:context=".MainActivity"
tools:ignore="HardcodedText">

<!-- 宽度设置为wrap_content -->
<TextView
android:id="@+id/A"
android:layout_width="wrap_content"
android:layout_height="60dp"
android:layout_marginStart="100dp"
android:layout_marginTop="50dp"
android:layout_marginEnd="100dp"
android:background="@drawable/tv_bg"
android:gravity="center"
android:text="AAAAAAAAAAAAAAAAAA"
android:textColor="@color/black"
android:textSize="25sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_default="spread" />

<!-- 宽度设置为0dp wrap模式 -->
<TextView
android:id="@+id/B"
android:layout_width="0dp"
android:layout_height="60dp"
android:layout_marginStart="100dp"
android:layout_marginTop="150dp"
android:layout_marginEnd="100dp"
android:background="@drawable/tv_bg"
android:gravity="center"
android:text="BBBBBBBBBBBBBBBBBBBBBBB"
android:textColor="@color/black"
android:textSize="25sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_default="wrap" />

</androidx.constraintlayout.widget.ConstraintLayout>

这里写了两个控件作为对比,控件A宽度设置为wrap_content,宽度适应内容大小,并且设置了margin,但是显然宽度已经超过margin的设置值了,而控件B宽度设置为0dp wrap模式,宽度适应内容大小,并且不会超过margin的设置值,也就是不会超过约束限制,这就是这两者的区别。Google还提供了两个属性用于强制约束:

1
2
3
xml复制代码<!--  当一个view的宽或高,设置成wrap_content时  -->
app:layout_constrainedWidth="true|false"
app:layout_constrainedHeight="true|false"

还是上一个例子,这里将控件A设置了强制约束,展示出的效果和控件B是一样的了:

10.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
xml复制代码<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#DAF3FE"
tools:context=".MainActivity"
tools:ignore="HardcodedText">

<TextView
android:id="@+id/A"
android:layout_width="wrap_content"
android:layout_height="60dp"
android:layout_marginStart="100dp"
android:layout_marginTop="50dp"
android:layout_marginEnd="100dp"
android:background="@drawable/tv_bg"
android:gravity="center"
android:text="AAAAAAAAAAAAAAAAAA"
android:textColor="@color/black"
android:textSize="25sp"
android:textStyle="bold"
app:layout_constrainedWidth="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_default="spread" />

</androidx.constraintlayout.widget.ConstraintLayout>

除此之外,0dp还有一些其他的独特属性用于设置尺寸的大小限制

1
2
3
4
ini复制代码app:layout_constraintWidth_min=""   0dp下,宽度的最小值
app:layout_constraintHeight_min="" 0dp下,高度的最小值
app:layout_constraintWidth_max="" 0dp下,宽度的最大值
app:layout_constraintHeight_max="" 0dp下,高度的最大值

1.3.3 比例宽高(Ratio)

ConstraintLayout中可以对宽高设置比例,前提是至少有一个约束维度设置为0dp,这样比例才会生效,该属性可使用两种设置:

1.浮点值,表示宽度和高度之间的比率

2.宽度:高度,表示宽度和高度之间形式的比率

1
ini复制代码app:layout_constraintDimensionRatio=""  宽高比例

11.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
xml复制代码<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#DAF3FE"
tools:context=".MainActivity"
tools:ignore="HardcodedText">

<TextView
android:id="@+id/A"
android:layout_width="0dp"
android:layout_height="100dp"
android:background="@drawable/tv_bg"
android:gravity="center"
android:text="A"
android:textColor="@color/black"
android:textSize="25sp"
android:textStyle="bold"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

1.4 Chains(链)

Chains(链)也是一个非常好用的特性,它是将许多个控件在水平或者垂直方向,形成一条链,用于平衡这些控件的位置,那么如何形成一条链呢?形成一条链要求链中的控件在水平或者垂直方向,首尾互相约束,这样就可以形成一条链,水平方向互相约束形成的就是一条水平链,反之则是垂直链,下面看示例:

12.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
xml复制代码<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#DAF3FE"
tools:context=".MainActivity"
tools:ignore="HardcodedText">

<TextView
android:id="@+id/A"
android:layout_width="80dp"
android:layout_height="80dp"
android:background="@drawable/tv_bg"
android:gravity="center"
android:text="A"
android:textColor="@color/black"
android:textSize="25sp"
android:textStyle="bold"
app:layout_constraintEnd_toStartOf="@id/B"
app:layout_constraintHorizontal_chainStyle="spread"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<TextView
android:id="@+id/B"
android:layout_width="80dp"
android:layout_height="80dp"
android:background="@drawable/tv_bg"
android:gravity="center"
android:text="B"
android:textColor="@color/black"
android:textSize="25sp"
android:textStyle="bold"
app:layout_constraintEnd_toStartOf="@id/C"
app:layout_constraintStart_toEndOf="@id/A"
app:layout_constraintTop_toTopOf="parent" />

<TextView
android:id="@+id/C"
android:layout_width="80dp"
android:layout_height="80dp"
android:background="@drawable/tv_bg"
android:gravity="center"
android:text="C"
android:textColor="@color/black"
android:textSize="25sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/B"
app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

A、B、C,三个控件在水平方向上首尾互相约束,这样就形成了一条水平链,他们默认的模式是spread,均分剩余空间,我们可以使用layout_constraintHorizontal_chainStyle和layout_constraintVertical_chainStyle分别对水平和垂直链设置模式,模式可选的值有:spread、packed、spread_inside

  • spread(默认):均分剩余空间

12.png

  • spread_inside:两侧的控件贴近两边,剩余的控件均分剩余空间

13.png

  • packed:所有控件贴紧居中

14.png

Chains(链)还支持weight(权重)的配置,使用layout_constraintHorizontal_weight和layout_constraintVertical_weight进行设置链元素的权重

15.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
xml复制代码<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#DAF3FE"
tools:context=".MainActivity"
tools:ignore="HardcodedText">

<TextView
android:id="@+id/A"
android:layout_width="0dp"
android:layout_height="80dp"
android:background="@drawable/tv_bg"
android:gravity="center"
android:text="A"
android:textColor="@color/black"
android:textSize="25sp"
android:textStyle="bold"
app:layout_constraintEnd_toStartOf="@id/B"
app:layout_constraintHorizontal_weight="2"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed" />

<TextView
android:id="@+id/B"
android:layout_width="0dp"
android:layout_height="80dp"
android:background="@drawable/tv_bg"
android:gravity="center"
android:text="B"
android:textColor="@color/black"
android:textSize="25sp"
android:textStyle="bold"
app:layout_constraintEnd_toStartOf="@id/C"
app:layout_constraintHorizontal_weight="1"
app:layout_constraintStart_toEndOf="@id/A"
app:layout_constraintTop_toTopOf="parent" />

<TextView
android:id="@+id/C"
android:layout_width="0dp"
android:layout_height="80dp"
android:background="@drawable/tv_bg"
android:gravity="center"
android:text="C"
android:textColor="@color/black"
android:textSize="25sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_weight="3"
app:layout_constraintStart_toEndOf="@id/B"
app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

二. 辅助类

ConstraintLayout为了解决嵌套问题还提供了一系列的辅助控件帮助开发者布局,这些工具十分的方便,我在日常开发工作中也是使用的非常频繁

2.1 Guideline(参考线)

Guideline是一条参考线,可以帮助开发者进行辅助定位,并且实际上它并不会真正显示在布局中,像是数学几何中的辅助线一样,使用起来十分方便,出场率很高,Guideline也可以用来做一些百分比分割之类的需求,有着很好的屏幕适配效果,Guideline有水平和垂直方向之分,位置可以使用针对父级的百分比或者针对父级位置的距离

1
2
3
4
ini复制代码android:orientation="horizontal|vertical"  辅助线的对齐方式
app:layout_constraintGuide_percent="0-1" 距离父级宽度或高度的百分比(小数形式)
app:layout_constraintGuide_begin="" 距离父级起始位置的距离(左侧或顶部)
app:layout_constraintGuide_end="" 距离父级结束位置的距离(右侧或底部)

16.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
xml复制代码<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#DAF3FE"
tools:context=".MainActivity"
tools:ignore="HardcodedText">

<androidx.constraintlayout.widget.Guideline
android:id="@+id/Guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.5" />

<TextView
android:id="@+id/A"
android:layout_width="120dp"
android:layout_height="80dp"
android:background="@drawable/tv_bg"
android:gravity="center"
android:text="A"
android:textColor="@color/black"
android:textSize="25sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/Guideline" />

</androidx.constraintlayout.widget.ConstraintLayout>

上图中设置了一条水平方向位置在父级垂直方向0.5(50%)的Guideline,控件A的顶部依赖于Guideline,这样无论布局如何更改,Guideline的位置始终都会是父级垂直方向50%的位置,控件A的位置也不会偏离预设

2.2 Barrier(屏障)

这个Barrier和Guideline一样,也不会实际出现在布局中,它的作用如同其名,形成一个屏障、障碍,使用也非常多。这里借助constraintlayout网站来讲解Barrier。

当我们创建布局时,有时会遇到布局可以根据本地化而更改的情况。这里借助有一个非常简单的例子:

17.png

这里有三个文本视图:左边的textView1和textView2;右边的textView3。textView3被限制在textView1的末尾,这工作得很好——它完全根据我们需要来定位和大小textView3。

然而,如果我们需要支持多种语言,事情会变得更加复杂。如果我们添加德语翻译,那么我们就会遇到一个问题,因为在英文版本中,textView1中的文本比textView2中的文本长,而在德语中,textView2中的文本比textView1长:

18.png

这里的问题在于textView3仍然是相对于textView1的,所以textView2直接插入了textView3中。在设计视图里看起来更明显(白色背景的那个)。比较直接的解决办法是使用TableLayout,或者把 textView1 & textView2 包裹在一个垂直的,android:layout_width="wrap_content" 的 LinearLayout中。然后让textView3约束在这个LinearLayout的后面。但是我们有更好的办法:Barriers。Barriers的配置属性如下:

1
2
3
4
5
xml复制代码<!--  用于控制Barrier相对于给定的View的位置  -->
app:barrierDirection="top|bottom|left|right|start|end"

<!-- 取值是要依赖的控件的id,Barrier将会使用ids中最大的一个的宽/高作为自己的位置 -->
app:constraint_referenced_ids="id,id"

修改过后的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
xml复制代码<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">

<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:text="@string/warehouse"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:text="@string/hospital"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView1" />

<androidx.constraintlayout.widget.Barrier
android:id="@+id/barrier7"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="end"
app:constraint_referenced_ids="textView2,textView1" />

<TextView
android:id="@+id/textView3"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:text="@string/lorem_ipsum"
app:layout_constraintStart_toEndOf="@+id/barrier7"
app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

效果:

19.gif

为了看到整体的效果,可以切换语言,此时你会看到Barrier会自动位于较宽的那个textView后面,也就间接让textView3也位于了正确的位置。

上述例子是直接使用的constraintlayout网站中的例子,可以直接访问链接进行查看。

2.3 Group(组)

工作当中常常会有很多个控件同时隐藏或者显示的场景,传统做法要么是进行嵌套,对父布局进行隐藏或显示,要么就是一个一个设置,这显然都不是很好的办法,ConstraintLayout中的Group就是来解决这个问题的。Group的作用就是可以对一组控件同时隐藏或显示,没有其他的作用,它的属性如下:

1
ini复制代码app:constraint_referenced_ids="id,id"  加入组的控件id

示例:

20.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
xml复制代码<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#DAF3FE"
tools:context=".MainActivity"
tools:ignore="HardcodedText">

<TextView
android:id="@+id/A"
android:layout_width="100dp"
android:layout_height="60dp"
android:layout_marginTop="56dp"
android:background="@drawable/tv_bg"
android:gravity="center"
android:text="A"
android:textColor="@color/black"
android:textSize="25sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.115"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<TextView
android:id="@+id/B"
android:layout_width="100dp"
android:layout_height="60dp"
android:layout_marginTop="280dp"
android:background="@drawable/tv_bg"
android:gravity="center"
android:text="B"
android:textColor="@color/black"
android:textSize="25sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.758"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<TextView
android:id="@+id/C"
android:layout_width="100dp"
android:layout_height="60dp"
android:layout_marginTop="164dp"
android:background="@drawable/tv_bg"
android:gravity="center"
android:text="C"
android:textColor="@color/black"
android:textSize="25sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.437"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<androidx.constraintlayout.widget.Group
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="visible"
app:constraint_referenced_ids="A,B,C" />

</androidx.constraintlayout.widget.ConstraintLayout>

A、B、C三个view,受Group控制,当Group的visibility为visible时,它们都是正常显示的,设置为gone时,它们都会隐藏:

21.png

2.4 Placeholder(占位符)

Placeholder的作用就是占位,它可以在布局中占好位置,通过app:content=""属性,或者动态调用setContent()设置内容,来让某个控件移动到此占位符中

示例:

22.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
xml复制代码<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#DAF3FE"
tools:context=".MainActivity"
tools:ignore="HardcodedText">

<TextView
android:id="@+id/A"
android:layout_width="100dp"
android:layout_height="60dp"
android:background="@drawable/tv_bg"
android:gravity="center"
android:text="A"
android:textColor="@color/black"
android:textSize="25sp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<androidx.constraintlayout.widget.Placeholder
android:layout_width="100dp"
android:layout_height="60dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

当我们设置app:content="@+id/A"或者调用setContent()时,控件A就会被移动到Placeholder中,当然在布局中使用app:content=""显然就失去了它的作用。

2.5 Flow(流式虚拟布局)

Flow是用于构建链的新虚拟布局,当链用完时可以缠绕到下一行甚至屏幕的另一部分。当您在一个链中布置多个项目时,这很有用,但是您不确定容器在运行时的大小。您可以使用它来根据应用程序中的动态尺寸(例如旋转时的屏幕宽度)构建布局。Flow是一种虚拟布局。在ConstraintLayout中,虚拟布局(Virtual layouts)作为virtual view group的角色参与约束和布局中,但是它们并不会作为视图添加到视图层级结构中,而是仅仅引用其它视图来辅助它们在布局系统中完成各自的布局功能。

下面使用动画来展示Flow创建多个链将布局元素充裕地填充一整行:

40.gif

我们来看具体的例子:

23.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
xml复制代码<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#DAF3FE"
tools:context=".MainActivity"
tools:ignore="HardcodedText">

<TextView
android:id="@+id/A"
android:layout_width="60dp"
android:layout_height="60dp"
android:background="@drawable/tv_bg"
android:gravity="center"
android:text="A"
android:textColor="@color/black"
android:textSize="25sp"
android:textStyle="bold" />

<TextView
android:id="@+id/B"
android:layout_width="60dp"
android:layout_height="60dp"
android:background="@drawable/tv_bg"
android:gravity="center"
android:text="B"
android:textColor="@color/black"
android:textSize="25sp"
android:textStyle="bold" />

<TextView
android:id="@+id/C"
android:layout_width="60dp"
android:layout_height="60dp"
android:background="@drawable/tv_bg"
android:gravity="center"
android:text="C"
android:textColor="@color/black"
android:textSize="25sp"
android:textStyle="bold" />

<TextView
android:id="@+id/D"
android:layout_width="60dp"
android:layout_height="60dp"
android:background="@drawable/tv_bg"
android:gravity="center"
android:text="D"
android:textColor="@color/black"
android:textSize="25sp"
android:textStyle="bold" />

<TextView
android:id="@+id/E"
android:layout_width="60dp"
android:layout_height="60dp"
android:background="@drawable/tv_bg"
android:gravity="center"
android:text="E"
android:textColor="@color/black"
android:textSize="25sp"
android:textStyle="bold" />

<androidx.constraintlayout.helper.widget.Flow
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:constraint_referenced_ids="A,B,C,D,E"
app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

2.5.1 链约束

Flow的constraint_referenced_ids关联的控件是没有设置约束的,这一点和普通的链是不一样的,这种排列方式是Flow的默认方式none,我们可以使用app:flow_wrapMode=""属性来设置排列方式,并且我们还可以使用flow_horizontalGap和flow_verticalGap分别设置两个view在水平和垂直方向的间隔,下面我们再添加几个控件来展示三种排列方式:

  • none(默认值):所有引用的view形成一条链,水平居中,超出屏幕两侧的view不可见

24.png

  • chian:所引用的view形成一条链,超出部分会自动换行,同行的view会平分宽度。

25.png

  • aligned:所引用的view形成一条链,但view会在同行同列。

26.png

下面使用动画来展示三种效果的变化

41.gif

当flow_wrapMode的值是chian或aligned时,我们还可以针对不同的链进行配置,这里就不一一展示效果了,具体的属性如下:

1
2
3
4
5
6
7
ini复制代码app:flow_horizontalStyle="packed|spread|spread_inside"  所有水平链的配置
app:flow_verticalStyle="packed|spread|spread_inside" 所有垂直链的配置

app:flow_firstHorizontalStyle="packed|spread|spread_inside" 第一条水平链的配置,其他条不生效
app:flow_firstVerticalStyle="packed|spread|spread_inside" 第一条垂直链的配置,其他条不生效
app:flow_lastHorizontalStyle="packed|spread|spread_inside" 最后一条水平链的配置,其他条不生效
app:flow_lastVerticalStyle="packed|spread|spread_inside" 最后一条垂直链的配置,其他条不生效

2.5.2 对齐约束

上面展示的都是相同大小的view,那么不同大小view的对齐方式,Flow也提供了相应的属性进行配置(flow_wrapMode="aligned"时,我试着没有效果)

1
2
3
4
5
xml复制代码<!--  top:顶对齐、bottom:底对齐、center:中心对齐、baseline:基线对齐  -->
app:flow_verticalAlign="top|bottom|center|baseline"

<!-- start:开始对齐、end:结尾对齐、center:中心对齐 -->
app:flow_horizontalAlign="start|end|center"

使用flow_verticalAlign时,要求orientation的方向是horizontal,而使用flow_horizontalAlign时,要求orientation的方向是vertical

下面展示下各个效果:

horizontal 水平排列

  • top

27.png

1
2
3
4
5
6
7
8
xml复制代码<androidx.constraintlayout.helper.widget.Flow
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
app:constraint_referenced_ids="A,B,C,D,E,F,G,H,I,J"
app:flow_verticalAlign="top"
app:flow_wrapMode="chain"
app:layout_constraintTop_toTopOf="parent" />
  • bottom

28.png

1
2
3
4
5
6
7
8
xml复制代码<androidx.constraintlayout.helper.widget.Flow
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
app:constraint_referenced_ids="A,B,C,D,E,F,G,H,I,J"
app:flow_verticalAlign="bottom"
app:flow_wrapMode="chain"
app:layout_constraintTop_toTopOf="parent" />
  • center

29.png

1
2
3
4
5
6
7
8
xml复制代码<androidx.constraintlayout.helper.widget.Flow
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
app:constraint_referenced_ids="A,B,C,D,E,F,G,H,I,J"
app:flow_verticalAlign="center"
app:flow_wrapMode="chain"
app:layout_constraintTop_toTopOf="parent" />
  • baseline

30.png

1
2
3
4
5
6
7
8
xml复制代码<androidx.constraintlayout.helper.widget.Flow
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
app:constraint_referenced_ids="A,B,C,D,E,F,G,H,I,J"
app:flow_verticalAlign="baseline"
app:flow_wrapMode="chain"
app:layout_constraintTop_toTopOf="parent" />

垂直方向排列这里就不再作过多的展示了

2.5.3 数量约束

当flow_wrapMode属性为aligned和chian时,通过flow_maxElementsWrap属性控制每行最大的子View数量,例如我们设置为flow_maxElementsWrap=4,效果图如下:

31.png

2.6 Layer(层布局)

Layer继承自ConstraintHelper,是一个约束助手,相对于Flow来说,Layer的使用较为简单,常用来增加背景,或者共同动画,图层 (Layer) 在布局期间会调整大小,其大小会根据其引用的所有视图进行调整,代码的先后顺序也会决定着它的位置,如果代码在所有引用view的最后面,那么它就会在所有view的最上面,反之则是最下面,在最上面的时候如果添加背景,就会把引用的view覆盖掉,下面展示下添加背景的例子,做动画的例子这里不再展示了

32.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
xml复制代码<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#DAF3FE"
tools:context=".MainActivity"
tools:ignore="HardcodedText">

<androidx.constraintlayout.helper.widget.Layer
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/common_rect_white_100_10"
android:padding="10dp"
app:constraint_referenced_ids="AndroidImg,NameTv" />

<ImageView
android:id="@+id/AndroidImg"
android:layout_width="200dp"
android:layout_height="wrap_content"
android:src="@drawable/android"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<TextView
android:id="@+id/NameTv"
android:layout_width="100dp"
android:layout_height="40dp"
android:gravity="center"
android:text="Android"
android:textColor="@color/black"
android:textSize="25sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="@id/AndroidImg"
app:layout_constraintStart_toStartOf="@id/AndroidImg"
app:layout_constraintTop_toBottomOf="@id/AndroidImg" />

</androidx.constraintlayout.widget.ConstraintLayout>

可以看到,当Layer的代码在所有引用view的上面时,效果是正常的,因为此时所有的view都在Layer的上面,下面我们来看一下Layer代码在最后面时的情况:

33.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
xml复制代码<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#DAF3FE"
tools:context=".MainActivity"
tools:ignore="HardcodedText">

<ImageView
android:id="@+id/AndroidImg"
android:layout_width="200dp"
android:layout_height="wrap_content"
android:src="@drawable/android"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<TextView
android:id="@+id/NameTv"
android:layout_width="100dp"
android:layout_height="40dp"
android:gravity="center"
android:text="Android"
android:textColor="@color/black"
android:textSize="25sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="@id/AndroidImg"
app:layout_constraintStart_toStartOf="@id/AndroidImg"
app:layout_constraintTop_toBottomOf="@id/AndroidImg" />

<androidx.constraintlayout.helper.widget.Layer
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/common_rect_white_100_10"
android:padding="10dp"
app:constraint_referenced_ids="AndroidImg,NameTv" />

</androidx.constraintlayout.widget.ConstraintLayout>

我们可以看到,此时Layer已经把所有的view覆盖住了

2.7 ImageFilterButton & ImageFilterView

ImageFilterButton和ImageFilterView是两个控件,他们之间的关系就和ImageButton与ImageView是一样的,所以这里就只拿ImageFilterView来做讲解。从名字上来看,它们的定位是和过滤有关系的,它们的大致作用有两部分,一是可以用来做圆角图片,二是可以叠加图片资源进行混合过滤,下面一一展示:

2.7.1 圆角图片

ImageFilterButton和ImageFilterView可以使用两个属性来设置图片资源的圆角,分别是roundPercent和round,roundPercent接受的值类型是0-1的小数,根据数值的大小会使图片在方形和圆形之间按比例过度,round=可以设置具体圆角的大小,我在使用的过程中发现我的AndroidStudio,没有这两个属性的代码提示,也没有预览效果,但是运行起来是有效果的,可能是没有做好优化吧。最近很热门的一个话题,小米花费200万设计的新logo,我们拿来做做例子:

34.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
xml复制代码<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#DAF3FE"
tools:context=".MainActivity"
tools:ignore="HardcodedText">

<androidx.constraintlayout.utils.widget.ImageFilterView
android:layout_width="100dp"
android:layout_height="100dp"
android:src="@drawable/mi"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:roundPercent="0.7" />

</androidx.constraintlayout.widget.ConstraintLayout>

虽然和小米新logo的圆弧不太一样,不过这也不是我们考虑的地方,可以看到我们使用roundPercent设置了圆角为0.7(70%),实现一个圆角图片就是如此简单。

2.7.2 图片过滤

ImageFilterButton和ImageFilterView不但可以使用src来设置图片资源,还可以使用altSrc来设置第二个图片资源,altSrc提供的资源将会和src提供的资源通过crossfade属性形成交叉淡化效果,默认情况下,crossfade=0,altSrc所引用的资源不可见,取值在0-1。下面看例子:

  • crossfade=0

35.png

  • crossfade=0.5

36.png

  • crossfade=1

37.png

除此之外,warmth属性可以用来调节色温,brightness属性用来调节亮度,saturation属性用来调节饱和度,contrast属性用来调节对比度,下面展示一下各自属性和取值的效果:

38.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
xml复制代码<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#DAF3FE"
tools:context=".MainActivity"
tools:ignore="HardcodedText">

<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/view1"
android:layout_width="80dp"
android:layout_height="80dp"
android:src="@drawable/jetpack"
app:layout_constraintBottom_toBottomOf="@id/view2"
app:layout_constraintEnd_toStartOf="@+id/view2"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/view2"
app:warmth="0" />

<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/view2"
android:layout_width="80dp"
android:layout_height="80dp"
android:src="@drawable/jetpack"
app:layout_constraintBottom_toTopOf="@+id/view5"
app:layout_constraintEnd_toStartOf="@+id/view3"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/view1"
app:layout_constraintTop_toTopOf="parent"
app:warmth="5" />

<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/view3"
android:layout_width="80dp"
android:layout_height="80dp"
android:src="@drawable/jetpack"
app:layout_constraintBottom_toBottomOf="@id/view2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toEndOf="@+id/view2"
app:layout_constraintTop_toTopOf="@id/view2"
app:warmth="9"
tools:layout_editor_absoluteY="0dp" />

<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/view4"
android:layout_width="80dp"
android:layout_height="80dp"
android:src="@drawable/jetpack"
app:brightness="0"
app:layout_constraintBottom_toBottomOf="@id/view5"
app:layout_constraintEnd_toStartOf="@+id/view5"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/view5"
tools:layout_editor_absoluteY="136dp" />

<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/view5"
android:layout_width="80dp"
android:layout_height="80dp"
android:src="@drawable/jetpack"
app:brightness="5"
app:layout_constraintBottom_toTopOf="@+id/view8"
app:layout_constraintEnd_toStartOf="@+id/view6"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/view4"
app:layout_constraintTop_toBottomOf="@+id/view2" />

<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/view6"
android:layout_width="80dp"
android:layout_height="80dp"
android:src="@drawable/jetpack"
app:brightness="9"
app:layout_constraintBottom_toBottomOf="@id/view5"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/view5"
app:layout_constraintTop_toTopOf="@id/view5"
tools:layout_editor_absoluteY="136dp" />

<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/view7"
android:layout_width="80dp"
android:layout_height="80dp"
android:src="@drawable/jetpack"
app:layout_constraintBottom_toBottomOf="@id/view8"
app:layout_constraintEnd_toStartOf="@+id/view8"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/view8"
app:saturation="0"
tools:layout_editor_absoluteY="285dp" />

<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/view8"
android:layout_width="80dp"
android:layout_height="80dp"
android:src="@drawable/jetpack"
app:layout_constraintBottom_toTopOf="@+id/view11"
app:layout_constraintEnd_toStartOf="@+id/view9"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/view7"
app:layout_constraintTop_toBottomOf="@+id/view5"
app:saturation="5" />

<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/view9"
android:layout_width="80dp"
android:layout_height="80dp"
android:src="@drawable/jetpack"
app:layout_constraintBottom_toBottomOf="@id/view8"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/view8"
app:layout_constraintTop_toTopOf="@id/view8"
app:saturation="9"
tools:layout_editor_absoluteY="296dp" />

<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/view10"
android:layout_width="80dp"
android:layout_height="80dp"
android:src="@drawable/jetpack"
app:contrast="0"
app:layout_constraintBottom_toBottomOf="@id/view11"
app:layout_constraintEnd_toStartOf="@+id/view11"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/view11"
tools:layout_editor_absoluteY="437dp" />

<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/view11"
android:layout_width="80dp"
android:layout_height="80dp"
android:src="@drawable/jetpack"
app:contrast="5"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/view12"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/view10"
app:layout_constraintTop_toBottomOf="@+id/view8" />

<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/view12"
android:layout_width="80dp"
android:layout_height="80dp"
android:src="@drawable/jetpack"
app:contrast="9"
app:layout_constraintBottom_toBottomOf="@id/view11"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/view11"
app:layout_constraintTop_toTopOf="@id/view11"
tools:layout_editor_absoluteY="437dp" />

</androidx.constraintlayout.widget.ConstraintLayout>

2.8 MockView

你家产品经理经常会给你画原型图,但这绝对不是他们的专属,我们也有自己的原型图,一个成熟的程序员要学会给自己的产品经理画大饼,我们可以使用MockView来充当原型图,下面看例子:

39.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
xml复制代码<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#DAF3FE"
tools:context=".MainActivity"
tools:ignore="HardcodedText">

<androidx.constraintlayout.utils.widget.MockView
android:id="@+id/Avatar"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginStart="80dp"
android:layout_marginTop="100dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<androidx.constraintlayout.utils.widget.MockView
android:id="@+id/Name"
android:layout_width="100dp"
android:layout_height="30dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/Avatar"
app:layout_constraintTop_toTopOf="@id/Avatar" />

<androidx.constraintlayout.utils.widget.MockView
android:id="@+id/Age"
android:layout_width="100dp"
android:layout_height="30dp"
app:layout_constraintBottom_toBottomOf="@id/Avatar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/Avatar" />

</androidx.constraintlayout.widget.ConstraintLayout>

嗯!有内味了!

2.9 ConstraintProperties(流式API)

2.0 提供了ConstraintProperties可以使用流式 API修改属性

1
2
3
4
kotlin复制代码val properties = ConstraintProperties(findViewById(R.id.image))
properties.translationZ(32f)
.margin(ConstraintSet.START, 43)
.apply()

三. MotionLayout

Motion Layout是Constraint Layout 2.0中最令人期待的功能之一。它提供了一个丰富的动画系统来协调多个视图之间的动画效果。MotionLayout基于ConstraintLayout,并在其之上进行了扩展,允许您在多组约束 (或者ConstraintSets) 之间进行动画的处理。您可以对视图的移动、滚动、缩放、旋转、淡入淡出等一系列动画行为进行自定义,甚至可以定义各个动画本身的自定义属性。它还可以处理手势操作所产生的物理移动效果,以及控制动画的速度。使用MotionLayout构建的动画是可追溯且可逆的,这意味着您可以随意切换到动画过程中任意一个点,甚至可以倒着执行动画效果。Android Studio集成了 Motion Editor(动作编辑器),可以利用它来操作MotionLayout对动画进行生成、预览和编辑等操作。这样一来,在协调多个视图的动画时,就可以做到对各个细节进行精细操控。由于我自己也没有用过,且说起来篇幅也挺大,这里就不再讲解MotionLayout(主要是我也不会)

结语

至此,关于Constraint Layout的内容基本已经介绍完毕,因为内容较多,代码示例代码和图片也比较多,一次性看完实属不易,可以点击收藏供以后翻阅,写这篇文章我是经历了无数次放弃和重新拾起,内容确实太多了,再加上也已经有很多不错的博文来介绍Constraint Layout,但是他们的肯定没有我的全!😂如有错误请及时联系我,我会尽快修改更正。

参考

  • 新小梦:Constraintlayout 2.0:你们要的更新来了
  • 谷歌开发者:Constraint Layout 2.0 用法详解
  • constraintlayout网站

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

操作手册 DWR 使用原理

发表于 2021-04-09

总文档 :文章目录

Github : github.com/black-ant

一 . 前言

DWR 是一个可以用于前后端交互的工具 , 其本质是一个长连接 , 它具有以下的主要功能 :

  • 前端JS直接调用后端方法
  • 后端代码直接运行前端JS

DWR 这个工具是一个比较有历史的工具了 , 在集群或者其他特殊环境中 , 具有很大的局限性 , 相对而言其实有很多更合适的解决方案 , 这一篇主要是对其设计的思路进行一个分析 .

来看看早期的前后端强耦合的意义和设计思路.

二 . 使用

使用主要分为以下几个步骤 :

  • 配置准备
  • 准备 Java 端 service
  • 准备 DwrScriptSessionManager 用于Java 端调用 Web 端
  • 前端使用

2.1 配置准备

准备配置Bean

配置的 Bean 准备没有太多东西 , 这个Bean 主要用于生成一个 ServletRegistrationBean , 并且为其配置了多个属性

ServletRegistrationBean : ServletContextInitializer是用于在Servlet 3.0容器中注册 Servlet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
java复制代码    /**
* @param springDwrServlet SpringDwrServlet
* @return ServletRegistrationBean
*/
@Bean
public ServletRegistrationBean registDwrServlet(SpringDwrServlet springDwrServlet) {
ServletRegistrationBean servletRegister = new ServletRegistrationBean(springDwrServlet, "/dwr/*");
Map<String, String> initParameters = new HashMap<String, String>();
initParameters.put("debug", "true");
initParameters.put("activeReverseAjaxEnabled", "true");
initParameters.put("pollAndCometEnabled", "true");
servletRegister.setInitParameters(initParameters);
return servletRegister;
}

准备配置 XML : dwr-spring-config.xml

主要是开启注解 , 设置扫描路径

1
2
3
4
5
6
7
8
9
10
11
12
XML复制代码<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:dwr="http://www.directwebremoting.org/schema/spring-dwr"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd

http://www.directwebremoting.org/schema/spring-dwr http://www.directwebremoting.org/schema/spring-dwr-3.0.xsd">

<dwr:annotation-config/>
<dwr:configuration/>
<dwr:annotation-scan base-package="com.gang.comgangcasedwr" scanDataTransferObject="true" scanRemoteProxy="true"/>
</beans>

注意要导入该配置 : @ImportResource(locations = "classpath:dwr-spring-config.xml")

2.2 准备 Java 端

  • @RemoteProxy : 远程代理类
  • @RemoteMethod : 远程方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
java复制代码@Service
@RemoteProxy
public class Demo2Service {

private Logger logger = LoggerFactory.getLogger(this.getClass());

/**
* @return
*/
@RemoteMethod
public String hello() {
return "hello";
}

/**
* @param msg
* @return
*/
@RemoteMethod
public String echo(String msg) {
return msg;
}

}

2.3 其他类

DwrContainer : 用于扩展容器

  • 从Spring上下文中指定的配置中查找所有bean
  • 从Spring web应用程序上下文中加载配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
java复制代码public class SpringDwrContainer extends SpringContainer {

/**
* @see org.directwebremoting.spring.SpringContainer#addParameter(java.lang.String, java.lang.Object)
*/
@Override
public void addParameter(String askFor, Object valueParam) throws ContainerConfigurationException {

try {
Class<?> clz = ClassUtils.forName(askFor, ClassUtils.getDefaultClassLoader());

@SuppressWarnings("unchecked")
Map<String, Object> beansOfType = (Map<String, Object>) ((ListableBeanFactory) beanFactory)
.getBeansOfType(clz);

if (beansOfType.isEmpty()) {
super.addParameter(askFor, valueParam);
} else if (beansOfType.size() > 1) {
String key = StringUtils.uncapitalize(SpringDwrServlet.class.getSimpleName());
if (beansOfType.containsKey(key)) {
beans.put(askFor, beansOfType.get(key));
} else {
throw new ContainerConfigurationException("spring容器中无法找到对应servlet:" + key);
}
} else {
beans.put(askFor, beansOfType.values().iterator().next());
}
} catch (ClassNotFoundException ex) {
super.addParameter(askFor, valueParam);
}

}
}

SpringDwrServlet : 构建 DWRServlet

DWRServlet 的主要实现 : 处理所有对DWR的调用的servlet

DWRServlet从Spring IoC容器中检索配置。这可以通过两种方式实现 :

  • 使用Spring命名空间。当为DWR使用Spring名称空间时,这个servlet会自动获取DWR的配置
  • 显式地指定要选取的配置。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
java复制代码@Component
public class SpringDwrServlet extends DwrSpringServlet {

/** */
private static final long serialVersionUID = 1L;

@Override
protected SpringContainer createContainer(ServletConfig servletConfig) {
ApplicationContext appContext = getApplicationContext(servletConfig.getServletContext());

SpringDwrContainer springContainer = new SpringDwrContainer();
springContainer.setBeanFactory(appContext);
StartupUtil.setupDefaultContainer(springContainer, servletConfig);
return springContainer;
}
}

DwrScriptSessionManagerUtil : 添加 container

ScriptSessionListener : 该对象是一个监听器 , 当web应用程序中的活动会话列表发生更改时,将通知此接口的实现 , 即监听变化时处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
java复制代码public class DwrScriptSessionManagerUtil extends DwrServlet {

private Logger logger = LoggerFactory.getLogger(this.getClass());


/**
*
* @throws ServletException
*/
public void init() throws ServletException {

Container container = ServerContextFactory.get().getContainer();
ScriptSessionManager manager = container.getBean(ScriptSessionManager.class);
ScriptSessionListener listener = new ScriptSessionListener() {

public void sessionCreated(ScriptSessionEvent ev) {
HttpSession session = WebContextFactory.get().getSession();
String userId = (String) session.getAttribute("userId");
System.out.println("a ScriptSession is created!");
ev.getSession().setAttribute("userId", userId);
}

public void sessionDestroyed(ScriptSessionEvent ev) {
System.out.println("a ScriptSession is distroyed");
}
};
manager.addScriptSessionListener(listener);
}

public void init(final String key, final String value) throws ServletException {
Container container = ServerContextFactory.get().getContainer();
ScriptSessionManager manager = container.getBean(ScriptSessionManager.class);
ScriptSessionListener listener = new ScriptSessionListener() {
public void sessionCreated(ScriptSessionEvent ev) {
// HttpSession session = WebContextFactory.get().getSession();
//String userId = ((User) session.getAttribute("userinfo")).getHumanid() + "";
logger.info("a ScriptSession is created!");
ev.getSession().setAttribute(key, value);
}

public void sessionDestroyed(ScriptSessionEvent ev) {
logger.info("a ScriptSession is distroyed");
}
};
manager.addScriptSessionListener(listener);

}
}

2.4 前端源码

其中主要是多个工具 js , 直接获取即可 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
java复制代码<html>
<head>
<title></title>
<script type='text/javascript' src='/dwr/engine.js'></script>
<script type='text/javascript' src='/dwr/util.js'></script>
<script type='text/javascript' src='/dwr/interface/Demo2Service.js'></script>
<script type='text/javascript' src='/dwr/interface/Demo1Service.js'></script>
</head>

<div>hello</div>
<input type="button" onclick="onpage()" value="接收后端推送消息">
<script>
// 激活ajax
dwr.engine.setActiveReverseAjax(true)
// 页面未加载的时候是否发送通知
dwr.engine.setNotifyServerOnPageUnload(true, true)
// 出现错误后的处理方法
dwr.engine.setErrorHandler(function () {
console.log("Handler error")
})

Demo2Service.echo('回声测试', function (str) {
alert(str);
});

function onpage() {
// 页面加载直接调用这个函数,我这块使用点击按钮
Demo1Service.onPageLoad("后端功能开启");
}

// 后端会调用这个函数
function getmessage(data) {
console.log("data =" + data)
alert(data);
}


</script>
</html>

三 .简单看看前端源码

DWR 核心是建立一个长连接 , 通过其本身封装的 JS 和 Servlet ,通过 Invoke 代理类实现相互调用

DWR001.jpg

以下是 DWR 的前端结构

DWR002.jpg

先简单过一下前端 JS 的调用 :

可以看到每个 @RemoteProxy 标注的类都有一个 对应的 JS :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
java复制代码if (typeof dwr == 'undefined' || dwr.engine == undefined) throw new Error('You must include DWR engine before including this file');

(function() {
if (dwr.engine._getObject("Demo2Service") == undefined) {
var p;

p = {};

// 对应上文 本地的 echo
p.echo = function(p0, callback) {
return dwr.engine._execute(p._path, 'Demo2Service', 'echo', arguments);
};

// 对应上文 本地的 hello 方法
p.hello = function(callback) {
return dwr.engine._execute(p._path, 'Demo2Service', 'hello', arguments);
};
// 对应远程类
dwr.engine._setObject("Demo2Service", p);
}
})();

核心都是通过一个 JS 对象 dwr.engine 来完成 , 这个 execute 做了这些事

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
java复制代码
dwr.engine._execute = function(overridePath, scriptName, methodName, args) {
var path = overridePath || dwr.engine._effectivePath();
dwr.engine._singleShot = false;
if (dwr.engine._batch == null) {
dwr.engine.beginBatch();
dwr.engine._singleShot = true;
}

var batch = dwr.engine._batch;

// All the paths MUST be to the same servlet
if (batch.path == null) {
batch.path = path;
}
else {
if (batch.path != path) {
dwr.engine._handleError(batch, { name:"dwr.engine.multipleServlets", message:"Can't batch requests to multiple DWR Servlets." });
return;
}
}

// 主要调用
dwr.engine.batch.addCall(batch, scriptName, methodName, args);

if (dwr.engine._isHeartbeatBatch(batch)) {
// Heartbeats should fail fast.
batch.timeout = 750;
}

// Now we have finished remembering the call, we increment the call count
batch.map.callCount++;
if (dwr.engine._singleShot) {
return dwr.engine.endBatch();
}
};

image-20210409105226969.png

DWR003.jpg

四 . 后端源码分析

4.1 一切的起点

核心还是通过一个 HttpServlet 类实现的 :

1
2
3
4
5
6
7
8
java复制代码
public class DwrServlet extends HttpServlet

// 其中还有耳熟能详的
public void doGet(HttpServletRequest req, HttpServletResponse resp)
public void doPost(HttpServletRequest request, HttpServletResponse response)

// 后文我们能看到 , DWR Servlet 是怎么处理这个方法的

4.2 核心逻辑

核心逻辑主要是以下几个步骤 :

Step 1 : Servlet 对请求做出拦截

Step 2 : 获取请求的属性 (UrlProcessor) , 获取一个 CreatorModule

Step 3 : 通过该 Module 获取其中的 creator (Module 类似于一个模块体系)

Step 4 : 通过 create 获取对应的 method ,反射调用接口

Step 1 : 核心注解的扫描

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
java复制代码C02- AnnotationsConfigurator
M101- void configure(Container container)
M102- Set<Class<?>> getClasses(Container container)


// 此处就是 Bean 的加载注入
C03- DwrClassPathBeanDefinitionScanner
M301- postProcessBeanFactory
FOR- ConfigurableListableBeanFactory.getBeanDefinitionNames() : 获取了ConfigurableListableBeanFactory下的所有的 BeanDefinition
- 判断是否包含 RemoteProxy / GlobalFilter 等注解
?-PS :这里应该可以优化 , 记得Spring 是可以直接通过注解获取 BeanDefinition 的
IF- 如果包含 RemoteProxy
- beanDefinitionClass.getSimpleName() : 实际上就是类名
- 调用 M302:registerCreator(beanDefinitionHolder, beanDefinitionRegistry, beanDefinitionClass, javascript)
IF- 如果包含 GlobalFilter
- filters.add(new RuntimeBeanReference(beanName)) : 添加远程 Filter
M302- registerCreator
- BeanDefinitionBuilder.rootBeanDefinition(BeanCreator.class) : 准备了一个创造者 BeanDefinitionBuilder.BeanCreator
- BeanDefinitionBuilder.rootBeanDefinition(CreatorConfig.class) : 准备了一个创造者 BeanDefinitionBuilder.CreatorConfig
- 获取 Bean 中的 RemoteMethod
- 处理 Filter 注解 ,
- 将 RemoteMethod 和 Filter 放入 BeanDefinitionBuilder.CreatorConfig
- 准备 BeanDefinitionHolder , 并且完成 注册

- 加载 beanDefinitionClass , 再获取其内部包含指定注解的bean

Step 3 : 请求的拦截调用

当我们访问一个 DWR 请求的时候 , 会被 DwrServlet 所拦截

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
java复制代码C04- DwrServlet
M401- init
- container = createContainer(servletConfig) :
- StartupUtil.createAndSetupDefaultContainer(servletConfig)
?- 创建一个容器 ,深入的就不详述了 , 这里会反射到我们继承的 SpringDwrServlet
- container.getBean(WebContextBuilder.class) : 获得一个 WebContextBuilder
- webContextBuilder.engageThread(container, null, null) :??
- configureContainer(container, servletConfig)
M403- doPost : doPost 调用
- webContextBuilder.engageThread(container, request, response);
- UrlProcessor processor = container.getBean(UrlProcessor.class);
- processor.handle(request, response) :详见 M501 , 处理请求

C05- UrlProcessor
M501- handle
- request.getPathInfo() : /call/plaincall/Demo2Service.echo.dwr
- request.getContextPath() : ""
FOR- Entry<String, Handler> entry : urlMapping.entrySet() : urlMapping 进行循环判断
- Handler handler = entry.getValue() : 获取handler
- handle(handler, request, response) : 执行 Handler
?- 到这里执行的方式基本上已经清楚了 , 我们来找一下最后的代理方法就行

C06- CreatorModule
M601- executeMethod
?- 这里就可以看到具体的 Method 执行逻辑
- creator.getType().getMethod(methodDecl.getName(), methodDecl.getParameterTypes()) : 获取 Method
- AjaxFilterChain 调用执行
- return chain.doFilter(object, method, parameters) : 返回结果


AjaxFilterChain chain = new AjaxFilterChain(){
public Object doFilter(Object obj, Method meth, Object[] params) throws Exception{
// ....
Method m = obj.getClass().getMethod(meth.getName(), meth.getParameterTypes());
return m.invoke(obj, params);
}
};

Object reply = chain.doFilter(object, method, parameters);
return reply;

剩下一个 , Method 是怎么管理的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
java复制代码C07- DefaultRemoter
?- 可以看到中间经过了这样一个类 , 其中有一个 Call 对象
M701- execute(Call call)
?- call 主要是之前的请求属性生成的 , 这里就不细说了
- Module module = moduleManager.getModule(call.getScriptName(), true);
- MethodDeclaration method = call.getMethodDeclaration();
C08- BaseCallHandler
?- 这里就是通过 batch 生成了 calls
M- handle
- CallBatch batch = new CallBatch(request)
- Calls calls = marshallInbound(batch);
- Replies replies = remoter.execute(calls);
M- marshallInbound
- call.findMethod(moduleManager, converterManager, inctx, callNum) : 此处查找 method , 详见 701

C07- Call
M701- findMethod
Module module = moduleManager.getModule(scriptName, true);
List<MethodDeclaration> allMethods = new ArrayList<MethodDeclaration>();
allMethods.addAll(Arrays.asList(module.getMethods()));

C08- CreatorModule
?- Module 管理
- Class<?> creatorType = creator.getType()
- Method[] methods = creatorType.getMethods()

CreatorModule 的管理逻辑
到这里 Method 的逻辑就清楚了 , 最后一步 , CreatorModule 的管理逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
java复制代码
C09- DefaultCreatorManager
?- 核心管理类
?- SpringConfigurator.configure 中完成了创建

DwrServlet.init
DwrSpringServlet.configureContainer
- StartupUtil.configure(container, configurators);
StartupUtil.configure
- configurator.configure(container);
SpringConfigurator.configure
?- 这个类里面是总得管理类 , 没必要深入了
- creatorManager.addCreator(creator);

五 . 扩展

如何通过 DWR 实现扫码 :

Step 1 : 前端生成一个 id 向后端申请二维码

Step 2 : 后端通过ID等其他本地信息生成二维码提供给前端 (二维码中包含 : redirect_uri / 认证信息)

Step 3 : App 扫描 二维码 , 通过 认证信息认证 判断后 , 调用 redirect_uri回调

Step 4 : 后端拿到回调信息 , 其中会包含 Step 1 的ID , 通过 ID 来告知对应前端 (当然也可以群发前端自己认证)

总结

关于使用:

DWR 源码看完后 ,感觉逻辑并不复杂 , 像个容器管理框架一样 ,去代理和反射了一套类 ,通过其本身的一套请求路径机制 , 来调用本地的方法 . 耦合性颇高.

关于历史:

dwr 的使用其实和 rpc 有在’说黑话‘的思路上是一致的 , 都隐藏了网络层的具体逻辑 , 但是 rpc 仍然被 dubbo 作为主流 , 而dwr 却逐渐从大众视野里消失

想了一下 ,大概有以下几个原因 :

  • DWR的发布周期慢
  • 面对现在越来越复杂的网络环境 , dwr 的耦合性还是太强了 , 不便于开发和接口控制.
    • 尤其是近年来发展了微服务 ,这种初期看起来很简单的直接调用带了了非常多的问题
  • RPC 是为了解决更多的请求问题 ,它比 http 精简 , 保密 , 使调用简单化 , 而 DWR , 看多年前的吐槽就能发现 , 它会导致所有的 HTTP 请求被沾满 , 用法简单 ,但是使底层更加复杂
  • rpc 是服务端互调 , 其要解决的是服务端互调的复杂性
  • 前后端互调 , 本身就需要通过复杂性来控制业务能力 , 尤其是业务越来越复杂 , 其出发点主要集中在了简化 , 而没有提高可用性

总得来说 , 就是不够轻量级 , 比如看这些图 :

2009 年的时候 , 用 DWR 比 AJAX 更简单

image-20210409101941884.png

2015 年 , 大家都认为它太笨重了

image-20210409102109341.png

2020+ , JQuery 的笨重都被diss 到哪了 , 更轻量级的工具更受欢迎~

大人 , 时代变了!!!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

SpringBoot+Quartz图形化(有源码) 在我们日

发表于 2021-04-09

在我们日常的开发中,很多时候,定时任务都不是写死的,都是写好代码,页面配置,定时器执行规则都是灵活的,是写到数据库中,从而实现定时任务的动态配置,下面就通过一个简单的示例,来实现这个功能。

github.com/huyufan1995…

本次demo是SpringBoot的,毕竟是19年了,废话不多说了。

1.maven引入的依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
html复制代码<dependencies>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.0.0</version>
</dependency>

</dependencies>

2.application.yml配置文件

1
2
3
4
5
6
7
8
9
10
11
html复制代码spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/quartz_test?useSSL=false&useUnicode=true
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver

# Mybatis����
mybatis:
mapperLocations: classpath:mapper/*.xml
configLocation: classpath:mybatis.xml

3.整合quartz官方推荐要写一个配置文件quartz.properties

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
html复制代码# 固定前缀org.quartz
# 主要分为scheduler、threadPool、jobStore、plugin等部分
#
#
org.quartz.scheduler.instanceName = DefaultQuartzScheduler
org.quartz.scheduler.rmi.export = false
org.quartz.scheduler.rmi.proxy = false
org.quartz.scheduler.wrapJobExecutionInUserTransaction = false

# 实例化ThreadPool时,使用的线程类为SimpleThreadPool
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool

# threadCount和threadPriority将以setter的形式注入ThreadPool实例
# 并发个数
org.quartz.threadPool.threadCount = 5
# 优先级
org.quartz.threadPool.threadPriority = 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true

org.quartz.jobStore.misfireThreshold = 5000

# 默认存储在内存中
#org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore

#持久化
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX

org.quartz.jobStore.tablePrefix = QRTZ_

org.quartz.jobStore.dataSource = qzDS

org.quartz.dataSource.qzDS.driver = com.mysql.jdbc.Driver

org.quartz.dataSource.qzDS.URL = jdbc:mysql://127.0.0.1:3306/quartz_test?useSSL=false&useUnicode=true&characterEncoding=UTF-8

org.quartz.dataSource.qzDS.user = root

org.quartz.dataSource.qzDS.password = 123456

org.quartz.dataSource.qzDS.maxConnections = 10

4.mysql需要的quartz官方表支持

DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS;

DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS;

DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE;

DROP TABLE IF EXISTS QRTZ_LOCKS;

DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS;

DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS;

DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS;

DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS;

DROP TABLE IF EXISTS QRTZ_TRIGGERS;

DROP TABLE IF EXISTS QRTZ_JOB_DETAILS;

DROP TABLE IF EXISTS QRTZ_CALENDARS;

CREATE TABLE QRTZ_JOB_DETAILS

(

SCHED_NAME VARCHAR(120) NOT NULL,

JOB_NAME VARCHAR(200) NOT NULL,

JOB_GROUP VARCHAR(200) NOT NULL,

DESCRIPTION VARCHAR(250) NULL,

JOB_CLASS_NAME VARCHAR(250) NOT NULL,

IS_DURABLE VARCHAR(1) NOT NULL,

IS_NONCONCURRENT VARCHAR(1) NOT NULL,

IS_UPDATE_DATA VARCHAR(1) NOT NULL,

REQUESTS_RECOVERY VARCHAR(1) NOT NULL,

JOB_DATA BLOB NULL,

PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)

);

CREATE TABLE QRTZ_TRIGGERS

(

SCHED_NAME VARCHAR(120) NOT NULL,

TRIGGER_NAME VARCHAR(200) NOT NULL,

TRIGGER_GROUP VARCHAR(200) NOT NULL,

JOB_NAME VARCHAR(200) NOT NULL,

JOB_GROUP VARCHAR(200) NOT NULL,

DESCRIPTION VARCHAR(250) NULL,

NEXT_FIRE_TIME BIGINT(13) NULL,

PREV_FIRE_TIME BIGINT(13) NULL,

PRIORITY INTEGER NULL,

TRIGGER_STATE VARCHAR(16) NOT NULL,

TRIGGER_TYPE VARCHAR(8) NOT NULL,

START_TIME BIGINT(13) NOT NULL,

END_TIME BIGINT(13) NULL,

CALENDAR_NAME VARCHAR(200) NULL,

MISFIRE_INSTR SMALLINT(2) NULL,

JOB_DATA BLOB NULL,

PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),

FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)

REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP)

);

CREATE TABLE QRTZ_SIMPLE_TRIGGERS

(

SCHED_NAME VARCHAR(120) NOT NULL,

TRIGGER_NAME VARCHAR(200) NOT NULL,

TRIGGER_GROUP VARCHAR(200) NOT NULL,

REPEAT_COUNT BIGINT(7) NOT NULL,

REPEAT_INTERVAL BIGINT(12) NOT NULL,

TIMES_TRIGGERED BIGINT(10) NOT NULL,

PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),

FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)

REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)

);

CREATE TABLE QRTZ_CRON_TRIGGERS

(

SCHED_NAME VARCHAR(120) NOT NULL,

TRIGGER_NAME VARCHAR(200) NOT NULL,

TRIGGER_GROUP VARCHAR(200) NOT NULL,

CRON_EXPRESSION VARCHAR(200) NOT NULL,

TIME_ZONE_ID VARCHAR(80),

PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),

FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)

REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)

);

CREATE TABLE QRTZ_SIMPROP_TRIGGERS

(

SCHED_NAME VARCHAR(120) NOT NULL,

TRIGGER_NAME VARCHAR(200) NOT NULL,

TRIGGER_GROUP VARCHAR(200) NOT NULL,

STR_PROP_1 VARCHAR(512) NULL,

STR_PROP_2 VARCHAR(512) NULL,

STR_PROP_3 VARCHAR(512) NULL,

INT_PROP_1 INT NULL,

INT_PROP_2 INT NULL,

LONG_PROP_1 BIGINT NULL,

LONG_PROP_2 BIGINT NULL,

DEC_PROP_1 NUMERIC(13,4) NULL,

DEC_PROP_2 NUMERIC(13,4) NULL,

BOOL_PROP_1 VARCHAR(1) NULL,

BOOL_PROP_2 VARCHAR(1) NULL,

PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),

FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)

REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)

);

CREATE TABLE QRTZ_BLOB_TRIGGERS

(

SCHED_NAME VARCHAR(120) NOT NULL,

TRIGGER_NAME VARCHAR(200) NOT NULL,

TRIGGER_GROUP VARCHAR(200) NOT NULL,

BLOB_DATA BLOB NULL,

PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),

FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)

REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)

);

CREATE TABLE QRTZ_CALENDARS

(

SCHED_NAME VARCHAR(120) NOT NULL,

CALENDAR_NAME VARCHAR(200) NOT NULL,

CALENDAR BLOB NOT NULL,

PRIMARY KEY (SCHED_NAME,CALENDAR_NAME)

);

CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS

(

SCHED_NAME VARCHAR(120) NOT NULL,

TRIGGER_GROUP VARCHAR(200) NOT NULL,

PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)

);

CREATE TABLE QRTZ_FIRED_TRIGGERS

(

SCHED_NAME VARCHAR(120) NOT NULL,

ENTRY_ID VARCHAR(95) NOT NULL,

TRIGGER_NAME VARCHAR(200) NOT NULL,

TRIGGER_GROUP VARCHAR(200) NOT NULL,

INSTANCE_NAME VARCHAR(200) NOT NULL,

FIRED_TIME BIGINT(13) NOT NULL,

SCHED_TIME BIGINT(13) NOT NULL,

PRIORITY INTEGER NOT NULL,

STATE VARCHAR(16) NOT NULL,

JOB_NAME VARCHAR(200) NULL,

JOB_GROUP VARCHAR(200) NULL,

IS_NONCONCURRENT VARCHAR(1) NULL,

REQUESTS_RECOVERY VARCHAR(1) NULL,

PRIMARY KEY (SCHED_NAME,ENTRY_ID)

);

CREATE TABLE QRTZ_SCHEDULER_STATE

(

SCHED_NAME VARCHAR(120) NOT NULL,

INSTANCE_NAME VARCHAR(200) NOT NULL,

LAST_CHECKIN_TIME BIGINT(13) NOT NULL,

CHECKIN_INTERVAL BIGINT(13) NOT NULL,

PRIMARY KEY (SCHED_NAME,INSTANCE_NAME)

);

CREATE TABLE QRTZ_LOCKS

(

SCHED_NAME VARCHAR(120) NOT NULL,

LOCK_NAME VARCHAR(40) NOT NULL,

PRIMARY KEY (SCHED_NAME,LOCK_NAME)

);

5.可以写一个接口,方便扩展

6.实体类Entity

7.因为springboot都是写配置类,我们不写尽量不写配置文件,所以如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
html复制代码package com.example.quartz.config;

import org.quartz.Scheduler;
import org.quartz.ee.servlet.QuartzInitializerListener;
import org.springframework.beans.factory.config.PropertiesFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;

import java.io.IOException;
import java.util.Properties;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;


@Configuration
public class SchedulerConfig {

@Bean(name="SchedulerFactory")
public SchedulerFactoryBean schedulerFactoryBean() throws IOException {
SchedulerFactoryBean factory = new SchedulerFactoryBean();
factory.setQuartzProperties(quartzProperties());
return factory;
}

@Bean
public Properties quartzProperties() throws IOException {
PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.properties"));
//在quartz.properties中的属性被读取并注入后再初始化对象
propertiesFactoryBean.afterPropertiesSet();
return propertiesFactoryBean.getObject();
}

/*
* quartz初始化监听器
*/
@Bean
public QuartzInitializerListener executorListener() {
return new QuartzInitializerListener();
}

/*
* 通过SchedulerFactoryBean获取Scheduler的实例
*/
@Bean(name="Scheduler")
public Scheduler scheduler() throws IOException {
return schedulerFactoryBean().getScheduler();
}
}

8.主要Controller代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
html复制代码package com.example.quartz.controller;


import java.util.HashMap;
import java.util.Map;

import com.example.quartz.entity.BaseJob;
import com.example.quartz.entity.JobAndTrigger;
import com.example.quartz.service.IJobAndTriggerService;
import com.github.pagehelper.PageInfo;
import org.quartz.CronScheduleBuilder;
import org.quartz.CronTrigger;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.TriggerBuilder;
import org.quartz.TriggerKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;



@RestController
@RequestMapping(value="/job")
public class JobController
{
@Autowired
private IJobAndTriggerService iJobAndTriggerService;

//加入Qulifier注解,通过名称注入bean
@Autowired @Qualifier("Scheduler")
private Scheduler scheduler;

private static Logger log = LoggerFactory.getLogger(JobController.class);


@PostMapping(value="/addjob")
public void addjob(@RequestParam(value="jobClassName")String jobClassName,
@RequestParam(value="jobGroupName")String jobGroupName,
@RequestParam(value="cronExpression")String cronExpression) throws Exception
{
addJob(jobClassName, jobGroupName, cronExpression);
}

public void addJob(String jobClassName, String jobGroupName, String cronExpression)throws Exception{

// 启动调度器
scheduler.start();

//构建job信息
JobDetail jobDetail = JobBuilder.newJob(getClass(jobClassName).getClass()).withIdentity(jobClassName, jobGroupName).build();

//表达式调度构建器(即任务执行的时间)
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);

//按新的cronExpression表达式构建一个新的trigger
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(jobClassName, jobGroupName)
.withSchedule(scheduleBuilder).build();

try {
scheduler.scheduleJob(jobDetail, trigger);

} catch (SchedulerException e) {
System.out.println("创建定时任务失败"+e);
throw new Exception("创建定时任务失败");
}
}

/**
* 暂停
* @param jobClassName
* @param jobGroupName
* @throws Exception
*/
@PostMapping(value="/pausejob")
public void pausejob(@RequestParam(value="jobClassName")String jobClassName, @RequestParam(value="jobGroupName")String jobGroupName) throws Exception
{
jobPause(jobClassName, jobGroupName);
}

public void jobPause(String jobClassName, String jobGroupName) throws Exception
{
scheduler.pauseJob(JobKey.jobKey(jobClassName, jobGroupName));
}

/**
* 恢复
* @param jobClassName
* @param jobGroupName
* @throws Exception
*/
@PostMapping(value="/resumejob")
public void resumejob(@RequestParam(value="jobClassName")String jobClassName, @RequestParam(value="jobGroupName")String jobGroupName) throws Exception
{
jobresume(jobClassName, jobGroupName);
}

public void jobresume(String jobClassName, String jobGroupName) throws Exception
{
scheduler.resumeJob(JobKey.jobKey(jobClassName, jobGroupName));
}

/**
* 重启
* @param jobClassName
* @param jobGroupName
* @param cronExpression
* @throws Exception
*/
@PostMapping(value="/reschedulejob")
public void rescheduleJob(@RequestParam(value="jobClassName")String jobClassName,
@RequestParam(value="jobGroupName")String jobGroupName,
@RequestParam(value="cronExpression")String cronExpression) throws Exception
{
jobreschedule(jobClassName, jobGroupName, cronExpression);
}

public void jobreschedule(String jobClassName, String jobGroupName, String cronExpression) throws Exception
{
try {
TriggerKey triggerKey = TriggerKey.triggerKey(jobClassName, jobGroupName);
// 表达式调度构建器
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);

CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);

// 按新的cronExpression表达式重新构建trigger
trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();

// 按新的trigger重新设置job执行
scheduler.rescheduleJob(triggerKey, trigger);
} catch (SchedulerException e) {
System.out.println("更新定时任务失败"+e);
throw new Exception("更新定时任务失败");
}
}

/**
* 删除
* @param jobClassName
* @param jobGroupName
* @throws Exception
*/
@PostMapping(value="/deletejob")
public void deletejob(@RequestParam(value="jobClassName")String jobClassName, @RequestParam(value="jobGroupName")String jobGroupName) throws Exception
{
jobdelete(jobClassName, jobGroupName);
}

public void jobdelete(String jobClassName, String jobGroupName) throws Exception
{
scheduler.pauseTrigger(TriggerKey.triggerKey(jobClassName, jobGroupName));
scheduler.unscheduleJob(TriggerKey.triggerKey(jobClassName, jobGroupName));
scheduler.deleteJob(JobKey.jobKey(jobClassName, jobGroupName));
}


@GetMapping(value="/queryjob")
public Map<String, Object> queryjob(@RequestParam(value="pageNum")Integer pageNum, @RequestParam(value="pageSize")Integer pageSize)
{
PageInfo jobAndTrigger = iJobAndTriggerService.getJobAndTriggerDetails(pageNum, pageSize);
Map<String, Object> map = new HashMap<String, Object>();
map.put("JobAndTrigger", jobAndTrigger);
map.put("number", jobAndTrigger.getTotal());
return map;
}

public static BaseJob getClass(String classname) throws Exception
{
Class<?> class1 = Class.forName(classname);
return (BaseJob)class1.newInstance();
}


}

9.service实现类

10.最重要的Mapper文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
html复制代码<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.example.quartz.dao.JobAndTriggerMapper">

<resultMap id="BaseResultMap" type="com.example.quartz.entity.JobAndTrigger">
<result column="JOB_NAME" property="JOB_NAME"/>
<result column="JOB_GROUP" property="JOB_GROUP"/>
<result column="JOB_CLASS_NAME" property="JOB_CLASS_NAME"/>
<result column="TRIGGER_NAME" property="TRIGGER_NAME"/>
<result column="TRIGGER_GROUP" property="TRIGGER_GROUP"/>
<result column="REPEAT_INTERVAL" property="REPEAT_INTERVAL"/>
<result column="TIMES_TRIGGERED" property="TIMES_TRIGGERED"/>
<result column="CRON_EXPRESSION" property="CRON_EXPRESSION"/>
<result column="TIME_ZONE_ID" property="TIME_ZONE_ID"/>


</resultMap>

<select id="getJobAndTriggerDetails" resultMap="BaseResultMap">
SELECT
qrtz_job_details.JOB_NAME,
qrtz_job_details.JOB_GROUP,
qrtz_job_details.JOB_CLASS_NAME,
qrtz_triggers.TRIGGER_NAME,
qrtz_triggers.TRIGGER_GROUP,
qrtz_cron_triggers.CRON_EXPRESSION,
qrtz_cron_triggers.TIME_ZONE_ID
FROM
qrtz_job_details
JOIN qrtz_triggers
JOIN qrtz_cron_triggers ON qrtz_job_details.JOB_NAME = qrtz_triggers.JOB_NAME
AND qrtz_triggers.TRIGGER_NAME = qrtz_cron_triggers.TRIGGER_NAME
AND qrtz_triggers.TRIGGER_GROUP = qrtz_cron_triggers.TRIGGER_GROUP
AND qrtz_job_details.JOb_GROUP = qrtz_cron_triggers.TRIGGER_GROUP
</select>

</mapper>

效果

问题整理:官方提供的表中,有个暂停的记录表,但是我使用暂停过程中,确实能够暂停,但是表中无记录信息,如果正式开发中,肯定需要页面开始与暂停切换,状态的一些信息,最好是将定时器的一部分信息在存放自己生成的表中,方便业务扩展

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1…688689690…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%