本篇文章,從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的自動裝配機制。