青花小记

这个人很懒,只想写写代码做做饭

0%

@Order 注解定义了类、方法和字段的优先级(排序情况),value 是可选的,默认为Ordered.LOWEST_PRECEDENCE,即最低优先级。表示 Ordered 接口中的 order 属性。

目前看到的 @Order 注解都是用在类上的,没有看到过用在方法和字段上的,包括 Spring 自有类 DefaultErrorAttributes、LogbackLoggingSystem.Factory 等。

1
2
3
4
5
6
7
8
9
10
11
12
13
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@Documented
public @interface Order {

/**
* 优先级排序值,value 的值越大,优先级越低
* 默认值为 Ordered.LOWEST_PRECEDENCE(Integer.MAX_VALUE)
* @see Ordered#getOrder()
*/
int value() default Ordered.LOWEST_PRECEDENCE;

}

从 Spring 4.0 开始,Spring 中的很多组件都支持基于 Order 注解的排序,但是使用了该注解后,不会影响bean的加载顺序,Bean的加载顺序是由依赖关系和 @DependsOn 注解来决定的。

我们定义两个bean:OrderBean1和OrderBean2,然后通过 @Bean 注入到容器中,启动 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
public class OrderBean1 {

public OrderBean1() {
System.out.println("init Order Bean 1");
}
}

public class OrderBean2 {

public OrderBean2() {
System.out.println("init Order Bean 2");
}
}

@Configuration
@Order(200)
public class BeanConfig {

public BeanConfig() {
System.out.println("bean config");
}

@Bean
public OrderBean1 orderBean1() {
return new OrderBean1();
}
}


@Configuration
@Order(100)
public class BeanConfig2 {

public BeanConfig2() {
System.out.println("bean config 2");
}

@Bean
public OrderBean2 orderBean2() {
return new OrderBean2();
}
}

虽然 BeanConfig 和 BeanConfig2 都加了 Order 注解,且 BeanConfig2 的优先级更高,但是实际的日志打印是

1
2
3
4
bean config
bean config 2
init Order Bean 1
init Order Bean 2

BeanConfig2 并没有优先于 BeanConfig 进行加载。

如果想要 BeanConfig2 优先加载,可以借助 @DependsOn 注解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Configuration
@DependsOn("beanConfig2")
public class BeanConfig1 {

public BeanConfig1() {
System.out.println("bean config 1");
}

@Bean
@DependsOn("orderBean2")
public OrderBean1 orderBean1() {
return new OrderBean1();
}
}

日志打印为:

1
2
3
4
bean config 2
bean config 1
init Order Bean 2
init Order Bean 1

那 Order 注解在什么场景下才能使用呢?在进行执行顺序的排序时使用。

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
public interface OrderBean {
void order();
}

@Order(200)
public class OrderBean1 implements OrderBean{

@Override
public void order() {
System.out.println("order bean1 ---> order command");
}
}

@Order(100)
public class OrderBean2 implements OrderBean{

@Override
public void order() {
System.out.println("order bean2 ---> order command");
}
}

@Configuration
public class BeanCommandConfig {

public BeanCommandConfig(List<OrderBean> orderBeans) {
orderBeans.forEach(orderBean -> orderBean.order());
}
}

输出信息为

1
2
order bean2 --->  order command
order bean1 ---> order command

可以看到,先删除了 bean2 的内容,再输出了 bean1 的内容。

SpringBoot中的典型应用就是 CommandLineRunner。

Interface used to indicate that a bean should run when it is contained within a SpringApplication. Multiple CommandLineRunner beans can be defined within the same application context and can be ordered using the Ordered interface or @Order annotation.

翻译:多个CommandLineRunner bean 可以在同一个应用程序上下文中定义,并且可以使用Ordered接口或@Order注释进行排序

示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Component
@Order(5)
public class Runner1 implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
System.out.println("runner 1 start");
}
}

@Component
@Order(1)
public class Runner2 implements CommandLineRunner {

@Override
public void run(String... args) throws Exception {
System.out.println("runner 2 start");
}
}

