乐趣区

关于java:你真的知道Spring注解驱动的前世今生吗这篇文章让你豁然开朗

本篇文章,从 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 带你学架构
如果本篇文章对您有帮忙,还请帮忙点个关注和赞,您的保持是我一直创作的能源。欢送关注同名微信公众号获取更多技术干货!

退出移动版