你真的知道Spring注解驅(qū)動的前世今生嗎?這篇文章讓你豁然開朗!

圖怪獸_40b0b39fdbb3d29e40b4b52282058329_23747

本篇文章,從Spring1.x到Spring 5.x的迭代中,站在現(xiàn)在的角度去思考Spring注解驅(qū)動的發(fā)展過程,這將有助于我們更好的理解Spring中的注解設計。

Spring Framework 1.x

在SpringFramework1.x時代,其中在1.2.0是這個時代的分水嶺,當時Java5剛剛發(fā)布,業(yè)界正興起了使用Annotation的技術(shù)風,Spring Framework自然也提供了支持,比如當時已經(jīng)支持了@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配置驅(qū)動,比如<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是一個里程碑式的時代,他的功能特性開始出現(xiàn)了非常大的擴展,比如全面擁抱Java5、以及Spring Annotation。更重要的是,它提供了配置類注解@Configuration, 他出現(xiàn)的首要任務就是取代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 來說在注解驅(qū)動領域卻是一個很大的進步,至此也體現(xiàn)了Spring 的無配置化支持。

Configuration配置演示

  • Configuration這個注解大家應該有用過,它是JavaConfig形式的基于Spring IOC容器的配置類使用的一種注解。因為SpringBoot本質(zhì)上就是一個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這個工程中,創(chuàng)建一個單獨的包路徑,并創(chuàng)建一個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注解是什么意思呢? 聯(lián)想到xml形式下有一個<import resource/> 形式的注解,就明白它的作用了。import就是把多個分來的容器配置合并在一個配置中。在JavaConfig中所表達的意義是一樣的。

  • 創(chuàng)建一個包,并在里面添加一個單獨的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);
            }
        }
    }
    
  • 在另外一個包路徑下在創(chuàng)建一個配置類。此時再次運行前面的測試方法,打印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();
        }
    }
    
  • 再次運行測試方法,即可看到對象實例的輸出。

至此,我們已經(jīng)了解了Spring Framework在注解驅(qū)動時代,完全替代XML的解決方案。至此,Spring團隊就此止步了嗎?你們太單純了。雖然無配置化能夠減少配置的維護帶來的困擾,但是,還是會存在很對第三方組建的基礎配置聲明。同樣很繁瑣,所以Spring 退出了@Enable模塊驅(qū)動。這個特性的作用是把相同職責的功能組件以模塊化的方式來裝配,更進一步簡化了Spring Bean的配置。

Enable模塊驅(qū)動

我們通過spring提供的定時任務機制來實現(xiàn)一個定時任務的功能,分別拿演示在使用Enable注解和沒使用Enable的區(qū)別。讓大家感受一些Enable注解的作用。

使用EnableScheduing之前

  • 在applicationContext.xml中添加定時調(diào)度的配置

    <?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屬性每隔固定時間執(zhí)行
        public void reportCurrentTime(){
            System.out.println("每隔5秒執(zhí)行一次 "+new Date());
        }
    }
    
  • 編寫測試類

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

使用EnableScheding之后

  • 創(chuàng)建一個配置類

    @Configuration
    @ComponentScan("com.gupaoedu.controller")
    @EnableScheduling
    public class SpringConfig {
    }
    
  • 創(chuàng)建一個service

    @Service
    public class TaskService {
        @Scheduled(fixedRate = 5000) //通過@Scheduled聲明該方法是計劃任務,使用fixedRate屬性每隔固定時間執(zhí)行
        public void reportCurrentTime(){
            System.out.println("每隔5秒執(zhí)行一次 "+new Date());
        }
    }
    
  • 創(chuàng)建一個main方法

    public class TaskMain {
        public static void main(String[] args) {
            ApplicationContext context=new AnnotationConfigApplicationContext(SpringConfig.class);
        }
    }
    
  • 啟動服務即可實現(xiàn)定時調(diào)度的功能。

思考使用Enable省略了哪個步驟呢?

首先我們看沒使用Enable的代碼,它里面會有一個

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

這個scheduler是一個注解驅(qū)動,會被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實現(xiàn)配合,彌補了之前版本條件化配置的短板。

簡單來說,Conditional提供了一個Bean的裝載條件判斷,也就是說如果這個條件不滿足,那么通過@Bean聲明的對象,不會被自動裝載進來,具體是怎么用的呢?,先來簡單帶大家了解一下它的基本使用。

Conditional的概述

@Conditional是一個注解,我們觀察一下這個注解的聲明, 它可以接收一個Condition的數(shù)組。

@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是一個函數(shù)式接口,提供了一個matchers的方法,簡單來說,它就是提供了一個匹配的判斷規(guī)則,返回true表示可以注入bean,返回false表示不能注入。

Conditional的實戰(zhàn)

  • 自定義個一個Condition,邏輯比較簡單,如果當前操作系統(tǒng)是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;
        }
    }
    
  • 創(chuàng)建一個配置類,裝載一個 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);
        }
    }
    

總結(jié)

經(jīng)過對Spring注解驅(qū)動的整體分析,不難發(fā)現(xiàn),我們?nèi)缃裰阅軌蚍浅7奖愕幕谧⒔鈦硗瓿蒘pring中大量的功能,得益于Spring團隊不斷解決用戶痛點而做的各種努力。
而Spring Boot的自動裝配機制,也是在Spring 注解驅(qū)動的基礎上演化而來,在后續(xù)的內(nèi)容中,我會專門分析Spring Boot的自動裝配機制。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,882評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,208評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,746評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,666評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,477評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,960評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,047評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,200評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,726評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,617評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,807評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,327評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,049評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,425評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,674評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,432評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,769評論 2 372

推薦閱讀更多精彩內(nèi)容