本篇文章,从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"/>
  • 增加注解申明

    @Servicepublic 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")@Configurationpublic 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@Configurationpublic class ConfigurationDemo {    @Bean    public DemoClass demoClass(){        return new DemoClass();    }}DemoClasspublic class DemoClass {    public void say(){        System.out.println("say: Hello Mic");    }}ConfigurationMainpublic 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。

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

    @RestControllerpublic 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 {}@Configurationpublic 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 {}@Configurationpublic class OtherConfig {    @Bean    public OtherBean otherBean(){        return new OtherBean();    }}
  • 批改springConfig,把另外一个配置导入过去

    @Import(OtherConfig.class)@Configurationpublic 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>
  • 编写工作解决类

    @Servicepublic 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")@EnableSchedulingpublic class SpringConfig {}
  • 创立一个service

    @Servicepublic 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)@Documentedpublic @interface Conditional {    Class<? extends Condition>[] value();}
@FunctionalInterfacepublic 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

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