目录
2.1 配置实现注解功能的BeanPostProcessor的Bean
使用XML文件配置是Spring最早的配置方式,大中型项目依据功能或不同的命名空间拆分成多个配置文件,每个配置文件的配置内容都可能很多,导致配置文件的维护工作量大,也容易出错。从Spring2.5开始,在以XML文件作为主要配置的同时,可以将某些配置以注解的形式在代码中直接配置,极大地减少了配置地繁琐度,提高了配置地效率,Java开发人员也更容易熟悉和适应。从Spring3.0开始可以完全脱离XML文件,使用Java代码地方式进行容器和框架地配置。
一、Java注解
注解(Annotation)是Java SE 5.0开始引入地概念,与类(Class)和接口(Interface)一样,也属于一种类型。
1.Java基本注解
注解是一种可以对应于类、方法、参数、变量、构造器及包地特殊修饰符,是源代码地元数据。区别于注释对源码地描述给人以提示,注解是Java编译器或代码用来读取和理解的。Java从JDK5开始引入注解,位于java.lang.annotation中,最早引入的基本注解如下:
- @Override:注解在方法上,说明当前方法是覆盖超类的方法;
- @Deprecated:弃用或者不建议使用的代码;
- @SuppressWarnings:忽略编译器的警告。
以上注解主要用于编译检查,也可以自定义注解。在Java中,注解与类、接口和枚举是在同一个层次,所以可以像定义接口一样来定义注解。Java提供了4种用于创建注解的注解,称之为元注解,分别如下:
- @Documented:注解是否包含在JavaDoc中;
- @Retention:什么时候使用该注解,取值如下:
- RetentionPolicy.SOURCE:注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃;
- RetentionPolicy.CLASS:注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期;
- RetentionPolicy.RUNTIME:注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在,那么在运行时就可以通过反射访问;
- @Target:注解可以标注在什么地方,常用取值如下:
- ElementType.TYPE:可以标注"类、接口(包括注释类型)或枚举声明";
- ElementType.FIELD:它可以标注"字段声明";
- ElementType.METHOD:它可以标注"方法";
- ElementType.PARAMETER:它可以标注"参数";
- ElementType.CONSTRUCTOR:它可以标注"构造方法";
- ElementType.LOCAL_VARIABLE:它可以标注"局部变量"。
- @Inherited:是否允许子类继承该注解。
注解通过@interface关键字进行定义。注解可以像类属性和方法一样,通过反射获取,结合反射就可以对标注注解的类和方法进行功能扩展。使用示例:
@Retention(RUNTIME)
@Target({ TYPE, METHOD, FIELD })
public @interface MyAnnotation {
//这里的value是参数名称,String[]是参数类型, unknown是参数的默认值。
String[] value() default "unknown";
}
当注解只有一个参数,且参数名称为value,那么在使用注解时可以省略参数名称。
@MyAnnotation(value = { "类上的注解value" })
public class AnnotationDemo {
@MyAnnotation //这里不指定参数,则使用默认参数值
private String field;
//参数类型为String[],当有多个值的时候,需要使用{},当只有一个值的时候,可以省略{}
@MyAnnotation(value = "This is a method")
private void method() {}
public static void showMyAnnotationValue(AnnotatedElement element) {
if (element == null) {
return;
}
if (element.isAnnotationPresent(MyAnnotation.class)) {
MyAnnotation myAnnotation = element.getAnnotation(MyAnnotation.class);
String[] valueArr = myAnnotation.value();
System.out.println(Arrays.toString(valueArr));
}
}
public static void main(String[] args) {
Class<?> klass = AnnotationDemo.class;
showMyAnnotationValue(klass);
Field[] fields = klass.getDeclaredFields();
for (Field field : fields) {
showMyAnnotationValue(field);
}
Method[] methods = klass.getDeclaredMethods();
for (Method method : methods) {
showMyAnnotationValue(method);
}
}
}
上面示例中获取注解的信息是在showMyAnnotationValue方法中,该方法用了一个AnnotatedElement接口类型,该接口在java.lang.reflect下,用于注解信息的获取。
2.Java注解标准
JCP官方除了在JDK中提供并实现了@Override等基本注解和功能之外,还规范了一系列的注解标准,可以由其他容器或框架去实现。
2.1 Java平台的公共注解标准(JSR-250)
JSR-250定义的标准注解主要如下:
- @Resource:声明对资源的引用(类似于数据库资源);
- @PostConstruct:使用在Servlet上,在init()方法之前执行;
- @PreDestroy:使用在Servlet上,在destroy()方法之后执行。
以上注解定义的注解类文件位于JRE的rt.jar中,对应目录是javax.annotation。JSR-250定义的注解规范基本是关于“资源”的构建、销毁和使用。
2.2 依赖注入的注解标准(JSR-330)
JSR-330是JCP官方定义的依赖注入的注解标准。相关注解定义文件位于javax.inject.jar中,这个jar包没有包含在JDK中,需要额外下载或使用Maven导入。其主要定义了5个注解和1个接口,如下:
- @Inject:标识需要由注入器注入的类成员,用于类的构造器、方法和属性上,可以于Spring依赖配置结合使用;
- @Qualifier和@Named:限制器,用于限制注入依赖的类型;
- @Scope和@Singleton:定义作用域;
- Provider接口用于提供类型T的实例。
2.3 软件缺陷检测注解标准(JSR-305)
JSR-305是软件缺陷检测注解标准,该注解不是来自Java官方标准,而是来自Java代码的静态分析工具的使用需求。静态分析工具开发人员可以通过注解定义代码的健壮性,例如那些值不能为空,哪些值不能为负。该标准包括@Nonnull和@Nullable等注解。
- @Nonnull:注解的元素不能为null。用在属性上,表示在对象构造完成之后,属性的值不能为空;用在方法上,表示方法的返回值不能为空。
- @Nullable:注解的元素可以为空。
二、Spring支持的注解类型与开启方式
1.Spring支持的注解类型
下面的注解类型基本是限定在容器配置方面的注解。
- 组件注解有Spring提供的@Component、@Controller和@Service等,也有Java标准注解@Resource、@Name等。
- 依赖注解有Spring提供的@Autowired、@Lookup和@Value,也有Java标准注解@Inject等。
从实现方式上来看,有的注解是在容器初始化的时候完成的,比如@Component等组件注解;有的注解是通过BeanPostProcessor的容器扩展方式来实现的。通过BeanPostProcessor扩展实现的注解,根据其对应的注解处理类,包含以下4种:
- Java公共注解:注解的处理类是CommonAnnotationBeanPostProcessor,提供对Java平台公共注解标准JSR-250的实现,包括@PostConstruct、@PreDestroy和@Resource等。
- 自动装配注解:注解的处理类是AutowiredAnnotationBeanPostProcessor,用来进行依赖注入的注解,有对官方注解标准JSR-330的支持(比如@Inject、@Named等,从Spring3.0开始支持),也有Spring自行定义的注解(@Autowired、@Value和Lookup)。
- 非空检查注解:注解的处理类是RequiredAnnotationBeanPostProcessor,对依赖进行检查的注解@Required的功能实现。
- 持久化注解:注解的处理类是PersistenceAnnotationBeanPostProcessor,用来处理对象关系映射及注解JPA资源EntityManagerFactory和EntityManager,支持@PersistenceUnit和@PersistenceContext等JPA注解。
2.Spring注解功能的开启方式
Spring提供或实现的容器配置注解,在默认情况下使用并不会立即生效,需要配置相关的处理类进行处理。Spring提供了三种方式开启此类型注解的功能,分别是配置实现注解功能的BeanPostProcessor的Bean、使用<context:annotation-config>标签和配置<context:component-scan>标签。
2.1 配置实现注解功能的BeanPostProcessor的Bean
对于使用容器扩展点BeanPostProcessor实现的注解来说,注解功能的开启,只需要在配置文件中加上注解处理类的Bean配置。以Java公共注解为例,在配置文件中加入以下配置:
<bean class="org.springframework.beans.factory.annotation.CommomAnnotationBeanPostProcessor"></bean>
AutowiredAnnotationBeanPostProcessor、RequiredAnnotationBeanPostProcessor和PersistenceAnnotationBeanPostProcessor的配置方式也与上述相同,但需注意使用正确包名。
2.2 使用<context:annotation-config>标签
上述方式配置比较麻烦,为了简化配置,Spring提供了一种更便捷的方式开启注解功能,在配置文件中加入一行即可开启4中注解的支持:
<context:annotation-config />
注意,在使用context标签时,需要在<beans>根元素中添加context的命名空间等,如下:
//指定beans根元素属性:
xmlns:context="http://www.springframework.org/schema/context"
//在xsi:schemaLocation属性中添加:
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
这个标签实质上是一次性在容器中注册以上4种PostProcessor的Bean。
2.3 配置<context:component-scan>标签
前面两种方式并不会开启@Component等组件的注解功能。要开启此类型注解,需要进行包扫描配置,示例配置如下:
<context:component-scan base-package="com.mec.spring.annotation" />
以上配置的base-package属性用于设置扫描的路径,多个路径间使用逗号分隔。使用该配置,容器对该包以及子包下标注@Component注解的类进行实例化。除了扫描和处理@Component注解外,context:component-scan默认还会注册AutowiredAnnotationBeanPostProcessor、CommonAnnotationBeanPostProcessor、RequiredAnnotationBeanPostProcessor,也就是开启@Autowired、@Required和@Resource等注解功能,这和配置context:annotation-config的作用时类似的,如果配置了component-scan,就不需要增加annotation-config的配置了。此外,component-scan也可以通过设置属性annotation-config的值为false禁用@Autowired、@Required和@Resource等注解功能,而仅处理@Component等组件注解。
3.Spring支持的Java标准注解
Spring提供了对Java标准注解的支持和实现,包含生命周期回调注解@PostConstruct和@PreDestroy,以及组件注解@Named和依赖注入注解@Inject及@Resource。
3.1 @PostConstruct和PreDestroy
从Java 5开始,Servlet增加了两个生命周期的注解:@PostConstruct和@PreDestroy这两个注解可以用来修饰一个非静态的void方法,注解相关的类位于JRE和rt.jar中。Spring提供了对这两个注解的实现:
- 被@PostConstruct修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器调用一次,@PostConstruct注解的方法会在Bean的构造函数之后,init()方法执行之前执行;
- 被@PreDestroy修饰的方法会在服务器卸载Servlet的时候运行,并且只会被服务器调用一次,@PreDestroy注解的方法在destroy()方法执行之后执行。
3.2 @Named---组件注解
@Inject和@Named是JSR-330定义的依赖注入的标准注解。与@PostConstruct、@PreDestroy和@Resource不同,这两个注解没有在JRE中,需要额外导入,Maven配置如下:
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
@Named使用在属性和参数上,作用是根据名字查找容器中对应的对象;也可以使用在类上,用于对该类进行组件的标注,功能类似于在XML文件中配置Bean。使用方式如下:
@Named("namedBeanAnno") // 定义bean名称
public class NamedBeanAnno {
}
3.3 @Resource---依赖注入的注解(重要)
@Resource是JSR-250的注解,用来标注系统或容器中资源类型的对象引用,包括持久层访问对象资源(DAO)、文件或容器等资源。@Resource注解定在rt.jar中。Spring支持这个注解来引用被Spring容器管理的对象,包括自定义的Bean实例和容器对象。@Resource可以使用在属性或属性的setter方法上,使用示例如下:
public class ResourceAnno {
@Resource // 使用在属性上,注入自定义的Bean
private NamedBeanAnno namedBeanAnno;
@Resource // 使用在属性上,注入容器对象
private ApplicationContext applicationContext;
private User user;
@Resource // 使用在方法上,注入参数定义的对象
public void setUser(User user) {
this.user = user;
}
}
@Resource注解的属性或者setter方法,默认会以属性名或setter方法的参数名去查找容器中的对象,如果没找到,则使用类来进行查找和注入。也可以显式地使用属性name来查找指定名称地Bean实例,如下:
@Resource(name = "namedBeanAnno")
private NamedBeanAnno namedBeanAnno;
3.4 @Inject---依赖注入
@Inject可以使用在构造函数、属性和属性的setter方法上,用来注入依赖对象,使用示例如下:
public class InjectAnno {
private NamedBeanAnno namedBeanAnno;
@Inject // 属性注入依赖对象
private User user;
private Person person;
@Inject // 构造函数注入依赖对象
public InjectAnno(NamedBeanAnno namedBeanAnno) {
this.namedBeanAnno = namedBeanAnno;
}
@Inject // setter方法注入依赖对象
public void setPerson(Person person) {
this.person = person;
}
}
4.Spring容器配置注解
除了对Java标准注解的支持和实现外,Spring自身也提供了诸多的容器注解,包括组件注册的@Component以及其子注解和@Bean注解、依赖自动注入的@Autowired注解、依赖项检查的注解@Required(该注解用在属性的setter方法上,用来判断该属性是否被注入,如果没有则会异常提示,该注解在Spring5中被弃用)
4.1 @Component(组件注解)
虽然Spring支持Java标准注解@Named和@ManagedBean来注解组件,但使用Spring本身的@Component注解组件更加常见一些。在类中使用@Component注解(该注解有一个value属性,用于指定bean名称,默认情况下bean名称为首字母小写的类名),容器可以在启动时进行该类的实例化。@Component是通用的组件注解,在Java Web开发中对应不同的组件层级,Spring在@Component元注解的基础上定义了不同类型的子注解,常用的包括@Controller、@Service、@Repository,分别对应控制层、业务层和数据访问层的组件。相比@Component,这三个注解提供了对应层级的一些附加功能,比如@Repository提供了持久层处理异常的自动转换。
通过配置<context:component-scan>即可开启@Component的注解功能,默认情况下会扫描base-package包下所有的@Component和子注解(@Controller、@Service和@Repository)标注的类进行实例化并注册。如果需要对扫描和注册的类及注解做一些过滤,有两种方式可以做到,分别是使用<context:exclude-filter>子标签,以及使用<context:include-filter>子标签和use-default-filters属性。
1.<context:exclude-filter>组件扫描的排除过滤
<context:exclude-filter>用于排除组件扫描的条件。比如,如果不需要扫描某个包下的@Controller主解的类,配置如下:
<context:component-scan base-package="com.mec.spring.annotation,com.mec.spring.event">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
2.<context:include-filter>子标签结合use-default-filters实现组件扫描的包含过滤
use-default-filters属性是<context:component-scan>标签可以配置的属性,默认值为true。如果只想扫描某种类型的注解,可以先将use-default-filters属性值设置为false(这个必须设置),之后再进行包含的过滤条件<context:include-filter>的配置。比如,若只想扫描@Repository注解的类,可以使用如下方式:
<context:component-scan base-package="com.mec.spring.annotation,com.mec.spring.event" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
</context:component-scan>
<context:include-filter>和<context:exclude-filter>标签有两个主要的属性:type和expression。type用于指定过滤类型,除了注解类型(annotation)之外,还支持assignable、regex、aspectj和custom这4种过滤类型。expression的值对应的是不同类型过滤器的匹配表达式,包含annotation类型在内的5种过滤器类型和表达式的描述如下:
- annotation:对应expression的值设置为注解类的全路径名。
- assignable:expression的值设置为类和接口的全路径名,如下:
<context:exclude-filter type="assignable" expression="com.mec.spring.annotation.InjectAnno"/>
- regex:表达式可以使用正则表达式匹配包的路径或类。例如,对某个包下所有类都不扫描:
<context:exclude-filter type="regex" expression="com.mec.spring.annotation.*"/>
- aspectj:表达式使用AspectJ类型表达式匹配包或类。
- custom:表达式使用自定义的过滤器类,这个类需要继承org.springframework.core.type.filter.TypeFilter接口,如下:
public class MyTypeFilter implements TypeFilter {
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
throws IOException {
// 获取类元数据
ClassMetadata classMetadata = metadataReader.getClassMetadata();
String className = classMetadata.getClassName();
if (className.contains("InjectAnno")) {
return true;
}
return false;
}
}
定义好之后进行如下配置:
<!--只扫描MyTypeFilter的match方法返回true的-->
<context:component-scan base-package="com.mec.spring.annotation,com.mec.spring.event" use-default-filters="false">
<context:include-filter type="custom" expression="com.mec.spring.annotation.MyTypeFilter"/>
</context:component-scan>
<!--排除match方法返回true的-->
<context:exclude-filter type="custom" expression="com.mec.spring.annotation.MyTypeFilter"/>
4.2 @Bean(方法层级的组件注解)
@Component及其子注解是使用在类层级的组件注解,也可以在类方法上使用@Bean注解来注册Bean,例如:
@Component
public class ServiceMedol {
@Bean
public User method() {
User user = new User();
user.setMailbox("123");
return user;
}
}
@Bean注解定义如下:
public @interface Bean {
@AliasFor("name")
String[] value() default {};
@AliasFor("value")
String[] name() default {};
@Deprecated
Autowire autowire() default Autowire.NO;
boolean autowireCandidate() default true; //用于指定是否作为其他对象注入时候的候选bean
String initMethod() default "";
String destroyMethod() default AbstractBeanDefinition.INFER_METHOD;
}
@Bean注解的方法需要有非空的返回类型,返回的对象就是注册Bean的对象,@Bean注解的方法不能是private或final的。该注解只有在其方法对应的类被注册为Bean的状况下才有效(可以通过XML配置或@Component注解配置)。默认情况下,@Bean注解方法注册的Bean的id是方法名。可以使用name属性指定名称,value属性指定别名。name和value可以单独使用,也可以混合使用,共同使用需要保持name和value的值一致,否则会出错。
@Bean(name="userBean", value="userBean")
容器在执行@Bean注解方法实例化Bean时,如果该方法有输入参数,则容器会根据参数名查找Bean并作为依赖项进行注入,若没找到,则容器启动失败。
XML配置的Bean使用init-method和destroy-method属性设置初始化和销毁的回调方法,与此类似,@Bean注解使用initMethod和destroyMethod属性定义回调方法。@Bean注解默认注册的是singleton作用域的Bean实例,结合@Scope注解,可以定义其他的作用域范围。使用@Description注解可以对该Bean做一些详细的描述,注解的描述内容通过beanDefinition.getDescription()方式获取。
@Component("serviceMedol")
public class ServiceMedol {
// 配置User类中的初始化方法和销毁方法
@Bean(name = "userBean", initMethod = "initUser", destroyMethod = "destroyUser")
@Scope("singleton")
@Description("这是一个通过@Bean产生的userBean")
public User method() {
User user = new User();
user.setMailbox("123");
return user;
}
}
@Bean较常使用在Java代码配置类中(使用@Configuration注解的类)。
4.3 @Autowired(依赖对象的自动装配)
在XML中通过<constructor-arg>和<property>标签进行构造器注入和属性注入依赖对象是传统的依赖注入方式,但这种方式较为繁琐,特别是对于依赖项特别多的状况,Bean的配置就显得冗杂,容易出错,整个XML文件的维护也比较麻烦。Spring提供了@Autowired注解,注解在代码中进行依赖项的自动装载,大大简化了配置,提高了开发效率。@Autowired注解可以使用在类构造器、属性和属性setter方法甚至一般方法上,也可以混合使用。
1.在构造器中使用@Autowired
构造器中的参数名和容器中存在的该类的实例名可以不一样,此时,容器会通过类型查找,如果该类的实例存在多个的话会出错。一般而言,建议保持参数的名称于需要注入的依赖的Bean名称一致。另外,从Spring4.3开始,如果该Bean类只有一个构造器,在该构造器包含参数的状况下,不加@Autowired注解,容器也会自动查找对象并进行注入。如果该类有多个构造器,则至少需要在某一个构造器上添加该注解。
2.在方法上使用@Autowired
可以使用在属性的setter方法上,也可以使用在一般方法上。对应的方法会被调用。如果两个方法同时有此配置,则都会被调用,如下:
public class AutowiredAnno {
private User user;
@Autowired
public void setUser(User user) {
System.out.println("setUser方法被调用");
}
@Autowired
public void method(User user) {
this.user = user;
System.out.println("method方法被调用");
}
}
3.在属性上使用@Autowired
可以在任何作用域的属性上使用@Autowired注解,包括private、public和protected。
使用@Autowired注解后,Bean在XML中的配置就不需要处理依赖项的注入了。@Autowired默认根据类来查找和注入容器中管理的对象,对于注解的属性和参数依赖要确保相应的Bean被容器托管(对于XML配置方式,要在配置文件中有定义相应的Bean),否则容器在初始化的时候就会找不到依赖对象而无法正常启动。如果多个Bean对应同一个类的话,则使用该类集合类型装载就可以得到该类的所有Bean的集合,集合类型包括该类的Array[]、List、Set和Map。集合类型也可以应用在构造器和方法的自动装载的注解方式中,以属性注解的方式来看:
@Autowired // 自动装载数组类型的依赖
private User[] userArray;
@Autowired // 自动装载集合类型的依赖
private Set<User> userSet;
@Autowired // 自动装载键值类型的依赖,键和值分别是Bean的id和根据Bean配置产生的实例
private Map<String, User> userMap;
4.自动装配的required配置
@Autowired默认是required的,也就是被注解的依赖对象必须已经在容器中注册。如果没有,则抛出UnsatisfiedDependencyException异常,容器初始化失败。这和@Required注解的效果是一致的,区别是@Required是对XML文件中的配置依赖项进行检查;@Autowired会自动在容器中查找依赖项并注入。使用了@Autowired注解的构造器和setter方法一般不再需要@Required注解。如果需要取消在容器初始化时对依赖项额检查,则可以设置required属性值为false。如果这样设置,那么容器启动时尽管被依赖的对象没有在容器中注册,也不会抛出异常,导致容器启动失败。
除了使用@Autowired的required属性方式检查,还可以在方法参数上使用@Nullable注解可以达到同样的效果。
5.自动装配的顺序和选择
在同一个类被注入多个Bean实例的状况下,使用@Autowired可以注入该类的依赖对象和该类对应的Bean的集合。
- 注入该类的对象时,需要在某一个Bean的配置上使用@Primary注解标注该Bean实例优先被使用。也可以结合@Autowired和@Qualifier,通过名称等限定标识符查找某一个Bean实例。
- 注入该类的对象时,在Bean配置中使用@Order注解可以设定各Bean实例在集合中的顺序。
@Autowired可以用来自动装配自定义的Bean,也可以用来装配容器的上下文和容器对象,包括BeanFactory、ApplicationContext、Environment等、自动装配可以简化依赖注入的配置,加快开发的速度,但是也存在一些限制和缺点,比如:简单类型不能自动装配。自动装配和显式依赖注入可以共存,显示依赖注入配置会覆盖自动装配。
4.4 @Primary(依赖的主候选)
@Autowired默认根据类来查找和注入容器中的对象,如果存在同一个类的多个Bean实例被容器管理的状况,在使用@Autowired装配该类的依赖对象时也会报UnsatisfiedDependencyException异常,容器初始化失败。可以在该类的某个Bean的配置中设置该Bean作为依赖注入的主候选解决此问题。与XML中<bean>上的primary属性一样。@Primary注解可以使用在类和方法上。可以使用在@Component注解的类上,也可以使用在@Bean注解的方法上。
1.@Primary使用在@Component注解的类上的场景
A是一个接口,BA和CA是该接口的实现类,这两个实现类都用Component注解为组件,如果通过@Autowired注入A接口类型的依赖,会找到两个Bean,此时可以在BA或CA之一中使用@Primary注解。
2.使用在@Bean注解的方法上
方法中使用@Bean注解,可以达成对同一个类的多个Bean实例的注册,这也是@Primary更为常见的使用方式。
@Bean
@Primary
public User firstUser() {
return new User();
}
@Bean
public User secondUser() {
return new User();
}
4.5 @Qualifier(精确查找依赖)
除了使用@Primary指定主候选外,可以用@Autowired和@Qualifier结合使用,根据Bean名字查找依赖对象。@Qualifier可以使用在属性、方法和参数中。例如:
@Autowired
@Qualifier("firstUser")
private User user;
4.6 @Order(同类型的集合注入顺序)
如果同一个类注册了多个Bean实例,则通过@Autowired注解可以自动装配该类的集合类型的依赖,集合中元素的顺序默认是以定义和注入的顺序,也就是在XML文件中的配置顺序或以@Bean注解的顺序进行排序。使用@Order注解可以指定Bean注入的顺序。
@Bean
@Order(1)
public User firstUser() {
return new User();
}
@Bean
@Order(2)
public User secondUser() {
return new User();
}
@Order后面的数值越小,则优先级越高,在集合中的位置越靠前。
@Autowired
private List<User> userList;
5.基于Java代码的配置
配置<context:component-scan>标签后,我们可以在Java类和方法中使用注解实现Bean的配置,自定义类的Bean配置也就可以从XML移到Java代码的注解中实现了。但是还有一些配置留在XML中,比如容器类的Bean配置,类似BeanPostProcessor等,以及<context:component-scan>等功能标签的配置。Spring也提供了完全脱离XML的容器配置方式,也就是所有配置都通过Java代码完成。
5.1 @Configuration(配置类注解)
@Configuration也是一种组件类型的注解,它是使用@Component元注解定义的注解。使用@Configuration注解的类相当于XML配置中的<beans>元素(即相当于一个配置文件),Spring首先会将该类注册为Bean然后可以在该类中的方法使用@Bean注解注册组件,对应的方法相当于XML配置中的<bean>元素。在非@Configuration注解类的@Bean注解方法中不能定义Bean间的依赖关系,如果定义在非@Configuration注解类的依赖关系中,则有可能被当作一般的方法被调用,而不是用来作为Bean定义的方法。对于至这一点,通过如下示例验证:
@Component
public class AppConfig {
@Bean
public User getUser() {
System.out.println("执行getUser方法");
return new User("123");
}
@Bean
public UserService getUserService1() {
System.out.println("执行getUserService1方法");
User user = this.getUser();
return new UserService(user);
}
@Bean
public UserService getUserService2() {
System.out.println("执行getUserService2方法");
User user = this.getUser();
return new UserService(user);
}
}
上面AppConfig类没有被@Configuration注解,UserService依赖User对象,我们将User也注册为Bean,通过getUser获取User这个Bean,并注入到两个UserService对应的Bean中。输出如下内容:
System.out.println(applicationContext.getBean("appConfig"));
System.out.println(applicationContext.getBean("getUser")); //默认方法名为Bean名称
System.out.println(applicationContext.getBean("getUserService1"));
System.out.println(applicationContext.getBean("getUserService2"));
从输出结果可以看出来没有被@Configuration注解的情况下容器中的User对象并没有注入到两个UserService中,而且getUser()方法总共被调用了三次,这就是没有将this.getUser()当成Bean定义方法,而是一般方法调用了,对AppConfig加上@Configuration注解,执行结果如下:
可以看到getUser方法只执行了一次,getUserService1和getUserService2方法中调用的getUser方法被当成从容器中获取Bean(User)的方法,而且这个配置类AppConfig也是通过cglib代理生成的,他会代理配置类中的@Bean注解方法(这个就是为什么User对象都是同一个的原因,因为代理类会先判断容器中是否存在getUser()这个方法返回的Bean,如果存在则直接从容器中获取)。当然还可以指定其中proxyBeanMethods属性为false;这样的话就又会变成像调用一般方法那样了。
@Component
public @interface Configuration {
@AliasFor(annotation = Component.class)
String value() default "";
//Spring 5.2开始引入 //默认为true
boolean proxyBeanMethods() default true;
}
所以如果某些配置类中的Bean没有依赖关系时最好将其设置为false,因为true的话每次还要判断容器中是否存在。
5.2 Java代码配置的容器初始化
XML的容器配置方式使用ClassPathXmlApplicationContext、FileSystemXmlApplicationContext或GenericXmlApplicationContext来读取配置文件并初始化容器,代码配置的方式使用AnnotationConfigApplicationContext根据配置类初始化容器。从类名上可以看出,这个容器初始化类就是通过读取@Configuration注解类来初始化容器的。AnnotationConfigApplicationContext的构造器参数是一个配置类(该类最好用@Configuration注解;该配置类上也可以没有任何注解,即使这样容器也会管理该配置类,个人认为@Configuration注解的作用就是语义性和proxyBeanMethods属性)。该参数是可变长的Class类型,可以添加多个配置类或者组件类。如下:
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(new Class[] { AppConfig.class });
虽然Spring支持@Component注解类作为容器初始化参数和使用register()进行组件注册,但更好的方式是使用类似于XML配置中路径扫描的方式来及进行组件注册。(register()方法是AnnotationConfigApplicationContext的应用上下文的类才有的方法,在添加完注解类之后,需要调用refresh()方法更新容器)
5.3 @ComponentScan(组件扫描注解)
XML配置方式中使用<context:component-scan>标签配置扫描组件,与此类似,在代码配置方式中可以使用@ComponentScan实现此功能,使用属性basePackages设置扫描的路径。如下:
@Configuration //该注解可以去掉,不影响结果。此处是为了语义性。
//该注解可以重复使用
@ComponentScan(basePackages = { "com.mec.spring.annotation" })
public class AppConfig {
}
如果@ComponentScan没有任何参数指定,则他会扫描AppConfig类所在包及其子包下的组件。
该注解定义如下:
public @interface ComponentScan {
//同basePackages属性
@AliasFor("basePackages")
String[] value() default {};
//指定需要扫描的包
@AliasFor("value")
String[] basePackages() default {};
//指定一些类,Spring容器会扫描这些类所在的包以及其子包中的类
Class<?>[] basePackageClasses() default {};
//自定义bean名称生成器
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;
ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;
//需要扫描包中的哪些资源,默认是扫描.class文件
String resourcePattern() default ClassPathScanningCandidateComponentProvider.DEFAULT_RESOURCE_PATTERN;
//类似XML配置中的useDefaultFilters属性。是否启用默认过滤器,默认为true
//默认的过滤器会将标注有@Component、@Repository、@Service、@Controller这几个注解的类注册到容器中
boolean useDefaultFilters() default true;
//类似于XML配置中的<context:include-filter>子标签(白名单)
//注意这里的Filter[]是该注解类中内部定义的@Filter注解
Filter[] includeFilters() default {};
//类似与XML配置中的<context:exclude-filter>子标签(黑名单)
Filter[] excludeFilters() default {};
//全局作用,是否延迟实例化bean
boolean lazyInit() default false;
//includeFilters和excludeFilters就是通过@Filter注解数组来控制过滤的
@Retention(RetentionPolicy.RUNTIME)
@Target({})
@interface Filter {
/*
通过哪种类型过滤,默认为ANNOTATION,表示通过注解进行过滤。还有如下4种取值
ASSIGNABLE_TYPE:通过指定的类型来过滤,即判断候选者是否是指定的类型
ASPECTJ:ASPECTJ表达式方式,即判断候选者是否匹配ASPECTJ表达式
REGEX:正则表达式方式,即判断候选者的完整名称是否和正则表达式匹配
CUSTOM:用户自定义过滤器来筛选候选者,对候选者的筛选交给用户自己来判断
*/
FilterType type() default FilterType.ANNOTATION;
/*
1.type=FilterType.ANNOTATION,该参数可以指定一些注解类,用来判断被扫描的类上是否有classes参数指定的注解
2.type=FilterType.ASSIGNABLE_TYPE,该参数可以指定一些类型,用来判断被扫描的类是否是classes参数指定的类型
3.type=FilterType.CUSTOM,表示这个过滤器是用户自定义的,classes参数就是用来指定用户自定义的过滤器,自定义的过滤器需要实现org.springframework.core.type.filter.TypeFilter接口
*/
@AliasFor("classes")
Class<?>[] value() default {};
@AliasFor("value")
Class<?>[] classes() default {};
/*
1.type=FilterType.ASPECTJ,该参数指定需要匹配的ASPECTJ表达式的值
2.type=FilterType.REGEX,该参数指定正则表达式的值,通过正则筛选过滤
*/
String[] pattern() default {};
}
}
AnnotationConfigApplicationContext还提供了scan()方法在容器初始化之后对包中的组件进行扫描注解,例如:
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.scan(new String[] { "com.mec.spring.annotation" });
applicationContext.refresh();
因为@Configuration是@Component的子注解,所以@Configuration注解的类也会被扫描并处理。在实际项目中,一般是将@Configuration和@Component注解的类放在不同的包中。
5.4 @Import(配置类导入注解)
@Import使用在@Configuration注解的类上,用于导入其他的类,它只有一个value属性,用于指定需要导入的类。使用@Import注解有一个好处就是初始化参数只需要指定一个类就可以,@Import类似于XML中的<import>标签。常用的用法为如下几种情况:
- value指定的类被@Configuration注解:就像在当前的XML配置文件中导入另一个XML配置文件。
- value指定的类被@ComponentScan注解:则被导入的类上面指定的扫描路径也被扫描。
- value指定的类为普通类:这两个普通类作为Bean被注册到容器中。
此外Spring还提供了另外一个标签@ImportResource,可以导入XML的配置文件。
5.5 @Conditional(条件判断注解)
@Conditional注解是Spring4.0引入的,它可以用在任何类型或者方法上,利用该注解可以配置一些条件判断,当所有条件满足时,被@Conditional注解的目标才会注册到容器中。该注解定义如下:
public @interface Conditional {
Class<? extends Condition>[] value();
}
该注解只有一个value属性,需要传入的参数类型必须是Condition接口的实现类,该接口定义如下:
public interface Condition {
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
matches方法用于条件判断,返回true则容器注册该Bean,否则不注册。其中ConditionContext用于获取容器的一些信息,AnnotatedTypeMetadata用于获取被@Conditional注解的对象上的注解信息。
1.使用在类上
@Conditional注解可以使用在配置类上,也可以使用在普通类上。如果使用在配置类上不能满足条件判断的话,那么配置类就不能被注册为Bean,那么里面的配置信息将不能被处理,作用在普通类上,如果不能满足条件的话则不能被注册为Bean。
自定义Condition接口的实现类如下:
public class MyCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return false;
}
}
将@Conditional注解到配置类上,如下:
@Conditional(MyCondition.class) //利用MyCondition中的逻辑判断是否可以处理该配置类
@Configuration
public class AppConfig {
@Bean(name = "userBean")
public User getUser() {
return new User("123");
}
}
MyCondition中默认返回false,所以此时该配置类不能被注册,那么userBean就不能被注册。利用getBean方法获取userBean会报异常。
2.使用在方法上
使用在方法上也是一样,比如在上面的getUser方法上加上该注解,利用MyCondition中的逻辑判断该userBean是否可以被注册。
5.6.@ImportResource(导入配置文件)
@Import用于导入配置类,而该注解用于导入XML配置文件。该注解定义如下:
public @interface ImportResource {
/*
用于指定配置文件路径,Spring中资源文件路径中最常用classpath和classpath*指定路径
在路径指定中可以使用*和**,*为通配符,**表示递归任意子目录。
如:classpath:com/mec/**/beans-*.xml(递归com/mec下的所有子目录,找到其中以“bean-”开头的xml文件)
*/
@AliasFor("locations")
String[] value() default {};
@AliasFor("value")
String[] locations() default {};
/*
指定bean定义的读取器,bean的配置方式有xml文件的方式,注解的方式,其实还有其他的方式,
比如properties文件的方式,如果用其他的方式,需要告诉框架具体要用那种解析器去解析这个bean配置文件,
这个解析器就是BeanDefinitionReader。
*/
Class<? extends BeanDefinitionReader> reader() default BeanDefinitionReader.class;
}
一般我们的项目是Maven项目,classpath就是编译后的.class文件存放的位置。Maven项目classpath即为classes目录,resources目录的文件也会被放到classes目录下。
6.Spring容器注解汇总与说明