本篇文章,從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帶你學架構
!
如果本篇文章對您有幫助,還請幫忙點個關注和贊,您的堅持是我不斷創作的動力。歡迎關注同名微信公眾號獲取更多技術乾貨!