注解分为三类,容器相关,bean相关,null相关。
1、容器
采用注解方式配置IOC容器,有三种方式
纯注解形式,提供至少一个配置类(@Configuration标注的类)
纯注解形式,提供至少一个Bean(@Component等标注的类)
混合方式,提供配置类,使用@ImportResource引入Spring的配置文件。
1.1 创建
容器创建,实质是创建applicationContext对象,使用注解时,创建的对象为AnnotationConfigApplicationContext对象,它的创建方式有以下几种。
单个配置类:创建AnnotationConfig对象,参数为XXApp.class
public ApplicationContext singleConfig() { ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class); return ac; }
多个配置类:有三种方式,第一种使用@Import注解,第二种方式多个XXApp.class参数,第三种方式调用register方法。
// 方式一 @Configuration @Import({AppConfig1.class,AppConfig2.class}) public class AppConfig {} // 方式二 ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class,AppConfig2.class); // 方式三,此时必须是AnnotationConfigApplicationContext对象, // ApplicationContext接口中无这些方法 ac.register(AppConfig1.class); ac.register(AppConfig.class);
纯注解形式:多个Component与Configuration注解相同,只不过IOC容器中只存在初始化时传入的几个Bean。
混合方式:使用@ImportResource注解
@Configuration @ImportResource({"classpath:spring/application.xml"}) public class AppConfig {}
1.2 扫描
1.2.1 路径
指定扫描路径的方式有两种
使用@ComponentScan注解,通过设置basePackages指定bean所在包的名称,多个包名时使用逗号分隔,包名可以是正则表达,SpringEL表达式。
调用context.scan方法,参数为包名。使用频率很低。
1.2.2 过滤条件
1.2.2.1 概念
添加过滤条件有两个含义,第一类是给配置类上添加过滤条件,第二类是给具体的某个bean上添加过滤条件。
第一类是给@Component-scan设置include-filter和exclude-filter属性,它们的值可以是以下五类:
- Annotation:根据类上的注解进行过滤,默认值
- Assignable:根据类的class或类名进行过滤
- Aspectj:根据AspectJ的类型表达式对类名进行过滤
- Regex:根据正则表达式对类型进行过滤
- Custom:根据TypeFilter的实现类进行过滤。
第二类是给具体的bean上添加过滤条件,当满足条件时注入bean,不满足时跳过。
它是给具体的bean上添加@Conditional注解,并设置value属性,每一个value属性的值都是Condition接口的实现类。Condition接口只有一个方法matches,当满足条件时,该方法返回true,不满足条件时,返回false。
要添加自定义过滤条件,至少需要两个步骤
- 第一步,编写XXConditionImpl,实现Condition接口,
- 第二步,在类上添加@Conditional注解,并设置value的值为XXConditionImpl.class
springBoot中存在大量该注解,@Conditional和Condition都有自己的类结构。例如@ConditionalOnClass表示某个类是否在IOC容器中,当存在时满足条件,不存在时不满足条件。
想要深入了解,可以查看DataSourceAutoConfig的源码。
1.2.2.2 componentScan
描述 |
它用于指定容器注入bean时的一些配置。 |
属性 |
basePackage: 说明:指定扫描包的根路径,存在多个时,使用逗号分隔 类型:字符串,包名或者包名的正则表达式 是否必填:否,不指定时默认为classpath |
属性 |
Include-filter: 说明:添加包含的过滤条件,过滤条件有五种类型,
类型:不同类型对应不同的数值类型 是否必填:否,不指定时无过滤器 |
属性 |
enclude-filter: 说明:添加排除的过滤条件,过滤条件有五种类型,
类型:不同类型对应不同的数值类型 是否必填:否,不指定时无过滤器 |
属性 |
lazyInit: 说明:指定bean加载时是否延迟加载 类型:布尔值 是否必填:否,默认值为false。 |
属性 |
nameGenerator: 说明:指定bean注入时,名称的生成 类型:NameGenerator的实现类 是否必填:否。 |
位置 |
配置类上 |
1.2.3 添加索引
添加索引是指在注入bean时,给bean添加索引,在获取bean时会变快,提高性能。
添加索引的步骤有两步:
- 添加spring-context-indexer依赖。
- 在IOC容器中注入CandidateComponentsIndexer类。
如果想禁用此功能,又不想移除依赖,可以设置spring.index.ignore属性值为true。
1.2.4 profile
Profile本质也是一种过滤条件,过滤条件是基于”环境”的,例如生产环境,测试环境。
使用@Profile注解的步骤有以下两步:
第一步:在bean上添加@profile注解,当激活的环境与设置的环境相等时,注入bean。方式有以下三种
- 在类上添加,在方法上添加,类上标注有@Component等,方法上有@Bean。
- 创建@Profile的元注解,在类上,方法上添加元注解。例如创建@Production注解
- 在配置文件中,给beans标签设置profile属性,相当于全局;或给单个bean标签设置profile属性。
第二步:激活profile。方式有四种
- 设置spring.profiles.active属性。Java argument形式,配置文件形式等等
- 调用context.getEnvironment().setActiveProfiles方法,参数为激活的环境。
- 指定默认的profile。
- 如果存在spring-test依赖,在配置类上添加@ActiveProfiles
设置默认profile的方式也有三种。
- 在配置类上添加@Profile注解
- 调用context.getEnvironment().setDefaultProfiles方法
- 设置spring.profiles.default属性。
Profile的值可以是单个环境,多个环境,或者是环境的逻辑表达式。
单个环境,@Profile(“envName”);
多个环境,@Profile({“envName1”,”envName2”}),它们是逻辑或关系,只要其中一个环境激活即加载。
逻辑表达式,与(&),或(|),非(!)。例如@Profile(“!envName”)。
2、bean
bean的注解分为三类,注入bean。获取bean。配置bean,例如属性,作用域,生命周期等。
2.1 注入
当存在配置文件,且没有配置类时(@Application标注的类),注解生效需要配置context:annotation-config,也可以配置context:component-scan,它基于annotation-config的基础上添加base-package,include,exclude等属性,用于添加扫描包的条件。
当存在配置类时,需要在其上添加ComponentScan注解,配置base-package,include,exclude属性。含义与context:component-scan标签相同。
当注解存在name属性时,容器会将name属性值作为bean的名称,当不存在name属性时,会将类名的首字母小写,作为bean的名称,需要特别注意的是类名不是类全名,当不同包下存在相同类名时,会存在同名的bean。
Spring支持自定义bean名称的生成策略,两个步骤,第一步编写MyNameGenerator实现NameGenerator接口,第二步设置该类为Component-scan注解的name-generator属性值。
2.1.1 configuration
描述 |
它用于创建AnnoationConfigApplicationContext,它属于配置类。地位等价于applicationContext.xml |
位置 |
类上 |
2.1.2 component
描述 |
它代表一个bean,当IOC容器扫描包时,会把标有该注解的类添加到容器中 |
位置 |
类上 |
2.1.3 repository
描述 |
它是Component注解的元注解,功能与Component相等,含义缩小,只表示MVC层中的Model层,即DAO数据层 |
位置 |
类上 |
2.1.4 service
描述 |
它是Component注解的元注解,功能与Component相等,含义缩小,只表示服务层 |
位置 |
类上 |
2.1.5 controller
描述 |
它是Component注解的元注解,功能与Component相等,含义缩小,只表示控制器层 |
位置 |
类上 |
2.1.6 restController
描述 |
它是Controller与ResponseBody的组合注解,表示它是一个Controller,并且响应类型为数据流。 |
位置 |
类上 |
2.1.7 bean
描述 |
它代表一个bean,标注在方法上,将该方法的返回值注入到IOC容器中。 |
属性 |
name: 说明:指定bean的名称,默认值为方法的名称,当指定多个name值时,本质是在设置bean的别名。 类型:字符串, 是否必填:否 |
属性 |
Init-method: 说明:对应bean标签的init-method属性。 类型:字符串,方法名称 是否必填:否,默认值为init, |
属性 |
destory-method: 说明:对应bean标签的destory-method属性。 类型:字符串,方法名称 是否必填:否,默认值为close,shutdown |
位置 |
方法上(居多),注解上 |
定义在@Configuration下的@Bean会通过CGLIB方式调用方法,并获取相关的原信息,所以方法不能使用private,final修饰。定义在@Component下的@Bean不会。
2.2 获取
2.2.1 required
描述 |
在设置bean属性时,Required注解表示该属性为必备属性,如果在创建Bean时,没有对应的属性值,会抛出异常。构造器依赖也会起到同样的效果。 |
位置 |
属性上,或者属性的set方法上 |
示例如下:
public class User { // 用户的名称,必填属性 @Required private String name; }
2.2.2 autoWired
描述 |
它对应bean标签的autowire属性,在注入bean的依赖时,从IOC容器中获取,获取方式有四种NONE,byName,byType,constructor。默认的方式为byName,如果查找不到,根据byType方式。 当属性为容器类型时,数组或集合时,会把找到的所有依赖都放入容器中 |
属性 |
required: 说明:当注入beanA的依赖beanB时,用于指定该依赖是否是必须的,当为true时,为必须的,此时容器中没有依赖的beanB,则beanA创建失败,并报错。 类型:布尔值,true或者false 是否必填:否,默认值为true |
位置 |
依赖的任何地方,属性上,构造器参数,方法参数 |
示例如下:
public class User { // 自动注入Address类,当IOC容器中没有Address时,User创建失败并报错 @Autowired(required=true) private Address homeAddress; }
2.2.3 order
描述 |
当beanA依赖beanB的容器时,IOC容器在查找beanB时,如果beanB有多个,Order注解用来决定在容器中的顺序 |
位置 |
类上 |
示例如下:
public class User { // IOC中存在多个Address时,IOC会根据Order注解决定Address在List<Address> // 的顺序 @Autowired(required=true) private List<Address> homeAddress; } // Address类 @Order(value=1) public class Address { }
它一般发生在根据byName无法找到依赖时,根据byType查找时,存在多个相同的类名,例如上例中不存在id为homeAddress的bean,存在多个Address类,com.test.Address,com.bean.Address。
2.2.4 primary
描述 |
当beanA依赖beanB的容器时,IOC容器在查找beanB时,如果beanB有多个,Primary用来指定优先使用该bean。 对应bean标签的primary属性 |
位置 |
注入bean时的任何地方,方法,类上 |
示例如下:
public class User { // IOC中存在多个Address时,IOC会根据Order注解决定Address在List<Address> // 的顺序 @Autowired(required=true) private Address homeAddress; } // Address类 @Component @Primary public class Address { }
与Order不同的是,beanA依赖beanB,它们之间的关系是1:1,而IOC容器中存在多个beanB,此时由于无法决定使用哪个beanB,导致beanA创建失败,primary用于决定优先选中哪个beanB。如果beanB只存在一个时,该注解无意义。
2.2.5 qualifier
描述 |
当beanA依赖beanB,并且在容器中存在多个beanB时,Qualifiler注解用于为beanB分类。 |
位置 |
注入beanB时用来为beanB归类,获取时qualifier作为过滤条件,查找合适的beanB。 |
分类的方式有三种,
- 第一种,设置qualifier的value属性
- 注入Address类
<!-- 家庭住址 --> <bean class="com.bean.Address"> <qualifier value="homeAddress" /> </bean> <!-- 公司住址 --> <bean class="com.bean.Address"> <qualifier value="companyAddress" /> </bean>
2.获取Address
public class User { // 用户的家庭地址 @Autowired @Qualifier("homeAddress") private Address homeAddress; // 用户的工作地址 @Autowired @Qualifier("companyAddress") private Address companyAddress; }
此时Qualifier注解的作用与bean的ID差不多,还不如指定id属性,并且使用byName的方式获取。
2.第二种,继承Qualifier
1.编写@HomeAddress,@CompanyAddress注解
/** * * @Title: HomeAddress.java * @Package com.annotation * @Description: HomeAddress注解 * @version V1.0 */ @Target({ElementType.FIELD,ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Qualifier public @interface HomeAddress { // 居住地址 String value(); }
2. 注入Address类,配置qualifier子标签,可以看到子类相当于二级分类。
<bean class="com.bean.Address"> <qualifier type="com.annotation.HomeAddress" value="homeTown" /> </bean> <bean class="com.bean.Address"> <qualifier type="com.annotation.CompanyAddress" value="working" /> </bean>
3.获取Address类
public class User { // 用户的家庭地址 @Autowired @HomeAddress("homeTown") private Address homeAddress; // 用户的工作地址 @Autowired @CompanyAddress("working") private Address companyAddress; }
此示例中,子类相当于二级目录,其中的value属性相当于过滤条件,如果value不是homeTown和working,则找不到Address类。homeAddress和companyAddress为null值
3.第三种,继承Qualifier,添加更多的属性值作为过滤条件,例如在上述示例中只有value作为过滤条件,可以在注解中添加其他属性。
它与第二种方式基本相同,在第二步时,注入Address类时,多个属性的格式为:
<bean class="com.bean.Address"> <qualifier type="com.annotation.HomeAddress"> <attribute key="field1" value="value1"/> <attribute key="field2" value="value2"/> </qualifier> </bean>
当beanA依赖beanB,并且存在多个beanB时,该注解为beanB分类,支持一级分类和二级分类。
当容器中只存在一个beanB时,该注解无任何意义
当只需要一级分类时,完全可以由bean的ID,name,alias等属性代替。
当需要二级分类时,可以使用该注解。
在实际的项目中,基本不会存在二级分类的情形,所以该注解的使用频率很低。
2.2.6 resource(Java_based)
描述 |
当beanA依赖beanB时,Resource注解用于在IOC容器中查找beanB。方式通常是byName,当不指定name属性时,name的属性值为类名首字母小写。 |
属性 |
name: 说明:根据name在IOC中查找beanB,它对应beanB的ID,name,alias中的任意一个。 类型:字符串 是否必填:否,默认值为类名首字母小写 |
位置 |
属性上,属性的set方法上 |
示例如下:
第一步,注入Address
<bean id="address" class="com.bean.Address"/>
第二步,获取
public class User { // 用户的工作地址,可以不指定,默认值也是address @Resource(name="address") private Address companyAddress; }
2.2.7 inject(Java_based)
等价于@Autowired
2.2.8 named
当不指定value属性值时,等价于@Component,当指定value值时,等价于@Qualifier(“value”)。
2.3 配置
2.3.1 description
描述 |
Bean的一段描述文字 |
位置 |
方法上,与@Bean一起使用 |
2.3.2 value
描述 |
用于获取Properties的值,并且把该值赋给对象的属性 |
属性 |
key: 说明:IOC容器中变量的key值,它可以从JVM变量,操作系统变量,配置文件等等获取。 类型:springEL表达式 是否必填:是 |
位置 |
属性上 |
2.3.3 lazyInit
描述 |
用于定义bean是否延迟加载 |
属性 |
value: 说明:true时延迟加载,false时随容器的创建加载。 类型:布尔值 是否必填:否,默认值为false |
位置 |
方法上,与@Bean一起使用 |
2.3.4 scope
描述 |
用于指定bean的作用域, |
属性 |
value: 说明:bean作用域的值,五个值中的任意一个。 类型:枚举值,singleton,prototype,request,session,application 是否必填:否 |
位置 |
方法上,与@Bean一起使用 |
2.3.5 requestScope
等价于@Scope(“request”)
2.3.6 sessionScope
等价于@Scope(“session”)
2.3.7 servletContextScope
等价于@Scope(“application”)
2.3.8 postConstruct(Java_based)
参考bean的生命周期,它添加在方法上,触发时机在构造器之后,init方法之前。
2.3.9 preDestory(Java_based)
参考bean的生命周期,它添加在方法上,触发时机在bean的销毁阶段,destory方法之前。
3、null相关
3.1 注解
原著中介绍了4个注解。
@NonNull:用于添加对象属性,方法参数,方法返回值不为NULL的约束条件。
// 对象属性上 @NonNull private String userName; // 方法参数上 public static void testLiteralExp(@NonNull String str) {} // 方法返回值上 public @NonNull String test(){}
@Nullable:与@NonNull作用刚好相反,属性,方法参数,返回值可以为NULL
@NonNullApi:当前包下面的所有方法参数和返回值都不可以为NULL,可以被@Nullable覆盖。
@org.springframework.lang.NonNullApi package learn.test;
@NonNullField:当前包下面的对象的所有字段都不可以为NULL,可以被@Nullable覆盖。
3.2 IDE
当字段,方法参数,方法返回值添加上这些注解时,IDE会有相应的提示。本文以Eclipse为例演示具体的步骤
第一步:打开Window---->preferences----->Java------>Complier---->Error/Warnings
找到Null analysis,展开,点击enable annotation-based null analysis。
第二步,在下面的use default annotation for null specifications把对勾去掉,点击configure,配置如下图,
第三步,点击Apply,重新编译即可看到效果。
3.3 JSR305
JSR305中也有相同功能的注解,而且还有很多其他的注解,使用这些注解首先必须引入jsr305的jar包
<!-- https://mvnrepository.com/artifact/com.google.code.findbugs/jsr305 --> <dependency> <groupId>com.google.code.findbugs</groupId> <artifactId>jsr305</artifactId> <version>3.0.2</version> </dependency>
编写代码时,再次使用NonNull注解时,会发现有javax.annotation.NonNull。
import javax.annotation.Nonnull;
要使编译器提示,需要重新配置第二步,将注解修改为javax.annotation.NonNull等注解