本篇文章,从Spring1.x到Spring 5.x的迭代中,站在现在的角度去思考Spring注解驱动的发展过程,这将有助于我们更好的理解Spring中的注解设计。

Spring Framework 1.x

在SpringFramework1.x时代,其中在1.2.0是这个时代的分水岭,当时Java5刚刚发布,业界正兴起了使用Annotation的技术风,Spring Framework自然也提供了支持,比如当时已经支持了@Transactional等注解,但是这个时候,XML配置方式还是唯一选择。

  • 在xml中添加Bean的声明

    <bean name="testService" class="com.gupaoedu.controller.TestService"/>
  • 测试

    public class XmlMain {
    public static void main(String[] args) {
    ApplicationContext context=new FileSystemXmlApplicationContext("classpath:applicationContext.xml");
    TestService testService=(TestService)context.getBean("testService");
    System.out.println(testService);
    }
    }

Spring Framework 2.x

Spring Framework2.x时代,2.0版本在Annotation中添加了@Required、@Repository以及AOP相关的@Aspect等注解,同时也提升了XML配置能力,也就是可扩展的XML,比如Dubbo这样的开源框架就是基于Spring XML的扩展来完美的集成Spring,从而降低了Dubbo使用的门槛。

在2.x时代,2.5版本也是这个时代的分水岭, 它引入了一些很核心的Annotation

  • Autowired 依赖注入
  • @Qualifier 依赖查找
  • @Component、@Service 组件声明
  • @Controller、@RequestMappring等spring mvc的注解

尽管Spring 2.x时代提供了不少的注解,但是仍然没有脱离XML配置驱动,比如context:annotation-config context:componet-scan , 前者的职责是注册Annotation处理器,后者是负责扫描classpath下指定包路径下被Spring模式注解标注的类,将他们注册成为Spring Bean

  • 在applicationContext.xml中定义context:componet-scan

    <context:component-scan base-package="com.gupaoedu.controller"/>
  • 添加注解声明

    @Service
    public class TestService {
    }
  • 测试类

    public class XmlMain {
    public static void main(String[] args) {
    ApplicationContext context=new FileSystemXmlApplicationContext("classpath:applicationContext.xml");
    TestService testService=(TestService)context.getBean("testService");
    System.out.println(testService);
    }
    }

Spring Framework 3.x

Spring Framework3.0是一个里程碑式的时代,他的功能特性开始出现了非常大的扩展,比如全面拥抱Java5、以及Spring Annotation。更重要的是,它提供了配置类注解@Configuration, 他出现的首要任务就是取代XML配置方式,不过比较遗憾的是,Spring Framework3.0还没有引入替换XML元素context:componet-scan的注解,而是选择了一个过渡方式@ImportResource。

@ImportResource允许导入遗留的XML配置文件,比如

@ImportResource("classpath:/META-INF/spring/other.xml")
@Configuration
public class SpringConfiguration{ }

并且在Spring Frameworkd提供了AnnotationConfigApplicationContext注册,用来注册@Configuration Class,通过解析Configuration类来进行装配。

在3.1版本中,引入了@ComponentScan,替换了XML元素Context:component-scan , 这个注解虽然是一个小的升级,但是对于spring 来说在注解驱动领域却是一个很大的进步,至此也体现了Spring 的无配置化支持。

Configuration配置演示

  • Configuration这个注解大家应该有用过,它是JavaConfig形式的基于Spring IOC容器的配置类使用的一种注解。因为SpringBoot本质上就是一个spring应用,所以通过这个注解来加载IOC容器的配置是很正常的。所以在启动类里面标注了@Configuration,意味着它其实也是一个IoC容器的配置类。

    举个非常简单的例子

  • 测试代码

    ConfigurationDemo
    @Configuration
    public class ConfigurationDemo {
    @Bean
    public DemoClass demoClass(){
    return new DemoClass();
    }
    }
    DemoClass
    public class DemoClass { public void say(){
    System.out.println("say: Hello Mic");
    }
    }
    ConfigurationMain
    public class ConfigurationMain { public static void main(String[] args) {
    ApplicationContext applicationContext=
    new AnnotationConfigApplicationContext
    (ConfigurationDemo.class);
    DemoClass demoClass=applicationContext.getBean(DemoClass.class);
    demoClass.say();
    }
    }

Component-scan

ComponentScan这个注解是大家接触得最多的了,相当于xml配置文件中的context:component-scan。 它的主要作用就是扫描指定路径下的标识了需要装配的类,自动装配到spring的Ioc容器中。