启动 SpringBoot 工程,查看控制台输出

1
2
runner 2 start
runner 1 start

可以看到,两个 Runner 按照配置的优先级执行,先运行 Runner2,再运行 Runner1。

从 Spring 4.1 开始,jakarta.annotation.Priority 可在一些排序场景中替代 @Order 的作用,但是在修饰单个元素时,@Priority 可能具有其他语义。

除了这两个注解外,也可以实现 Ordered 接口,来设置优先级信息,相对使用注解的方式更加灵活。

Order注解

@Order 注解定义了类、方法和字段的优先级(排序情况),value 是可选的,默认为Ordered.LOWEST_PRECEDENCE,即最低优先级。表示 Ordered 接口中的 order 属性。

目前看到的 @Order 注解都是用在类上的,没有看到过用在方法和字段上的,包括 Spring 自有类 DefaultErrorAttributes、LogbackLoggingSystem.Factory 等。

1
2
3
4
5
6
7
8
9
10
11
12
13
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@Documented
public @interface Order {

/**
* 优先级排序值,value 的值越大,优先级越低
* 默认值为 Ordered.LOWEST_PRECEDENCE(Integer.MAX_VALUE)
* @see Ordered#getOrder()
*/
int value() default Ordered.LOWEST_PRECEDENCE;

}

从 Spring 4.0 开始,Spring 中的很多组件都支持基于 Order 注解的排序,使用了该注解后,会影响依赖注入的优先级,但是不影响bean的加载顺序,因为加载顺序受依赖关系和 @DependsOn声明影响的。

这句理解上还是有问题~

从 Spring 4.1 开始,jakarta.annotation.Priority 可在一些排序场景中替代 @Order 的作用,但是在修饰单个元素时,@Priority 可能具有其他语义。

除了这两个注解外,也可以实现 Ordered 接口,来设置优先级信息,相对使用注解的方式更加灵活。

Priority注解

@Priority 注解位于 jakarta.annotation 包下,可以标识类或参数以什么样的顺序来执行或使用。

Priority 注解的具体作用由具体的使用类来定义,如在Jakarta Interceptors 规范定义了使用拦截器的优先级来控制调用拦截器的顺序。

value 一般情况下应该是非负值,负值作为保留值有特定的含义,如“未定义”或“未指定”等。

定义 @Priority 使用的规范可以定义允许的优先级范围和任何具有特殊含义的优先级值。

1
2
3
4
5
6
7
8
9
@Target({TYPE,PARAMETER}) //只能用于TYPE和PARAMETER,这是和Order注解不一样的地方
@Retention(RUNTIME)
@Documented
public @interface Priority {
/**
* 优先级排序值
*/
int value();
}

@Priorityvalue 越小,优先级越高。

OrderUtils类

OrderUtils 用于对添加了 @Order注解和 @Priority注解的类,根据声明的顺序和优先级进行处理,相当于是两个注解的具体实现。

OrderUtils 提供了三个常量:

1
2
3
4
5
6
7
8
//用于缓存每个Class的排序值value,如果没有使用 @Order注解,则使用NOT_ANNOTATED标记记录
private static final Map<AnnotatedElement, Object> orderCache = new ConcurrentReferenceHashMap<>(64);

//未用 @Order 注解的缓存标记
private static final Object NOT_ANNOTATED = new Object();

//Priority注解的类路径
private static final String JAVAX_PRIORITY_ANNOTATION = "jakarta.annotation.Priority";

针对 @Order@Priority 注解提供了4个 getOrder 的方法,进行排序值的获取。

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
//获取某个类的排序值,如果排序值未设置,则返回默认值
public static int getOrder(Class<?> type, int defaultOrder) {
Integer order = getOrder(type);
return (order != null ? order : defaultOrder);
}

//获取某个类的排序值,如果排序值未设置,则返回默认值
@Nullable
public static Integer getOrder(Class<?> type, @Nullable Integer defaultOrder) {
Integer order = getOrder(type);
return (order != null ? order : defaultOrder);
}

