【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[] 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的源码比较复杂,分支旁路众多,建议读者自己学会去摸索。

你可能感兴趣的:(spring源码解析)