Spring 系列篇之彻底了解Annotation的使用
本篇文章介绍如何使用Annotation
方式来配置元数据
,当然在整个demo讲解过程中,你会看到xml
与Annotation
共存。但是Annotation注入会在XML注入之前解析。 因此,XML配置将覆盖
通过两种方法连接的属性的Annotation。
目录
1 开启IoC Annotation
1.1 创建自定义标签模拟实现<context:annotation-config/>
1.1.1 定义xsd
1.1.2 向Spring告知xsd所在的位置 - 配置spring-schemas
1.1.3 创建NamespaceHandler
1.1.4 创建BeanDefinitionParser
2 常用注解
2.1 @Autowired
2.1.1 Autowired 在构造函数上的使用
2.1.2 Autowired 在属性和方法使用
2.1.3 Autowired 配合使用 Optional @Nullable
2.2 @Configuration 、 @Import 和 @Bean
2.2.1 <context:component-scan > 相关配置说明
2.2.1.1 过滤器配置
2.2.1.2 annotation-config
2.2.1.3 scope-resolver 和 scoped-proxy
2.2.1.4 name-generator
2.3 @Component
2.4 @PostConstruct 和 @PreDestroy
2.5 @Profile
3 AnnotationConfigApplicationContext
1 开启IoC Annotation
我们在使用Annotation 之前必须先把这个功能开启。
<?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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
</beans>
添加上面的配置后,Spring执行过程中,会默认帮我们注入
ConfigurationClassPostProcessor
,AutowiredAnnotationBeanPostProcessor
,CommonAnnotationBeanPostProcessor
,PersistenceAnnotationBeanPostProcessor
,EventListenerMethodProcessor
,DefaultEventListenerFactory
。
那么问题来了,Spring是如何通过这么一个简单的配置,帮我们做了这么多事呢?别急,别慌!
具体流程与细节在这里就不多说了,我先带着大家也实现一个类似功能。
1.1 创建自定义标签模拟实现<context:annotation-config/>
就像<context:annotation-config>
一样,首先我们要先创建一个自定义的元素<lykos:auto-config>
。
1.1.1 定义xsd
在使用自定义元素之前,我们需要先创建一个XSD
文件(存放在resources资源文件夹下),用来定义我们自定义的元素结构,因为XSD
定义xml
文件结构不是这里的重点,所以我们只是简单的定义auto-config
元素,对于xsd这里不做过多的解释。注意lykos
单词出现的地方,我们称为命名空间,这里可以随变定义自己的唯一的命名空间,命名空间将会在<beans>
中引入并使用
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://www.lykosliu.org/spring/lykos"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:beans="http://www.springframework.org/schema/beans"
targetNamespace="http://www.lykosliu.org/spring/lykos"
attributeFormDefault="unqualified">
<xsd:import namespace="http://www.springframework.org/schema/beans" schemaLocation="https://www.springframework.org/schema/beans/spring-beans.xsd"/>
<xsd:element name="auto-config">
</xsd:element>
</xsd:schema>
创建好xsd文件之后,我们在beans.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:context="http://www.springframework.org/schema/context"
xmlns:lykos="http://www.lykosliu.org/spring/lykos"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.lykosliu.org/spring/lykos
https://www.lykosliu.org/spring/lykos/spring-lykos.xsd">
<lykos:auto-config></lykos:auto-config>
</beans>
同样注意引入lykos
命名空间。此时引入可能会报错,报红。如果老铁是用的idea
,你需要做下一个操作
1.1.2 向Spring告知xsd
所在的位置 - 配置spring-schemas
操作完上面的几步后,我们只是能在xml文件中可以添加自定义标签元素,此时Spring解析器还无法识别我们的标签,我们还需要告诉Spring 我们定义的命名空间应该如何解析,也就是告诉Spring我们的xsd
在哪个位置。
在resources/META-INF
下添加文件spring-schemas.xml
,内容如下:
http\://www.lykosliu.org/spring/lykos/spring-lykos.xsd=xsd/spring-lykos.xsd
注意 :
需要使用\
转义
1.1.3 创建NamespaceHandler
Spring 对 xml
解析都是封装成NamespaceHandler
解析器,上面我们自己定义了新的命名空间,当然我们也要为其创建一个解析器。
public class LykosNamespaceHandlerSupport extends NamespaceHandlerSupport {
//注册解析器,元素解析器
@Override
public void init() {
registerBeanDefinitionParser("auto-config", new CustomBeanDefinitionParser());
}
}
这里使用了registerBeanDefinitionParser
向NamespaceHandler注册了一个auto-config
元素解析器(BeanDefinitionParser)。创建好NamespaceHandler
后还需要在META-INF
目录下添加spring.handlers
文件,内容如下:
http\://www.lykosliu.org/spring/lykos=com.lykos.handle.LykosNamespaceHandlerSupport
1.1.4 创建BeanDefinitionParser
BeanDefinitionParser
主要是将元素标签解析成BeanDefinition对象,并将其注册到Spring IoC中去。这里我们就使用它来注册我们需要的扩展对象。
public class CustomBeanDefinitionParser implements BeanDefinitionParser {
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
Object source = parserContext.extractSource(element);
RootBeanDefinition def = new RootBeanDefinition(MyBeanPostProcessor.class);
def.setSource(source);
def.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
BeanDefinitionRegistry registry = parserContext.getRegistry();
registry.registerBeanDefinition("myBeanPostProcessor", def);
return null;
}
}
到这里相信老铁们应该知道<context:annotation-config/>
实现的大致流程了,其最终也是使用AnnotationConfigBeanDefinitionParser
帮我自动注册的几个扩展对象。
当然Spring 不只annotation-config
还有很多,如:component-scan
,spring-configured
,property-placeholder
等,还有其它元素解析器,建义老铁查看ContextNamespaceHandler
对象。
上面的讲解建议老铁们,可以从
DefaultBeanDefinitionDocumentReader.parseBeanDefinitions
为突破口查看源码。
2 常用注解
2.1 @Autowired
autowired 自动装配注解
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
public @interface Autowired {
boolean required() default true;
}
由上面的定义可以知道,autowired可以用于构造函数
,方法
,属性
等上面。
required
用于标注依赖项是否必须的,默认是必须的。比如下面的例子中,如果容器中没有Person实例,容器不会报错也不会执行setPerson方法,一旦将reuired设为true(或者不写),容器就会报NoSuchBeanDefinitionException: No qualifying bean
异常。
@Autowired(required = false)
private void setPerson(Person person){
this.person = person;
}
2.1.1 Autowired 在构造函数上的使用
有且仅有一个@Autowired(required=true)
添加在构造函数上,可以使用多个@Autowired(required=false)
用在其它构造函数上
默认情况下,构造函数上不加@Autowired 容器选择的规则是
- 只有一个构造函数:如果有参数,那参数对象在容器中必须注册
- 多个构造函数,必须要有无参构造函数
全部使用@Autowired或者混合使用时,顺序如下
- 选择@Autowired(required=true) 注解
- 选择@Autowired(required=false):存在多个优先选择参数多的
- @Autowired(required=false)参数都不满足的,选无参构造函数,如果没有则报错。
2.1.2 Autowired 在属性和方法使用
autowired在属性和方法上其它没有什么需要说明的,这里只是稍微说明下,@Autowired
还可以用于集合
、数组
、Map
2.1.3 Autowired 配合使用 Optional
@Nullable
Autowired可以配合使用Optional,Nullable,目的和@Autowired(required=false)是一样的,但是区别是如果没有匹配对象@Autowired(required=false)
处理逻辑是不执行,而Optional
,Nullable
是可以继续执行方法的。
@Autowired
private void setPerson(Optional<Person> personOptional){
if(personOptional.isPresent()){
this.person = personOptional.get();
}
}
@Autowired
private void setTelevision(@Nullable Television television){
this.television = television;
}
2.2 @Configuration 、 @Import 和 @Bean
此三个注解类似我们配置文件中<beans>
,<import>
,<bean>
标签的功能。此功能是ConfigurationClassPostProcessor
为我们引入的,原理是使用BeanDefinitionRegistryPostProcessor.postProcessBeanDefinitionRegistry
实现的。还包含Component
,ComponentScan
,ImportResource
注解。下面是普通用法。
@Import(TargetImportConfigration.class)
@Configuration
public class CustomConfigration {
@Bean
public Television television(){
return new Television();
}
}
当然如果要能正常使用@Configuration
我还必须要告诉容器应该自动注册哪些包下面的Configuration类,这里我们需要添加<context:component-scan base-package="包地址,这里可以写多个用,;等分隔" />
配置,此配置默认是引入了我们前面重点讲的<context:annotation-config/>
配置。其实现原理可以查看ComponentScanBeanDefinitionParser
,之所以容器可以识别我们的配置类,是因为ComponentScanBeanDefinitionParser在创建ClassPathBeanDefinitionScanner
扫描器时默认会添加AnnotationTypeFilter(Component.class)
扫描过滤器,而@Configuration
又是包含了@Component
的组合注解。
2.2.1 <context:component-scan > 相关配置说明
2.2.1.1 过滤器配置
include-filter
扫描器的过滤器配置,目的是筛选出配置类。
exclude-filter
刚与include-filter功能相反。
type
配置过滤器类型,支持:annotation
,assignable
,aspectj
,regex
,custom
。
expression
是配合type
使用的实际值。
<context:component-scan base-package="com.lykos.ioc.chapter4" >
<context:include-filter type="" expression=""/>
<context:exclude-filter type="annotation" expression=""/>
</context:component-scan>
其它细节可以查看原码ComponentScanBeanDefinitionParser.parseTypeFilters
2.2.1.2 annotation-config
是否开启annotation-config配置,默认开启。
2.2.1.3 scope-resolver 和 scoped-proxy
scope-resolver自定义ScopeMetadata
解析配置类需要实现ScopeMetadataResolver
接口,与scope-proxy
不能同步使用,scope-proxy
设置代理模式,有targetClass
,interfaces
,no
选项值。
2.2.1.4 name-generator
设置bean命名器,自定义命名器需要实现BeanNameGenerator
接口。
2.3 @Component
上面有提到过,@Component注解,默认容器扫描会扫描包下面所有@Component类。我们有很多常用注解都以@Component
做为元注解,@Service
,@Repository
,@Configuration
,@Controller
,@ControllerAdvice
,@RestController
等。
2.4 @PostConstruct 和 @PreDestroy
这两个注解功能是分别是在Bean初始化完成后和销毁时执行的方法,此功能是CommonAnnotationBeanPostProcessor
引入的,其原理也分别是BeanPostProcessor.postProcessBeforeInitialization
和DestructionAwareBeanPostProcessor.postProcessBeforeDestruction
实现的。
2.5 @Profile
此注解可以用于方法或者类上面,主要是功能是依照active profiles
来选择性实例化Bean对象。
我们可以通过以下方式来设置active profiles
ctx.getEnvironment().setActiveProfiles("profile1", "profile2");
-Dspring.profiles.active="profile1,profile2"
当我们使用@Profile
(@Conditional作为元注解)时,容器实际会使用ProfileCondition.matches
筛选出符合active profiles
配置的标识对象。
3 AnnotationConfigApplicationContext
之前的讲解都是基于ClassPathXmlApplicationContext
xml
的启动配置,还是依赖于xml配置文件,而AnnotationConfigApplicationContext
可以让我们达到零配置文件
启动IoC容器。
下面列举常用三种启动模式
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(App.class);
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(App1.class,App2.class);
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
ac.register(App.class);
ac.refresh();
AnnotationConfigApplicationContext有两个重要属性
- AnnotatedBeanDefinitionReader 主要是向容器中注入组件类,比如常用的
AnnotationConfigProcessors
这个与我们之前讲解的annotation-config一样 - ClassPathBeanDefinitionScanner 组件扫描器。
感谢
感谢各位老铁花时间观看!
欢迎留言指正!
内容持续更新!