//获取某个类的排序值,可返回null值
@Nullable
public static Integer getOrder(Class<?> type) {
return getOrder((AnnotatedElement) type);
}

//获取某个类的排序值,可返回null值
@Nullable
public static Integer getOrder(AnnotatedElement element) {
return getOrderFromAnnotations(element, MergedAnnotations.from(element, SearchStrategy.TYPE_HIERARCHY));
}

可以看到,getOrder(Class<?> type, int defaultOrder)getOrder(Class<?> type, @Nullable Integer defaultOrder)调用了 getOrder(Class<?> type) , 而 getOrder(Class<?> type) 则调用了 getOrder(AnnotatedElement element)

看看 getOrderFromAnnotations 的具体实现:

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
/**
* 从指定的 Order注解集合中返回对应的顺序值
*
*
*
*/
@Nullable
static Integer getOrderFromAnnotations(AnnotatedElement element, MergedAnnotations annotations) {

if (!(element instanceof Class)) {
return findOrder(annotations);
}
//缓存中是否已存在
Object cached = orderCache.get(element);
if (cached != null) {
//转换成Integer类型
return (cached instanceof Integer ? (Integer) cached : null);
}
//获取Order的排序值,并写入到orderCache中
Integer result = findOrder(annotations);
orderCache.put(element, result != null ? result : NOT_ANNOTATED);
//返回排序值
return result;
}

@Nullable
private static Integer findOrder(MergedAnnotations annotations) {
//判断是否加了Order注解,如果加了,则获取VALUE值,并返回
MergedAnnotation<Order> orderAnnotation = annotations.get(Order.class);
if (orderAnnotation.isPresent()) {
return orderAnnotation.getInt(MergedAnnotation.VALUE);
}
//判断是否加了Priority注解,如果加了,则获取VALUE值,并返回
MergedAnnotation<?> priorityAnnotation = annotations.get(JAVAX_PRIORITY_ANNOTATION);
if (priorityAnnotation.isPresent()) {
return priorityAnnotation.getInt(MergedAnnotation.VALUE);
}
//Order和Priority注解都没有,则返回null值
return null;
}

最后针对 @Priority 注解单独提供了 getPriority(Class<?> type) 方法,获取优先级的数值,如果没有添加注解或者 value 未设值,则返回 null。

1
2
3
4
5
@Nullable
public static Integer getPriority(Class<?> type) {
return MergedAnnotations.from(type, SearchStrategy.TYPE_HIERARCHY).get(JAVAX_PRIORITY_ANNOTATION)
.getValue(MergedAnnotation.VALUE, Integer.class).orElse(null);
}

Ordered接口

优先级排序接口,和 @Order 注解一样,获取到的 order 值越小,优先级越高。

1
2
3
4
5
//最高优先级
int HIGHEST_PRECEDENCE = Integer.MIN_VALUE;

//最低的优先级
int LOWEST_PRECEDENCE = Integer.MAX_VALUE;

提供获取排序值的方法,如果两个对象的 order 值相同,则会进行随机排序。

1
int getOrder();

PriorityOrdered接口

Ordered接口的扩展接口,表示优先级排序。

PriorityOrdered 接口的优先级要高于 Ordered接口的优先级,即使后者对应的 order 值比较小,也是 PriorityOrdered 的优先级高。

在对一组对象进行排序时, PriorityOrdered对象和 Ordered对象可以被看作两个单独的子集,其中一组 PriorityOrdered对象位于一组 Ordered 对象之前,然后在这两个子集中再根据 order 进行相对排序。

PriorityOrdered 是一个专用接口,用于标记特别重要的的对象,甚至不需要获取剩余的对象,比如 Spring 中优先级比较高的后置处理器 ApplicationContextPriorityOrdered 相关的后置处理器 Bean 在特殊阶段进行初始化,早于其他的后置处理器,这巧妙地影响了它们的自动装配行为:只会针对不需要急切初始化类型匹配的 bean 进行自动装配。

