前言
由於目前網上比較的文章都是基於xml,完全基於註解開發,或者是基於註解源碼分析比較少。所以決定寫一寫spring基於註解驅動開發的使用,以及實現原理分析。
一、Spring的註解有哪些?功能是什麼?
spring提供的註解有很多,常用的有:@Component、@Configuration&@Bean、@ComponentScan、@ComponentScans、@Repository、@Service、@Controller 、@Scope、@Lazy 、@Conditional、@Import、@DependsOn、 @PostConstruct、@PreDestroy、@Value、@Resouce和@Inject、@ProtertySource等等,下面我按功能來逐個詳細介紹。
1.組件註冊
@Configuration&@Bean
@Configuration:標明這個類是一個配置類。組合註解,組合了@Component。所以本身也是一個bean。
@Bean:在@Configuration註解的類裏面,作用於方法。value和name一樣 數組,即bean的id,默認是方法名 。initMethod destroyMethod 指定方法。如果修飾的是帶參數的方法,參數名即爲bean的id,如果只有一個 就會按類型匹配。這個參數也是Bean的依賴注入。
@ComponentScan、@ComponentScans
@ComponentScan:該註解必須和@Configuration配合使用。掃描組件。裏面的比較參數:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
@AliasFor("basePackages")
String[] value() default {}; //被掃描包,可以配置多個
@AliasFor("value")
String[] basePackages() default {};//被掃描包,可以配置多個
Class<?>[] basePackageClasses() default {};//包含當前類,以及該類的包和子包,可以配置多個
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;//BeanName生成器
Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;
ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;//scope代理
String resourcePattern() default "**/*.class";//默認匹配class資源
boolean useDefaultFilters() default true;//使用默認過濾器,即掃描@Component註解,其中@Service、@Controller、@Configuration、@repository都組合了@Component註解。
ComponentScan.Filter[] includeFilters() default {};//如果配置這個,默認就自動失效
ComponentScan.Filter[] excludeFilters() default {};//排除
boolean lazyInit() default false;//是否掃描到的組件需要懶加載
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface Filter {
FilterType type() default FilterType.ANNOTATION;//按註解類型排除,和Classes配合使用
@AliasFor("classes")
Class<?>[] value() default {};
@AliasFor("value")
Class<?>[] classes() default {};
String[] pattern() default {};//如果類型爲正則的話,使用這個參數
}
}
@Repository、@Service、@Controller
這三個註解等同於@Component,沒有什麼其他意思,標明是一個組件。源碼都是組合了@Component
@Scope、@Lazy 、@DependsOn
@Scope:默認註冊的組件是單例,也可以設置爲原型模式。
@Lazy :默認註冊的組件是false,可以使用該註解標明懶加載。即使用的時候再初始化。
@DependsOn:作用在@Componet,在注入Bean之前,先注入被依賴的Bean。
@Import
該註解也必須和@Configuration組合使用。我們常用的@Enable*註解就是使用了@Import註解來實現的。
他的屬性可以配置多個類,這個類可以實現ImportSelector、ImportBeanDefinitionRegistrar。
- 普通類:自動註冊這個組件,id默認是全類名
- ImportSelector:返回需要導入的組件的全類名數組
- ImportBeanDefinitionRegistrar:手動註冊bean到容器
@Conditional、@Profile
@Conditional:符合條件的纔會被註冊,他的參數是一個class ,必須繼承Condition接口。裏面有個方法,返回true就會被註冊。boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
Class<? extends Condition>[] value(); 參數只有一個,必須要繼承Condition。該接口只有一個方法
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
可以結合@Bean @Component使用
@Profile:指定一個環境,如何是激活的環境值一樣,則被加載,底層實現原理就是 @Conditional。
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional({ProfileCondition.class})//實現原理
public @interface Profile {
String[] value();
}
class ProfileCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
if (context.getEnvironment() != null) {
MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
if (attrs != null) {
for (Object value : attrs.get("value")) {//與激活的環境值比較
if (context.getEnvironment().acceptsProfiles(((String[]) value))) {
return true;
}
}
return false;
}
}
return true;
}
}
@Value、@ProtertySource
@Value取值方式:
- 1.基本數值 @Value(“張三”)
- 2.#{} SpEL表達式
- 3.${} 取出配置文件中的值(在運行環境變量裏面的值)
@ProtertySource(“classpath:/com/myco/app.properties”)
用來指定資源的位子,加載到環境裏面
Indicate the resource location(s) of the properties file to be loaded.
* For example, {@code “classpath:/com/myco/app.properties”} or
* {@code “file:/path/to/file”}.
@Autowired、@Resouce和@Inject
1)@Autowired 自動注入
1)默認按照類型去容器中找對應得組件 applicationContext.getBean(Book.class)
2)如果找到多個相同類型的組件,再按照屬性名作爲id去查找
3)使用@Qualifier(“book”)可以限定bean的id,不適用屬性名
4)自動裝配 默認 require=true 找不到會報錯
5)@Primary :在自動裝配的時候默認使用主要的組件,
也可以繼續使用@Qualifier限定組件的id
2)@Resouce和@Inject
@Resource:默認是按照屬性名稱注入 不支持@Primary 不支持require=false
@Inject:需要導包javax.inject 不支持require=false
@Autowired :spring定義的 功能最強
@Resource/@Inject:Java規範,不建議使用
除了作用在屬性上,還可以作用在方法上,參數上。效果都是一樣的。
實現原理:AutowiredAnnotationBeanPostProcessor.class
2、Spring AOP註解
註解編寫AOP的三步:
1)將業務邏輯組件和切面類都加入到容器中;告訴Spring哪個是切面類 @Aspect
2)在切面類上的每一個通知方法上標註通知註解,告訴Spring何時何地運行(編寫切點表達式)
3)開啓基於註解的aop模式;@EnableAspectJAutoProxy
實戰演示:
第一步:導入jar包
<!--Spring aop 源碼內置了aopalliance-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.0.6.RELEASE</version>
</dependency>
<!--aspectj:使用註解開發,會使用到aspectj的註解-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.1</version>
</dependency>
第二步:編寫通知
@Aspect
@Component
public class LogAspect {
/*
* @Before:前置通知。value參數定義切點
*
* @AfterReturning:目標方法執行結束後執行。參數:value:切點 returning:方法返回值
*
* @AfterThrowing:在目標方法拋出異常後執行。參數:throwing:被拋出的異常綁定
*
* @After:Final增強,不管是拋出異常還是正常退出,都會執行。可以看做是
*
* @Around:環繞通知,手動執行目標方法。參數:value:切點
*/
@Pointcut(value = "execution(public void com.xinchao.test.*(..))")
public void pointCut(){}
@Around("pointCut()")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("start");
proceedingJoinPoint.proceed();//異常直接拋,調用目標方法
System.out.println("end");
}
}
第三步:@EnableAspectJAutoProxy 開啓AOP
3、聲明式事務
1)@EnableTransactionManagement 開啓基於註解的事務管理器
2)給方法上標註@Transactional 表示當前方法是一個事務方法
3)配置事務管理器來控制事務
總結:
Spring提供了很多註解,讓我們不再使用XML配置,註解配置也是大勢所趨,目前我們用到的項目幾乎都沒有xml配置了。所以掌握這些註解對以後的開發大有裨益。
後續我也會詳解每個註解的解析,Spring是如何啓動並加載這些Bean。AOP又是如何開啓的等。
Spring的源碼比較複雜,分支旁路衆多,建議讀者自己學會去摸索。