标识需要装配的类的形式主要是:@Component、@Repository、@Service、@Controller这类的注解标识的类。

  • 在spring-mvc这个工程中,创建一个单独的包路径,并创建一个OtherServcie。

    @Service
    public class OtherService {
    }
  • 在Controller中,注入OtherService的实例,这个时候访问这个接口,会报错,提示没有otherService这个实例。

    @RestController
    public class HelloController { @Autowired
    OtherService otherService; @GetMapping("/hello")
    public String hello(){
    System.out.println(otherService);
    return "Hello Gupaoedu";
    }
    }
  • 添加conpoment-scan注解,再次访问,错误解决。

    @ComponentScan("com.gupaoedu")

ComponentScan默认会扫描当前package下的的所有加了相关注解标识的类到IoC容器中;

Import注解

import注解是什么意思呢? 联想到xml形式下有一个<import resource/> 形式的注解,就明白它的作用了。import就是把多个分来的容器配置合并在一个配置中。在JavaConfig中所表达的意义是一样的。

  • 创建一个包,并在里面添加一个单独的configuration

    public class DefaultBean {
    }
    @Configuration
    public class SpringConfig { @Bean
    public DefaultBean defaultBean(){
    return new DefaultBean();
    }
    }
  • 此时运行测试方法,

    public class MainDemo {
    
        public static void main(String[] args) {
    ApplicationContext ac=new AnnotationConfigApplicationContext(SpringConfig.class);
    String[] defNames=ac.getBeanDefinitionNames();
    for(String name:defNames){
    System.out.println(name);
    }
    }
    }
  • 在另外一个包路径下在创建一个配置类。此时再次运行前面的测试方法,打印OtherBean实例时,这个时候会报错,提示没有该实例

    public class OtherBean {
    }
    @Configuration
    public class OtherConfig { @Bean
    public OtherBean otherBean(){
    return new OtherBean();
    }
    }
  • 修改springConfig,把另外一个配置导入过来

    @Import(OtherConfig.class)
    @Configuration
    public class SpringConfig { @Bean
    public DefaultBean defaultBean(){
    return new DefaultBean();
    }
    }
  • 再次运行测试方法,即可看到对象实例的输出。

至此,我们已经了解了Spring Framework在注解驱动时代,完全替代XML的解决方案。至此,Spring团队就此止步了吗?你们太单纯了。虽然无配置化能够减少配置的维护带来的困扰,但是,还是会存在很对第三方组建的基础配置声明。同样很繁琐,所以Spring 退出了@Enable模块驱动。这个特性的作用是把相同职责的功能组件以模块化的方式来装配,更进一步简化了Spring Bean的配置。

Enable模块驱动

我们通过spring提供的定时任务机制来实现一个定时任务的功能,分别拿演示在使用Enable注解和没使用Enable的区别。让大家感受一些Enable注解的作用。

使用EnableScheduing之前

  • 在applicationContext.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:task="http://www.springframework.org/schema/task"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
    http://www.springframework.org/schema/task
    http://www.springframework.org/schema/task/spring-task-3.2.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="com.gupaoedu.controller"/>
    <!--AnnotationDrivenBeanDefinitionParser-->
    <task:annotation-driven scheduler="scheduler"/> <!-- 定时器开关-->
    <task:scheduler id="scheduler" pool-size="5"/>
    </beans>
  • 编写任务处理类

    @Service
    public class TaskService { @Scheduled(fixedRate = 5000) //通过@Scheduled声明该方法是计划任务,使用fixedRate属性每隔固定时间执行
    public void reportCurrentTime(){
    System.out.println("每隔5秒执行一次 "+new Date());
    }
    }
  • 编写测试类

    public class TestTask {
    
        public static void main(String[] args) {
    ApplicationContext applicationContext=new FileSystemXmlApplicationContext("classpath:applicationContext.xml"); }
    }

使用EnableScheding之后

  • 创建一个配置类

    @Configuration
    @ComponentScan("com.gupaoedu.controller")
    @EnableScheduling
    public class SpringConfig {
    }
  • 创建一个service

    @Service
    public class TaskService {
    @Scheduled(fixedRate = 5000) //通过@Scheduled声明该方法是计划任务,使用fixedRate属性每隔固定时间执行
    public void reportCurrentTime(){
    System.out.println("每隔5秒执行一次 "+new Date());
    }
    }
  • 创建一个main方法

    public class TaskMain {
    public static void main(String[] args) {
    ApplicationContext context=new AnnotationConfigApplicationContext(SpringConfig.class);
    }
    }
  • 启动服务即可实现定时调度的功能。

思考使用Enable省略了哪个步骤呢?

