Skip to main content

彻底了解Annotation的使用

本篇文章介绍如何使用Annotation方式来配置元数据,当然在整个demo讲解过程中,你会看到xmlAnnotation共存。但是Annotation注入会在XML注入之前解析。 因此,XML配置将覆盖通过两种方法连接的属性的Annotation。

开启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是如何通过这么一个简单的配置,帮我们做了这么多事呢?别急,别慌! 具体流程与细节在这里就不多说了,我先带着大家也实现一个类似功能。

标签模拟实现<context:annotation-config/>#

就像<context:annotation-config>一样,首先我们要先创建一个自定义的元素<lykos:auto-config>

定义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

配置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

注意 :需要使用\转义

创建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

创建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为突破口查看源码。

常用注解#

@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;    }

Autowired 在构造函数上的使用#

有且仅有一个@Autowired(required=true)添加在构造函数上,可以使用多个@Autowired(required=false)用在其它构造函数上 默认情况下,构造函数上不加@Autowired 容器选择的规则是

  • 只有一个构造函数:如果有参数,那参数对象在容器中必须注册
  • 多个构造函数,必须要有无参构造函数

全部使用@Autowired或者混合使用时,顺序如下

    1. 选择@Autowired(required=true) 注解
    1. 选择@Autowired(required=false):存在多个优先选择参数多的
    1. @Autowired(required=false)参数都不满足的,选无参构造函数,如果没有则报错。

Autowired 在属性和方法使用#

autowired在属性和方法上其它没有什么需要说明的,这里只是稍微说明下,@Autowired还可以用于集合数组Map

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;    }

@Configuration 、 @Import 和 @Bean#

此三个注解类似我们配置文件中<beans>,<import>,<bean>标签的功能。此功能是ConfigurationClassPostProcessor为我们引入的,原理是使用BeanDefinitionRegistryPostProcessor.postProcessBeanDefinitionRegistry实现的。还包含Component,ComponentScan,ImportResource注解。下面是普通用法。

@Import(TargetImportConfigration.class)@Configurationpublic 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的组合注解。

<context:component-scan > 相关配置说明#

过滤器配置#

include-filter扫描器的过滤器配置,目的是筛选出配置类。 exclude-filter刚与include-filter功能相反。

type配置过滤器类型,支持:annotation,assignable,aspectj,regex,customexpression是配合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

annotation-config#

是否开启annotation-config配置,默认开启。

scope-resolver 和 scoped-proxy#

scope-resolver自定义ScopeMetadata解析配置类需要实现ScopeMetadataResolver接口,与scope-proxy不能同步使用,scope-proxy设置代理模式,有targetClass,interfaces,no选项值。

name-generator#

设置bean命名器,自定义命名器需要实现BeanNameGenerator接口。

@Component#

上面有提到过,@Component注解,默认容器扫描会扫描包下面所有@Component类。我们有很多常用注解都以@Component做为元注解,@Service,@Repository,@Configuration,@Controller,@ControllerAdvice,@RestController等。

@PostConstruct 和 @PreDestroy#

这两个注解功能是分别是在Bean初始化完成后和销毁时执行的方法,此功能是CommonAnnotationBeanPostProcessor引入的,其原理也分别是BeanPostProcessor.postProcessBeforeInitializationDestructionAwareBeanPostProcessor.postProcessBeforeDestruction实现的。

@Profile#

此注解可以用于方法或者类上面,主要是功能是依照active profiles来选择性实例化Bean对象。 我们可以通过以下方式来设置active profiles

ctx.getEnvironment().setActiveProfiles("profile1", "profile2");
-Dspring.profiles.active="profile1,profile2"

当我们使用@Profile@Conditional作为元注解)时,容器实际会使用ProfileCondition.matches筛选出符合active profiles配置的标识对象。

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 组件扫描器。