Spring 系列篇之彻底了解Annotation的使用

2020-04-20  本文已影响0人  雪狼_lykos

本篇文章介绍如何使用Annotation方式来配置元数据,当然在整个demo讲解过程中,你会看到xmlAnnotation共存。但是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,你需要做下一个操作

idea 引入xsd

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或者混合使用时,顺序如下

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.postProcessBeforeInitializationDestructionAwareBeanPostProcessor.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有两个重要属性

感谢

感谢各位老铁花时间观看!
欢迎留言指正!
内容持续更新!

上一篇下一篇

猜你喜欢

热点阅读