首先我们看没使用Enable的代码,它里面会有一个

<task:annotation-driven scheduler="scheduler"/>

这个scheduler是一个注解驱动,会被AnnotationDrivenBeanDefinitionParser 这个解析器进行解析。

在parse方法中,会有如下代码的定义

 builder = BeanDefinitionBuilder.genericBeanDefinition("org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor");
builder.getRawBeanDefinition().setSource(source);

这个类是用来解析@Scheduled注解的。

ok,我们再看一下EnableScheduling注解,我们可以看到,它会自动注册一个ScheduledAnnotationBeanPostProcessor的bean。所以,通过这个例子,就是想表达Enable注解的作用,它可以帮我们省略一些第三方模块的bean的声明的配置。

public class SchedulingConfiguration {
public SchedulingConfiguration() {
} @Bean(
name = {"org.springframework.context.annotation.internalScheduledAnnotationProcessor"}
)
@Role(2)
public ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor() {
return new ScheduledAnnotationBeanPostProcessor();
}
}

Spring Framework 4.x

Spring 4.x版本,是注解的完善时代,它主要是提升条件装配能力,引入了@Conditional注解,通过自定义Condition实现配合,弥补了之前版本条件化配置的短板。

简单来说,Conditional提供了一个Bean的装载条件判断,也就是说如果这个条件不满足,那么通过@Bean声明的对象,不会被自动装载进来,具体是怎么用的呢?,先来简单带大家了解一下它的基本使用。

Conditional的概述

@Conditional是一个注解,我们观察一下这个注解的声明, 它可以接收一个Condition的数组。

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
Class<? extends Condition>[] value();
}
@FunctionalInterface
public interface Condition {
boolean matches(ConditionContext var1, AnnotatedTypeMetadata var2);
}

这个Condition是一个函数式接口,提供了一个matchers的方法,简单来说,它就是提供了一个匹配的判断规则,返回true表示可以注入bean,返回false表示不能注入。

Conditional的实战

  • 自定义个一个Condition,逻辑比较简单,如果当前操作系统是Windows,则返回true,否则返回false

    public class GpCondition implements Condition{
    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata
    annotatedTypeMetadata) {
    //此处进行条件判断,如果返回 true,表示需要加载该配置类或者 Bean
    //否则,表示不加载
    String os=conditionContext.getEnvironment().getProperty("os.name");
    if(os.contains("Windows")){
    return true;
    }
    return false;
    }
    }
  • 创建一个配置类,装载一个 BeanClass

    @Configuration
    public class ConditionConfig {
    @Bean
    @Conditional(GpCondition.class)
    public BeanClass beanClass(){
    return new BeanClass();
    }
    }
  • 在 BeanClass 的 bean 声明方法中增加@Conditional(GpCondition.class),其中具体的条件是我们自定义的 GpCondition 类。上述代码所表达的意思是,如果 GpCondition 类中的 matchs 返回 true,则将 BeanClass 装载到 Spring IoC 容器中

  • 运行测试方法

    public class ConditionMain {
    public static void main(String[] args) {
    AnnotationConfigApplicationContext context=new
    AnnotationConfigApplicationContext(ConditionConfig.class);
    BeanClass beanClass=context.getBean(BeanClass.class);
    System.out.println(beanClass);
    }
    }

总结

经过对Spring注解驱动的整体分析,不难发现,我们如今之所以能够非常方便的基于注解来完成Spring中大量的功能,得益于Spring团队不断解决用户痛点而做的各种努力。

而Spring Boot的自动装配机制,也是在Spring 注解驱动的基础上演化而来,在后续的内容中,我会专门分析Spring Boot的自动装配机制。

版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Mic带你学架构

如果本篇文章对您有帮助,还请帮忙点个关注和赞,您的坚持是我不断创作的动力。欢迎关注「跟着Mic学架构」公众号公众号获取更多技术干货!

