基于注释的容器配置
隐式注解
注解的方式,看起来很明显就比 xml 这种方式简洁,不需要配置大量的自定义标签。
注解的使用很简单,只要在 xml 配置作少量的配置,然后在需要容器加载的类、方法、属性等地方的上面加上适用的注解即可。Spring 在运行的时候,会根据在 xml 配置的扫描范围进行查找需要注册的类、方法、属性。Spring 通常的做法是在必要功能的启动之后来注册并且实例化,这种方式是针对于 singleton 模式,原型的则由在具体程序地方使用的时候,在注册加载。
需要注意的是:注解的方式在 xml 之前,也就是说,在 xml 的配置会覆盖 annotaion 的配置。
随着 spring 版本的编程,实现的方式也不同。
<context:annotaion-config>
:隐式注解
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<bean id="helloWorld" class="com.huasio.ssm.beans.HelloWorld"/>
<bean id="student" class="com.huasio.ssm.beans.Student"/>
</beans>
HelloWorld 类之中,使用 @Autowired 实现自动装配。
@Component
public class HelloWorld {
private String name;
@Autowired
private Student student;
public HelloWorld() {}
// ......
}
在一个测试方法中:
public class HelloTest {
@Test
public void testHello() {
ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
HelloWorld helloWorld = ioc.getBean("helloWorld", HelloWorld.class);
System.out.println(helloWorld); // HelloWorld{name='null', student=Student{id=null, name='null', age=null}}
}
}
也就是说,结果是实现了自动装配,与在 bean 之中开启 autowire 一样的效果。
这种方式同样的也是在 ioc 容器寻找注册的 bean,没有找到报错,并且比起在 bean 属性中定义自动装配,需要事先按照什么类型,而隐式装配会自动切换装配类型。
隐式注解后的处理器:
- AutowiredAnnotationBeanPostProcessor
- CommonAnnotationBeanPostProcessor
- PersistenceAnnotationBeanPostProcessor
- RequiredAnnotationBeanPostProcessor
需要注意的是:<context:annotaion-config>
只作用于当前相同的应用程序的上下文中,在当前的范围寻找的 bean 的注解。
扫描包,托管组件
其实改来改去,底层的还是一回事,就像是这个扫描组件的配置,其底层还是包含了隐式注解,换句话来说就是它们本质都是一样,区别在于扫描组件的方式在这个本质上面丰富了配置,比如说:不用在实现定义自动装配的 bean,让 spring 根据某种过滤条件,将其自动注册并且按照需要自动装配。
通过查看 xml 源代码可以发现,单纯的隐式注解直接使用这个配置:
<xsd:element name="annotation-config">
<xsd:annotation></xsd:annotation>
</xsd:element>
而扫描组件在这之前增加了许多属性:
<xsd:element name="component-scan">
<xsd:annotation></xsd:annotation>
<xsd:complexType>
<xsd:sequence>
<xsd:element name="include-filter" type="filterType"
minOccurs="0" maxOccurs="unbounded">
<xsd:annotation></xsd:annotation>
</xsd:element>
<xsd:element name="exclude-filter" type="filterType"
minOccurs="0" maxOccurs="unbounded">
<xsd:annotation></xsd:annotation>
</xsd:element>
</xsd:sequence>
<xsd:attribute name="base-package" type="xsd:string"
use="required">
<xsd:annotation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="resource-pattern" type="xsd:string">
<xsd:annotation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="use-default-filters" type="xsd:boolean"
default="true">
<xsd:annotation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="annotation-config" type="xsd:boolean"
default="true">
<xsd:annotation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="name-generator" type="xsd:string">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation>
<tool:expected-type type="java.lang.Class"/>
<tool:assignable-to type="org.springframework.beans.factory.support.BeanNameGenerator"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="scope-resolver" type="xsd:string">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation>
<tool:expected-type type="java.lang.Class"/>
<tool:assignable-to type="org.springframework.context.annotation.ScopeMetadataResolver"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="scoped-proxy">
<xsd:annotation>
</xsd:annotation>
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:enumeration value="no"/>
<xsd:enumeration value="interfaces"/>
<xsd:enumeration value="targetClass"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
贴上配置源代码,一看就知道那种方式的功能更丰富。
使用这个需要:xmlns:context="http://www.springframework.org/schema/context"
命名空间,一般 IDEA 会自动导入。
说了这么多,还是直接上实例:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 就这个配置即可 -->
<context:component-scan base-package="com.huasio.spring"/>
</beans>
通过上面的简单一个配置,这个配置定义了要扫描包的范围,在这个范围内,只要是加了 @Component 类、都会自动被 ioc 容器注册,并且加载(若是单例的话)。
Spring 识别的组件有:
- @Component
- 基础组件,其它三个注解继承该注解。
- 可以修饰一些没有特殊作用的 POJO。
- @Controller
- 控制层注解,在 spring 没有特殊作用。
- 在 SpringMVC 中代表着控制层
- @Service
- 服务层,注解一些编写业务逻辑的类。
- @Repository
- 数据层注解,一般使用在 DAO 类。
后面三个继承第一个注解,所以它们能被 spring 识别。
并且使用了扫描包的方式,就不用配置隐式注解了,配置了也会被 spring 忽略。
在使用方面,扫描的有一些属性需要会的,还有子标签,例如说:
- 自动识别注解:use-default-filters
- true and false
- 这个属性的一般与 include-filter 子标签搭配使用。
- 子标签:
<context:include-filter>
- 只加载......
<context:exclude-filter>
- 排除加载......
这两个子标签在整合 spring、springMVC 的时候非常有用,可用区分它们的负责范围。
两个子标签过滤类型都一样,不同的是过滤条件。
类型 | 说明 |
---|---|
annotation | 按注解的方式过滤加载 |
custom | 自定义 |
aspectj | aspectj 语法 |
assignable | 指定 class 或 interface |
regex | 正则表达式 |
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 只加载 org.springframework.stereotype.Controller 注解。 -->
<!-- 不适用默认的过滤器 -->
<context:component-scan base-package="com.huasio.spring" use-default-filter="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!-- 排除 org.springframework.stereotype.Controller 注解。 -->
<context:component-scan base-package="com.huasio.ssm">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
</beans>
注解列表
@Required
使用这个注解的,方法,构造器,属性等,标识必须要显式定义 bean 或者实现自动装配。
使用在 setter 方法上面,标识着形参列表必须要,若是没有显式赋值或者自动装配,那么会抛出异常。
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Required
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
通常对于一些在对象需要某些依赖的时候,必要在初始化方法检测,避免在后续使用时出现 NullPoniterException 异常。、
@Autowired
此注解作用与:属性、构造函数、方法等,表示由 ioc 容器自动装配,并且这个注解默认是强制必须的,同样没有的找到对应 bean,也会出现异常。
可用其 required 属性设置为:false,这样表示不是必须的。
public class SimpleMovieLister {
@Autowired
private MovieFinder movieFinder;
}
@Primary
使用自动装配的时候,其中有个模式的按照类型自动装配,这种情况很有可能会出现找到多个 bean,因为其可能有父类,同类等。
那么一旦出现多个 bean 的时候,若是没有特别处理,那么会抛出异常。
使用这个注解的作用就是将特定的结构标识为主要自动装配对象,避免在出现多个 bean 的时候没法处理。
public class SimpleMovieLister {
@Autowired
private MovieFinder movieFinder;
}
@Primary
class MovieFinder extends MovieFinderParent{}
class MovieFinderParent {}
像这个例子中,若是没有将 MovieFinder 标识为主要自动装配对象,那么就会可能将其父类也找到。
@Qualifier
自动装配的时候有多种模式,其中有一种是按照 tyName id 进行装配的,具体是根据标识名与 id 匹配。
拿属性来举例,有些时候,可能属性名与 id 不一致的时候,可用使用这个注解来指定这个属性装配 bean id。
public class SimpleMovieLister {
@Autowired
@Qualifier("MF")
private MovieFinder movieFinder;
}
会在容器寻找名为:MF 的 bean。
@Resource
这个注解如同 @Autowired 一样的作用,同样都是要求属性方法自动装配。
区别在于 @Resource 可用两种方式找到,byName 和 byType。
@Resource 自动装配顺序:
- 按照 name 属性值在容器中查找与之相同的 bean,自动装配。
- 若是 name 为空,type 也为空,则按照 byType 的方式。
- 若是指定 type,则按照 byType 在容器中匹配。
- 若是同时指定 name 和 type 则在容器中查找满足两个条件的 bean。
- 若是都没有找到,则抛出异常。
public class SimpleMovieLister {
@Resource("mf")
private MovieFinder movieFinder;
}
@Value
这个注解通常用于外部属性注入,例如说 *.properties、*.yml。
@PropertySource("classpath:application.properties")
@Component
public class MovieRecommender {
private final String catalog;
public MovieRecommender(@Value("${catalog.name}") String catalog) {
this.catalog = catalog;
}
}
在 application.properties 文件中:
catalog.name=MovieCatalog
在配置文件若是没有找到指定标识符的值,那么会将 $ 作为值。
若是要限制这种行为,声明一个方法:
@Configuration
public AppConfig {
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfgurer() {
reutrn new PropertySourcesPlaceholderConfigurer();
}
}
需要搭配 @Configuration 和 @Bean,这两个的是将在 spring 配置文件中增加 bean。
默认的 spring 会对于 String 和 Integer 自动转换支持,除了字面量,还支持数组,多个值以逗号 ,
分割,就是形成数组。
Spring BeanPostProcessor 使用 CoversionService 将 @Value 进行转换类型,如果要定义类型,则提供 ConversionService bean。
@Configuration
public class AppConfig {
@Bean
public ConversionService conversionService() {
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
conversionService.addConverter(new MyCustomConverter());
return conversionService;
}
}
除了这些之外,还有小知识点需要了解的,在引用外部属性的时候是使用:${}
这个标识符号来表示使用外部的一个变量值。同样,各种符号可用方法自定义。
方法 | 说明 |
---|---|
setPlaceholderPrefix | 前缀 |
setPlaceholderSuffix | 后缀 |
setValueSeparator | 值之间的分割符号 |
这个几个方法是来自于 PropertySourcesPlaceholderConfigurer 类,设置了强制检测之后,一旦没有值报错,那么可以指定一个默认值。
@Component
public class MovieRecommender {
private final String catalog;
public MovieRecommender(@Value("${catalog.name:defaultCatalog}") String catalog) {
this.catalog = catalog;
}
}
在 @Value 之中也能使用 EL 表达式:
@Component
public class MovieRecommender {
private final Map<String, Integer> countOfMoviesPerCatalog;
private final String catalog;
public MovieRecommender(@Value("#{systemProperties['user.catalog'] + 'Catalog' }") String catalog) {
this.catalog = catalog;
}
public MovieRecommender(
@Value("#{{'Thriller': 100, 'Comedy': 300}}") Map<String, Integer> countOfMoviesPerCatalog) {
this.countOfMoviesPerCatalog = countOfMoviesPerCatalog;
}
}
@PostConstruct 和 @PreDestroy
还记不记得,在 xml 配置 bean,其中有两个属性就是定义生命周期的方法。同样的,注解驱动的也有两个注解,代表着这两个阶段的生命周期。
前者是初始化阶段,后者是销毁阶段。
public class CachingMovieLister {
@PostConstruct
public void populateMovieCache() {
// populates the movie cache upon initialization...
}
@PreDestroy
public void clearMovieCache() {
// clears the movie cache upon destruction...
}
}
这两个注解和上面的 @Resource 一样,都是 jdk6 ~ jdk8 提供的,但是在 jdk9 的时候与核心部分分开了,在 jdk11 被移除。如果有需要,可以通过 Maven Central 获取 javax.annotation-api 工具,如同其它类库一样使用。
相关链接
- 生命周期:https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#beans-factory-lifecycle-combined-effects
- 注解:https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#beans-annotation-config
- 扫描组件:https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#beans-classpath-scanning
Q.E.D.
Comments | 0 条评论