PriorityOrdered 的应用可参见 org.springframework.beans.factory.config.PropertyOverrideConfigurer 类。

FunctionalInterface 是一个信息性注解类型,用于指示接口类型符合 Java 语言规范定义的函数式接口要求。

从概念上讲,函数式接口只有一个抽象方法,其他方法都有默认的实现。

如果接口声明了一个覆盖 java.lang.Object 的公共方法之一的抽象方法,这也不会进入抽象方法计数,因为接口的任何实现都具有来自 java.lang.Object 或其他地方的实现。

请注意,函数式接口的实例可以使用 lambda 表达式、方法引用或构造函数引用来创建。

如果使用此注解类型对类型进行注解,则编译器需要生成错误消息,除非:

  • 该类型是接口类型,而不是注释类型、枚举或类。
  • 带注释的类型满足函数式接口的要求。

但是,无论接口声明中是否存在 FunctionalInterface 注解,编译器都会将满足函数式接口定义的任何接口视为函数式接口。

注解定义

1
2
3
4
5
@Documented
@Retention(RetentionPolicy.RUNTIME)
//Target表明FunctionalInterface只能用来修饰接口、类和枚举
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}

函数式接口只有一个抽象方法

1
2
3
4
5
6
7
@FunctionalInterface
public interface HelloInterface {

void test1(String str);

void test2();
}

在 HelloInterface 中定义两个抽象方法,编译器会给出提示:在HelloInterface中找到多个非重写的abtract方法。

接口声明覆盖 java.lang.Object的抽象方法,不会进行抽象方法计数

1
2
3
4
5
6
7
@FunctionalInterface
public interface HelloInterface {

void test1(String str);

String toString();
}

HelloInterface 定义了 toString() 方法,编译器不会给出错误提示,编译可以通过。

函数式接口的实例可以使用 lambda 表达式、方法引用或构造函数引用来创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Test {

public static void main(String[] args) {
//lambda表达式方法 定义抽象方法test1的实现,生成接口实例
HelloInterface hello = x -> System.out.println("hello " + x);
//进行接口调用
hello.test1("qinghuazs");

//构造器方式来实现抽象方法,生成接口实例
HelloInterface hello2 = new HelloInterface() {
@Override
public void test1(String str) {
System.out.println("hello construction " + str);
}
};
hello2.test1("qinghuazs");
}
}

方法引用还没搞明白,等我搞明白了再来补充。

FunctionalInterface 用来修饰类或枚举会编译失败

因为 FunctionalInterface 要求必须有一个抽象方法,所以普通的类肯定是不满足条件的,我们来看一下抽象类。

1
2
3
4
5
6
7
8
9
@FunctionalInterface
public abstract class AbstractTest {
void test();
}

@FunctionalInterface
public enum EnumTest {

}

编译器提示 AbstractTest 不是函数式接口,编译失败。

在使用 idea 开发时,创建了一个类,但是这个类并没有纳入到版本控制中,如果不小心把这个类删除了,该怎么办?重新花时间去写一个一样的类?但是项目马上就要交付了,哪有那么多时间重新写一个啊,而且还要花时间把原来的功能重新测试一遍!

别慌,idea 的 Local History 功能帮你免除这种烦恼!

现在我们有一个类 Regression , 这个类里面实现了一些逻辑

图1

Regression 类不小心被删掉了

图2

这时候,可以选中 regression 包,点击右键,从菜单中找到 Local History,再点击 Show History 子菜单

图3

此时,会出现一个弹窗,弹窗内容分为两部分,左侧是 regression 包的操作记录,右侧是每条记录操作的具体内容

图4

根据记录,可以看到5分钟之前删除了 Regression 类,此时我们选中右侧的 Regression 类,右键,点击 Revert Selection 菜单即可恢复 Regression

图5

网卡配置文件

进入到 /etc/sysconfig/network-scripts 目录,找到网卡配置文件 ifcfg-*

1
> cd /etc/sysconfig/network-scripts

