SpringBoot源码

Spring注解的发展

Spring1.x:

  1. 需要使用ClasspathXmlApplicationContext对象读取applicationContext.xml文件中的定义的所有<bean/>实例

  2. 因为项目所需的Bean往往较大,所以在这个阶段往往将xml配置分为多个不同的xml文件进行管理,启动指定一个配置文件,在该配置文件中使用<import />标签关联其他多个配置文件

  3. @Transaction 事务支持则是在spring-tx包下,是一个扩展注解

Spring2.x:

  1. 为了简化bean的管理,引入@Component<context:component-sacn />xml标签,component-scan 需要配置在xml中,然后容器会根据路径扫描包下的所有含有注解的类,对这些Bean进行实例化

  2. @Required 适用于bean属性setter方法,表示受影响的bean属性必须在XML配置文件在配置时进行填充。否则,容器会抛出一个BeanInitializationException异常。

  3. @Repository 继承自@Component 表示MVC持久层操作Bean

  4. @Aspect 代表切面,来自于Aspect包

  5. @Autowired 代表使用类型自动注入

  6. @Qualifier 使用类型注入时,如果有多个类型匹配,可以使用该注解指定目标bean的名称

  7. @RequestMapping 指定MVC控制器的请求方法与请求URL

Spring3.0:

  1. 在3.0版本阶段,引入了@Configuration 用于替代一个applicationContext.xml配置,其中@Bean注解修饰的方法,等同于xml中的<bean/> 标签

  2. 虽然提供了替代xml文件的注解,但是在这个阶段仍然完全使用注解替换xml配置文件,主要是因为<context:component-sacn />仍然需要xml配置扫描驱动

  3. @ImportResource 用于过渡的注解,他可以导入指定位置的xml配置文件

  4. 这个阶段是注解与xml配置混合开发的时代

Spring3.1:

  1. 在3.1版本中,提供了@ComponentScan用于替换<context:component-sacn />,他会默认扫描当前注解所修饰的包及其子包下的所有@Component

  2. 开启了注解全替换xml配置的注解黄金时代

  3. 在这个阶段,使用AnnotationConfigApplicationContext读取@Configuration配置,并装载容器

  4. 因为可能存在多个配置类,但是只有一个配置入口,故提供@Import注解,对应xml的<import />标签,用来导入多个Configuration配置。

  5. @Import共有三种方式注入Bean:

    1. @Import(UserService.class) 直接导入一个Component/Configuration

    2. @Import(MyImportSelector.class) 如果value指定的class实现了ImportSelector,他将会调用接口的selectImports()方法,得到要注入的所有bean的路径然后注入

    3. @Import(MyImportBeanDefinitionRegistrar.class) 如果value指定的class实现了ImportBeanDefinitionRegistrar接口,他将会调用该该接口的registerBeanDefinitions()方法,你可以在这个方法中向容器注入任何Bean实例

  6. @EnableXXX开关注解,它实际上就是Import注解,引入Enable注解就会注入相关的配置类:

     @Target(ElementType.TYPE)
     @Retention(RetentionPolicy.RUNTIME)
     @Documented
     @Import(AsyncConfigurationSelector.class)
     public @interface EnableAsync {
     }

Spring4.x:

  1. @Conditional 条件注解,根据注解中的条件控制该bean的注入。其条件由Condition接口的matches方法决定。

  2. @EventListener 用于创建事件监听器

  3. @AliasFor 注解别名:

    1. 在同一个注解中使用@AliasFor为注解的多个属性互相设置别名

      @Target({ElementType.TYPE, ElementType.METHOD})
      @Retention(RetentionPolicy.RUNTIME)
      @Documented
      @Mapping
      public @interface RequestMapping {
      
          @AliasFor("path")
          String[] value() default {};
      
          @AliasFor("value")
          String[] path() default {};
      
          //...
      }
    2. 将当前注解设置为其他注解属性的别名

      @Target(ElementType.TYPE)
      @Retention(RetentionPolicy.RUNTIME)
      @Documented
      @Inherited
      @SpringBootConfiguration
      @EnableAutoConfiguration
      @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
              @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
      public @interface SpringBootApplication {
      
          @AliasFor(annotation = EnableAutoConfiguration.class)
          Class<?>[] exclude() default {};
      
          @AliasFor(annotation = EnableAutoConfiguration.class)
          String[] excludeName() default {};
          
          // 设置注解的别名
          @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
          String[] scanBasePackages() default {};
      }
  4. @CrossOrigin 注解用于解决跨域问题,他可以修饰在Controller类或者方法上,被修饰的接口将会通过HTTP CORS进行跨域资源共享

