目录java
最近在学习Spring Boot相关的课程,过程当中以笔记的形式记录下来,方便之后回忆,同时也在这里和你们探讨探讨,文章中有漏的或者有补充的、错误的都但愿你们可以及时提出来,本人在此先谢谢了!web
开始以前呢,但愿你们带着几个问题去学习:
一、Spring注解驱动是什么?
二、这个功能在什么时代背景下发明产生的?
三、这个功能有什么用?
四、怎么实现的?
五、优势和缺点是什么?
六、这个功能能应用在工做中?
这是对自个人提问,我认为带着问题去学习,是一种更好的学习方式,有利于加深理解。好了,接下来进入主题。spring
咱们先来简单的聊聊Spring注解的发展史。Spring1.x时代,那时候注解的概念刚刚兴起,仅支持如 @Transactional 等注解。到了2.x时代Spring的注解体系有了雏形,引入了 @Autowired 、 @Controller 这一系列骨架式的注解。3.x是黄金时代,它除了引入 @Enable 模块驱动概念,加快了Spring注解体系的成型,还引入了配置类 @Configuration 及 @ComponentScan ,使咱们能够抛弃XML配置文件的形式,全面拥抱Spring注解,但Spring并未彻底放弃XML配置文件,它提供了 @ImportResource 容许导入遗留的XML配置文件。此外还提供了 @Import 容许导入一个或多个Java类成为Spring Bean。4.X则趋于完善,引入了条件化注解 @Conditional ,使装配更加的灵活。当下是5.X时代,是SpringBoot2.0的底层核心框架,目前来看,变化不是很大,但也引入了一个 @Indexed 注解,主要是用来提高启动性能的。好了,以上是Spring注解的发展史,接下来咱们对Spring注解体系的几个议题进行讲解。编程
模式注解是一种用于声明在应用中扮演“组件”角色的注解。如 Spring 中的 @Repository 是用于扮演仓储角色的模式注解,用来管理和存储某种领域对象。还有如@Component 是通用组件模式、@Service 是服务模式、@Configuration 是配置模式等。其中@Component 做为一种由 Spring 容器托管的通用模式组件,任何被 @Component 标注的组件均为组件扫描的候选对象。相似地,凡是被 @Component 标注的注解,如@Service ,当任何组件标注它时,也被视做组件扫描的候选对象。
举例:缓存
Spring注解 | 场景说明 | 起始版本 |
---|---|---|
@Componnt | 通用组件模式注解 | 2.5 |
@Repository | 数据仓储模式注解 | 2.0 |
@Service | 服务模式注解 | 2.5 |
@Controller | Web 控制器模式注解 | 2.5 |
@Configuration | 配置类模式注解 | 3.0 |
那么,被这些注解标注的类如何交由Spring来管理呢,或者说如何被Spring所装配呢?接下来咱们就来看看Spring的两种装配方式。服务器
<?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.loong.spring.boot" /> </beans>
第一种是XML配置文件的方式,经过 base-package 这个属性指定扫描某个范围内全部被 @Component 或者其派生注解标记的类(Class),将它们注册为 Spring Bean。app
咱们都知道XML Schema 规范,标签须要显示地关联命名空间,如配置文件中的 xmlns:context="http://www.springframework.org/schema/context" ,且须要与其处理类创建映射关系,而该关系维护在相对于 classpath 下的/META-INF/spring.handlers 文件中。以下:框架
http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler http\://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler http\://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler http\://www.springframework.org/schema/task=org.springframework.scheduling.config.TaskNamespaceHandler http\://www.springframework.org/schema/cache=org.springframework.cache.config.CacheNamespaceHandler
能够看到, context 所对应的处理器为 ContextNamespaceHandler异步
public class ContextNamespaceHandler extends NamespaceHandlerSupport { @Override public void init() { ..... registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser()); registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser()); ..... } }
这里当Spring启动时,init方法被调用,随后注册该命名空间下的全部 Bean 定义解析器,能够看到
@ComponentScan(basePackages = "com.loong.spring.boot") public class SpringConfiguration { }
第二种是注解的形式,一样也是依靠 basePackages 属性指定扫描范围。
Spring 在启动时,会在某个生命周期内建立全部的配置类注解解析器,而 @ComponentScan 的处理器为 ComponentScanAnnotationParser ,感兴趣的同窗能够去深刻了解,这里一样再也不赘述。
咱们用自定义注解的方式来看一看文中提到的派生性:
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Repository public @interface FirstLevelRepository { String value() default ""; }
能够看到咱们自定义了一个
@FirstLevelRepository 注解,当前注解又标注了 @Repository,而 @Repository 又标注了 @Component 而且注解属性一致(String value() default""),那么就能够表示当前注解包含了
@Repository 及 @Component 的功能。
派生性其实能够分为多层次的,如
@SprintBootApplication -> @SpringBootConfiguration -> @Configuration -> @Component 能够看到@Component被派生了多个层次,但这种多层次的派生性Spring 4.0版本才开始支持,Spring3.0仅支持两层。
前文提到Spring3.X是一个黄金时代,它不只全面拥抱注解模式,还开始支持“@Enable模块驱动”。所谓“模块”是指具有相同领域的功能组件集合,组合所造成的一个独立的单元,好比 Web MVC 模块、AspectJ代理模块、Caching(缓存)模块、JMX(Java 管理扩展)模块、Async(异步处理)模块等。这种“模块”理念在后续的Spring 、Spring Boot和Spring Cloud版本中都一直被使用,这种模块化的注解均以 @Enable 做为前缀,以下所示:
框架实现 | @Enable注解模块 | 激活模块 |
---|---|---|
Spring Framework | @EnableWebMvc | Web Mvc 模块 |
/ | @EnableTransactionManagement | 事物管理模块 |
/ | @EnableWebFlux | Web Flux 模块 |
Spring Boot | @EnableAutoConfiguration | 自动装配模块 |
/ | @EnableConfigurationProperties | 配置属性绑定模块 |
/ | @EnableOAuth2Sso | OAuth2 单点登录模块 |
Spring Cloud | @EnableEurekaServer | Eureka 服务器模块 |
/ | @EnableFeignClients | Feign 客户端模块 |
/ | @EnableZuulProxy | 服务网关 Zuul 模块 |
/ | @EnableCircuitBreaker | 服务熔断模块 |
引入模块驱动的意义在于简化装配步骤,屏蔽了模块中组件集合装配的细节。但该模式必须手动触发,也就是将该注解标注在某个配置Bean中,同时理解原理和加载机制的成本较高。那么,Spring是如何实现 @Enable 模块呢?主要有如下两种方式。
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import(DelegatingWebMvcConfiguration.class) public @interface EnableWebMvc { } @Configuration public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport { ... }
这种实现模式主要是经过 @Import 导入配置类 DelegatingWebMvcConfiguration ,而该类标注了 @Configuration 注解,代表这是个配置类,咱们都知道 @EnableWebMvc 是用来激活Web MVC模块,因此如HandlerMapping 、HandlerAdapter这些和MVC相关的组件都是在这个配置类中被组装,这也就是所谓的模块理念。
基于接口编程一样有两种实现方式,第一种参考 @EnableCaching的实现:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Import(CachingConfigurationSelector.class) public @interface EnableCaching { ... } public class CachingConfigurationSelector extends AdviceModeImportSelector<EnableCaching> { @Override public String[] selectImports(AdviceMode adviceMode) { switch (adviceMode) { //switch语句选择实现模式 case PROXY: return new String[]{AutoProxyRegistrar.class.getName(), ProxyCachingConfiguration.class.getName()}; case ASPECTJ: return new String[]{AnnotationConfigUtils.CACHE_ASPECT_CONFIGURATION_CLASS_NAME}; default: } } }
这种方式主要是继承 ImportSelector 接口(AdviceModeImportSelector实现了ImportSelector接口),而后实现 selectImports 方法,经过入参进而动态的选择一个或多个类进行导入,相较于注解驱动,此方法更具备弹性。
第二种参考 @EnableApolloConfig 的实现:
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import(ApolloConfigRegistrar.class) public @interface EnableApolloConfig { .... }
public class ApolloConfigRegistrar implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { .... BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, PropertySourcesPlaceholderConfigurer.class.getName(), PropertySourcesPlaceholderConfigurer.class, propertySourcesPlaceholderPropertyValues); BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, PropertySourcesProcessor.class.getName(), PropertySourcesProcessor.class); BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, ApolloAnnotationProcessor.class.getName(), ApolloAnnotationProcessor.class); BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, SpringValueProcessor.class.getName(), SpringValueProcessor.class); BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, SpringValueDefinitionProcessor.class.getName(), SpringValueDefinitionProcessor.class); BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, ApolloJsonValueProcessor.class.getName(), ApolloJsonValueProcessor.class); } }
这种方式主要是经过 @Import 导入实现了 ImportBeanDefinitionRegistrar 接口的类,在该类中重写 registerBeanDefinitions 方法,经过 BeanDefinitionRegistry 直接手动注册和该模块相关的组件。接下来,咱们用这两种方式实现自定义的 @Enable 模块。
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import(HelloWorldConfiguration.class) public @interface EnableHelloWorld { }
@Configuration public class HelloWorldConfiguration { // 能够作一些组件初始化的操做。 @Bean public String helloWorld(){ return "hello world"; } // .... }
@EnableHelloWorld public class EnableHelloWorldBootstrap { public static void main(String[] args) { ConfigurableApplicationContext context = new SpringApplicationBuilder(EnableHelloWorldBootstrap.class) .web(WebApplicationType.NONE).run(args); String helloWorld = context.getBean("helloWorld",String.class); System.out.println(helloWorld ); } }
这里咱们自定义了一个 @EnableHelloWorld 注解,再用 @Import 导入一个自定义的配置类 HelloWorldConfiguration,在这个配置类中初始化 helloWorld 。
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import(HelloWorldImportSelector.class) public @interface EnableHelloWorld { }
public class HelloWorldImportSelector implements ImportSelector { /** * 这种方法比较有弹性: * 能够调用importingClassMetadata里的方法来进行条件过滤 * 具体哪些方法参考:https://blog.csdn.net/f641385712/article/details/88765470 */ @Override public String[] selectImports(AnnotationMetadata importingClassMetadata) { if (importingClassMetadata.hasAnnotation("com.loong.case3.spring.annotation.EnableHelloWorld")) { return new String[]{HelloWorldConfiguration.class.getName()}; } } }
@Configuration public class HelloWorldConfiguration { // 能够作一些组件初始化的操做 @Bean public String helloWorld(){ return "hello world"; } // .... }
@EnableHelloWorld public class EnableHelloWorldBootstrap { public static void main(String[] args) { ConfigurableApplicationContext context = new SpringApplicationBuilder(EnableHelloWorldBootstrap.class) .web(WebApplicationType.NONE).run(args); String helloWorld = context.getBean("helloWorld",String.class); System.out.println(helloWorld ); } }
这里咱们一样是自定义 @EnableHelloWorld 注解,经过 @Import 导入 HelloWorldImportSelector 类,该类实现了 ImportSelector 接口,在重写的方法中经过 importingClassMetadata.hasAnnotation("com.loong.case3.spring.annotation.EnableHelloWorld") 判断该类是否标注了 @EnableHelloWorld 注解,从而导入 HelloWorldConfiguration 类,进行初始化工做。
第二种基于 ImportBeanDefinitionRegistrar 接口:
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import(HelloWorldRegistrar.class) public @interface EnableHelloWorld { }
public class HelloWorldRegistrar implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) { if (annotationMetadata.hasAnnotation("com.loong..case4.spring.annotation.EnableHelloWorld")) { RootBeanDefinition beanDefinition = new RootBeanDefinition(HelloWorldConfiguration.class); beanDefinitionRegistry.registerBeanDefinition(HelloWorldConfiguration.class.getName(), beanDefinition); } } }
@Configuration public class HelloWorldConfiguration { public HelloWorldConfiguration() { System.out.println("HelloWorldConfiguration初始化...."); } }
@EnableHelloWorld public class EnableHelloWorldBootstrap { public static void main(String[] args) { ConfigurableApplicationContext context = new SpringApplicationBuilder(EnableHelloWorldBootstrap.class) .web(WebApplicationType.NONE).run(args); } }
这里就是在 HelloWorldRegistrar 中利用 BeanDefinitionRegistry 直接注册 HelloWorldConfiguration。
条件装配指的是经过一些列操做判断是否装配 Bean ,也就是 Bean 装配的前置判断。实现方式主要有两种:@Profile 和 @Conditional,这里咱们主要讲 @Conditional 的实现方式,由于 @Profile 在 Spring 4.0 后也是经过 @Conditional 来实现。
@Conditional(HelloWorldCondition.class) @Component public class HelloWorldConfiguration { public HelloWorldConditionConfiguration (){ System.out.println("HelloWorldConfiguration初始化。。。"); } }
public class HelloWorldCondition implements Condition { @Override public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) { // ... return true; } }
这里经过自定义一个 HelloWorldConfiguration 配置类,再标注 @Conditional 注解导入 HelloWorldCondition类,该类必须实现 Condition 接口,而后重写 matches 方法,在方法中能够经过两个入参来获取一系列的上下文数据和元数据,最终返回ture或false来断定该类是否初始化,
关于Spring注解驱动的概念就告一段落,最后来简单的回顾下这篇文章的内容,这篇文章主要讲了 Spring 注解相关的几个概念:Spring模式注解、@Enable 模块驱动和 Spring 的条件装配。其中 Spring 模式注解的核心是 @Component,全部的模式注解均被它标注,而对应两种装配方式实际上是寻找 @Component 的过程。Spring @Enable 模块的核心是在 @Enable 注解上经过 @Import 导入配置类 ,从而在该配置类中实现和当前模块相关的组件初始化工做。能够看到,Spring 组件装配并不具有自动化,都须要手动标注多种注解,且之间需相互配合,因此下一章咱们就来说讲 Spring Boot是如何基于 Spring 注解驱动来实现自动装配的。
以上就是本章的内容,如过文章中有错误或者须要补充的请及时提出,本人感激涕零。
参考:
《Spring Boot 编程思想》