配置

ifcfg-ens192 文件中修改或添加以下信息

1
2
3
4
5
6
BOOTPROTO="static" #dhcp改为static 
ONBOOT="yes" #开机启用本配置
IPADDR=10.192.89.130 #静态IP
GATEWAY=10.192.89.254 #默认网关
NETMASK=255.255.255.0 #子网掩码
DNS1=10.1.7.7 #DNS 配置

重启网卡服务

1
> service network restart

查看配置结果

虚拟机下安装CentOS7时报错:Operating System Not Found,具体信息如下截图

解决方案

关闭虚拟机电源,在选择镜像的地方勾选 Connect At Power On 选项,并选择 CentOS7 的镜像,重启电源即可。

Spring Boot 自动配置目的是根据添加的 jar 依赖项自动配置 Spring 应用程序。 例如,如果 HSQLDB 位于classpath上,而你没有手动配置任何数据库连接 bean,那么 Spring Boot 将自动配置一个内存数据库。

您需要通过向 @Configuration 类中添加@EnableAutoConfiguration@SpringBootApplication 注解来选择自动配置。

你应该只添加一个·@SpringBootApplication@EnableAutoConfiguration 注解,通常建议 你将其中一个添加到你的主 @Configuration 类中。

逐渐取代自动配置

自动配置是非侵入性的。 在任何时候,您都可以定义自己的配置,以替换自动配置的特定部分。 例如,如果您添加自己的 DataSource bean,则默认的嵌入式数据库支持将失效。

如果您需要了解当前应用的自动配置了哪些内容,以及为什么会自动配置,那么可以使用 --debug 开关启动应用程序。 这样做可以为选择的核心日志记录器启用调试日志,并将日志输出到控制台。

禁用特定的自动配置类

如果您发现您不想要的特定自动配置类正在被应用,您可以使用 @SpringBootApplicationexclude 属性来禁用它们,如下面的示例所示:

1
2
3
4
5
6
import org.springframework.boot.autoconfigure.*;
import org.springframework.boot.autoconfigure.jdbc.*;

@SpringBootApplication(exclude={DataSourceAutoConfiguration.class})
public class MyApplication {
}

如果类不在 classpath 下,则可以使用 excludeName 属性并指定完全限定名。 如果您更喜欢使用@EnableAutoConfiguration而不是@SpringBootApplication ,也可以使用 excludeexcludeName,此外,还可以使用 spring.autoconfigure.exclude 属性控制要排除的自动配置类列表。

可以在注释级别和通过使用属性定义要排除的自动配置类

Spring Boot 支持基于Java的配置。虽然可以将 SpringApplication 与 XML 配置文件一起使用,但是我们通常建议你使用 @Configuration 类来定义配置信息;通常定义main 方法的类是一个很好的作为主要@Configuration 的候选类。

网络上已经有很多使用XML配置的Spring案例,如果可能的话,将这些案例尝试使用基于Java配置的形式来实现。在网络上搜索Enable*注解是一个很好的起点。

导入额外的配置类

您不必将所有 @Configuration 都放在一个类中,可以使用 @Import 注解导入其他配置类,或者,您可以使用@ComponentScan 自动扫描所有 Spring 组件,包括 @Configuration类。

@Import注解

@Import 注解相当于 XML 配置中的 <import> 标签,可以导入其他配置文件中的配置,避免所有配置都写在一个配置文件或配置类中。该注解也可以将普通的类声明为一个bean。

定义类
1
2
3
public class DemoService {

}
将类声明为Bean
1
2
3
4
5
@Configuration
@Import(DemoService.class)
public class DemoConfiguration {

}

这样Spring容器启动时就会将DemoService类加载到容器中

输入图片说明

@Import(DemoService.class)这种写法的效果和 @Bean 的效果相同

导入XML配置

如果您必须使用基于 XML 的配置,我们建议您仍然从 @Configuration 类开始。 然后可以使用@ImportResource 注解加载 XML 配置文件,将XML中配置的类和属性加载到容器中。