Spring5.x:

  1. @Indexed 是一种组件索引,在5.x的Spring中,@Component注解会继承自该注解。然后引入spring-context-indexer这个jar,Spring会在编译期间扫描所有的Bean,并将其写入到META-INT/spring.components文件中。当Spring应用上下文执行ComponentScan扫描时,META-INT/spring.components将会被CandidateComponentsIndexLoader 读取并加载,转换为CandidateComponentsIndex对象,这样的话@ComponentScan不在扫描指定的package,而是读取CandidateComponentsIndex对象,从而达到提升启动性能的目的。

其他注解:

  1. @Nullable@NonNull,可以标注在字段、方法、方法参数上,表示对应的值可为空或者不可为空,他们在程序运行的过程中不会起任何作用,只会在IDE、编译器、FindBugs检查、生成文档的时候有做提示。(since 3.1)

  2. @NonNullApi, @NonNullFields 包级别的注解:@Target(ElementType.PACKAGE),前者表示该包下定参数和方法返回值默认不能为null, 后者表示变量不可为null:

     @NonNullApi //指示该包下,参数,方法返回值不能为null
     @NonNullFields // 指示该包下,变量不能为null
     package org.springframework.core;
     
     import org.springframework.lang.NonNullApi;
     import org.springframework.lang.NonNullFields;

SPI

全称为 Service Provider Interface,是JDK内置的一种服务提供发现机制,常用于创建可扩展、可替换组件的应用程序,是java中模块化插件化的关键。

SPI 框架包含3个基本组件:

  1. 服务接口 Service Interface

  2. 服务接口的实现类,Service Provider

  3. 服务加载器 Service Loader

Service Interface是一组定义标准的接口和类。Service Provider是服务接口的特定实现,需要实现接口,并子类化服务本身中定义的类。java.util包下的ServiceLoader类就是SPI机制的核心类,主要功能是通过相关的类加载器扫描并加载provider的jar包,并且通过反射实例化服务的实现类。服务Provider程序可以以扩展的形式安装在Java平台的实现中,也就是说,可以将provider的jar文件放置在任何常用扩展目录中,也可以通过将其jar包添加到应用程序的类路径或通过其他一些特定于平台的方式来使使用方来调用。

以JDBC为例,JDK中定义了服务接口 Driver:

public interface Driver {

    Connection connect(String url, java.util.Properties info) throws SQLException;

}

各个数据库厂商提供自己的服务接口实现:

// mysql
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    public Driver() throws SQLException {
    }

    static {
        try {
            DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {
            throw new RuntimeException("Can't register driver!");
        }
    }
}

// oracle
public class OracleDriver extends oracle.jdbc.driver.OracleDriver {
    private static final String _Copyright_2007_Oracle_All_Rights_Reserved_ = null;
    public static final boolean TRACE = false;

    public OracleDriver() {
    }

    // ... 
}

为了告知JDBC接口实现类的位置,需要在META-INF/services文件夹下建立接口名这里是java.sql.Driver文件,文件内容为接口的实现类的全限定名,以达到注册Provider的效果:

mysql(mysql-connector-java-8.0.28.jar/META-INF/services/java.sql.Driver):
com.mysql.cj.jdbc.Driver

oracle(ojdbc6-11.2.0.4.jar/META-INF/services/java.sql.Driver):
oracle.jdbc.OracleDriver

为了加载具体的实现,需要借助ServiceLoader来读取实现类,并运用反射创建实例对象:

ServiceLoader<Driver> driversLoder = ServiceLoader.load(Driver.class);
for (Driver pusher : driversLoder) {
    // ....
}

最后更新于