你真的知道Spring注解驱动的前世今生吗?这篇文章让你豁然开朗!的更多相关文章

  1. Spring注解驱动开发之Ioc容器篇

    前言:现今SpringBoot.SpringCloud技术非常火热,作为Spring之上的框架,他们大量使用到了Spring的一些底层注解.原理,比如@Conditional.@Import.@Ena ...

  2. 【Spring注解驱动开发】二狗子让我给他讲讲@EnableAspectJAutoProxy注解

    写在前面 最近,二狗子入职了新公司,新入职的那几天确实有点飘.不过慢慢的,他发现他身边的人各个身怀绝技啊,有Spring源码的贡献者,有Dubbo源码的贡献者,有MyBatis源码的贡献者,还有研究A ...

  3. 【Spring注解驱动开发】使用InitializingBean和DisposableBean来管理bean的生命周期,你真的了解吗?

    写在前面 在<[Spring注解驱动开发]如何使用@Bean注解指定初始化和销毁的方法?看这一篇就够了!!>一文中,我们讲述了如何使用@Bean注解来指定bean初始化和销毁的方法.具体的 ...

  4. Spring 注解驱动(一)基本使用规则

    Spring 注解驱动(一)基本使用规则 Spring 系列目录(https://www.cnblogs.com/binarylei/p/10198698.html) 一.基本使用 @Configur ...

  5. Spring 注解驱动(二)Servlet 3.0 注解驱动在 Spring MVC 中的应用

    Spring 注解驱动(二)Servlet 3.0 注解驱动在 Spring MVC 中的应用 Spring 系列目录(https://www.cnblogs.com/binarylei/p/1019 ...

  6. 1、课程简介-Spring 注解驱动开发

    1.课程简介-Spring 注解驱动开发

  7. 0、Spring 注解驱动开发

    0.Spring注解驱动开发 0.1 简介 <Spring注解驱动开发>是一套帮助我们深入了解Spring原理机制的教程: 现今SpringBoot.SpringCloud技术非常火热,作 ...

  8. 【Spring注解驱动开发】聊聊Spring注解驱动开发那些事儿!

    写在前面 今天,面了一个工作5年的小伙伴,面试结果不理想啊!也不是我说,工作5年了,问多线程的知识:就只知道继承Thread类和实现Runnable接口!问Java集合,竟然说HashMap是线程安全 ...

  9. 【Spring注解驱动开发】组件注册-@ComponentScan-自动扫描组件&指定扫描规则

    写在前面 在实际项目中,我们更多的是使用Spring的包扫描功能对项目中的包进行扫描,凡是在指定的包或子包中的类上标注了@Repository.@Service.@Controller.@Compon ...

随机推荐

  1. bzoj4821 && luogu3707 SDOI2017相关分析(线段树,数学)

    题目大意 给定n个元素的数列,每一个元素有x和y两种元素,现在有三种操作: \(1\ L\ R\) 设\(xx\)为\([l,r]\)的元素的\(x_i\)的平均值,\(yy\)同理 求 \(\fra ...

  2. 枚举类型(enum)

    关于枚举 枚举类型是Java 5中新增特性的一部分,它是一种特殊的数据类型,之所以特殊是因为它既是一种类(class)类型却又比类类型多了些特殊的约束,但是这些约束的存在也造就了枚举类型的简洁性.安全 ...

  3. C 字符串相关的库函数

    字符串操作函数 size_t strlen( char *string ); 返回字符串长度 char* strcpy( char *dst, char const *src ); 将src复制到ds ...

  4. HCNP Routing&Switching之BGP报文结构、类型和状态

    前文我们了解了BGP的邻居建立条件.优化以及BGP认证相关话题,回顾请参考https://www.cnblogs.com/qiuhom-1874/p/15395723.html:今天我们来聊一聊BGP ...

  5. 数字IC设计工程师的知识结构

    刚毕业的时候,我年少轻狂,以为自己已经可以独当一面,庙堂之上所学已经足以应付业界需要.然而在后来的工作过程中,我认识了很多牛人,也从他们身上学到了很多,从中总结了一个IC设计工程师需要具备的知识架构, ...

  6. 小白自制Linux开发板 八. Linux音频驱动配置

    不知不觉小白自制开发板系列已经到第八篇了,本篇要配置的是音频驱动,也算是硬件部分的最后一片了,积攒的文章也差不多全放完了,后续更新可能会放缓,还请见谅. 对于F1C200s是自带了多媒体处理功能的,所 ...

  7. 全连接层dense作用

    参考来源

  8. 轻量级 Java 基础开发框架,Solon & Solon Cloud 1.5.52 发布

    Solon 已有120个生态扩展插件,此次更新主要为细节打磨: 插件 mybatis-solon-plugin 增加 mappers 单行配置支持 之前的多行模式: mybatis.db1: type ...

  9. 热身训练1 Calculator

    题目出处:Calculator 简要题意: 你有一个确定的函数,f(x)=+...*...^...,其中共有n个操作,从左到右依次计算. 共有m次询问,我们每次询问,1.会修改f(x)中的操作:2.输 ...

  10. Windows平台编译器相关的几个预定义宏

    WIN32 是在windows.h 中定义的宏,包含winodws.h则定义该宏 _WIN32/_WIN64跟windows平台有关的宏,_WIN32在windows   32位和64位下都有该宏,_ ...