【Spring源碼解析】-- 基於註解驅動開發(二)

前言

由於目前網上比較的文章都是基於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的源碼比較複雜,分支旁路衆多,建議讀者自己學會去摸索。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章