1
2
3
4
5
@Configuration
@ImportResource(locations={"classpath:applicationContext.xml"})
public class XmlConfiguration {

}

Spring Boot 不需要任何特定的代码布局就可以工作。 然而,有一些最佳实践可以帮助你。

使用默认package

当一个类不包含 package 声明时,它被认为是在“默认包”中。 通常不鼓励使用“默认包” ,并且应该避免使用。 对于使用 @ComponentScan@configurationPropertiesScan@EntityScan@SpringBootApplication 注解的 Spring Boot 应用程序,它可能会导致特殊的问题,因为每个 jar 的每个类都会被读取。

建议您遵循 Java 推荐的包命名约定,并使用反向域名,例如 com.example.project

定位主程序类

我们通常建议您将主程序类定位在根包中,位于其他类之上。 @SpringBootApplication 注解通常放在您的主类上,它隐式地为某些项定义一个基本的“搜索包”。 例如,如果您正在编写一个 JPA 应用程序,可以使用 @SpringBootApplication 注解类的包来搜索@Entity 项。 使用根包还允许组件扫描只应用于项目。

如果你不想使用 @SpringBootApplication@EnableAutoConfiguration@ComponentScan 注解提供了相同和功能,可以使用这两个注解来代替 @SpringBootApplication

下面显示了一个典型的布局:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
com
+- example
+- myapplication
+- Application.java
|
+- customer
| +- Customer.java
| +- CustomerController.java
| +- CustomerService.java
| +- CustomerRepository.java
|
+- order
+- Order.java
+- OrderController.java
+- OrderService.java
+- OrderRepository.java

Application.java 文件声明了一个 main 方法和 @SpringBootApplication ,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.example.myapplication;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {

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

}

总结

全篇的意思就是:在SpringBoot应用中不要使用无package声明的类,不然可能会导致SpringBoot应用启动时无法扫描到这些类;SpringBoot的主类要放在最外层的包目录下,其他模块在该包下再创建子包。

Starters是一组可以包含在应用程序中的方便的依赖描述符。 您可以获得所有 Spring 和相关技术的一站式服务,而无需遍历示例代码和复制粘贴大量依赖描述符。 例如,如果您希望开始使用 Spring 和 JPA 进行数据库访问,那么可以在项目中包含 spring-boot-starter-data-jpa 依赖项。

starters 中包含了许多依赖项,您需要这些依赖项来快速启动和运行项目,并且具有一致的、受支持的托管传递依赖项集合。

官方的 Starter 遵循 spring-boot-starter-* 的命名模式,如 spring-boot-starter-data-jpa 。第三方的 Starter 通常以自己的项目名称开始,例如,名为 thirdpartyproject 的第三方入门项目通常被命名为 thirdpartyproject-spring-boot-starter

Spring Boot 应用程序 starter

名称 描述
spring-boot-starter 核心starter,包括自动配置支持,日志和 YAML
spring-boot-starter-activemq 使用 Apache ActiveMQ 实现 JMS 消息传递的starter
spring-boot-starter-amqp 使用 Spring AMQP 和 Rabbit MQ 的starter
spring-boot-starter-aop 带有 Spring AOP 和 AspectJ 的面向切面编程的starter
spring-boot-starter-artemis 使用 Apache Artemis 的 JMS 消息传递starter
spring-boot-starter-batch 基于 Spring Batch 实现的 starter
spring-boot-starter-web 用于构建 web 的入门程序,包括 RESTful,使用 Spring MVC 的
应用程序。 使用 Tomcat 作为默认的嵌入式容器
spring-boot-starter-validation 使用 Hibernate Validator 进行 javabean 验证的启动器
spring-boot-starter-webflux 用于使用 Spring 框架的 Reactive Web 支持构建 WebFlux 应用程序的starter

Starter 比较多,就不一一列举了,需要的话,可以自行去spring-boot-starter查询,另外还有很多第三方的starter,Spring 官网查不到的,需要去对应的开源网站或者官方网站查询。