springboot源码(一)


1.先来谈谈spring的发展史
  • spring的注解发展史
    • 2004--》spring1.0诞生,完全基于xml的形式,此时只有一个注解@Transcation
    • 2006--》spring2.0诞生,新增了很多重要的注解 eg:@Controller @Service @Repository @Component @RequestMapping @Autowired @Required @Aspect等 此时虽然可以简化xml配置,但是不能完全脱离xml。
    • 2009--》spring3.0诞生,新增了@import @ComponentScan , @ComponentScan这个注解可以取代之前的标签 ,真正的可以脱离xml。
    • 2013--》spring4.0诞生,新增了@Conditional ,这个注解实现了可以根据条件来决定是否加载类。
    • 2017--》spirng5.0诞生,新增了@Indexed,随着使用@ComponentScan的增多,要检索的配置越来越多,速度慢,所以这个注解可以把所有的Component标志的类放到一个文件中,提高效率。
  2.spring常用的核心注解
  • 首先了解一个前置知识,java的4种元注解 (具体参考博客:https://blog.csdn.net/weixin_29010003/article/details/114768402)
    • @Target --》表示该注解可以被用到什么地方
    • @Retention --》表示该注解保留的时间段
    • @Documented --》表示可以被javadoc工具提取成文档
    • @Inherited --》表示可以被子类继承
 
  • @Autowired、@Component、@Controller、@Service、@Repository、@RequestMapping
@Autowired 自动注入
@Component 声明组件
@Controller 声明控制层组件
@Service 声明服务层组件
@Repository 声明持久层组件
@RequestMapping 声明请求对应的处理方法
这些注解的意义在于可以不用再xml文件中声明标签,直接就可以在类上加入响应的注解,就可以纳入springIOC容器管理。简化了配置和维护工作。 只需要在xml中添加扫描的路径就ok了。 xml中仍需配置: ,没有完全摆脱xml。  
  • @Import 可以快速的把类加到springIOC容器中 类似的注解有@Bean @Import可以用于引入第三方包 (springboot自动装配会用到)
    • 直接引入
      • 优点:简单,直接。
      • 缺点:如果需要导入很多包,不灵活。

@Service
public class Sleep{ 
}

@Configuration
@Import(value={Sleep.class})
public class PersonConfig{
    public static void main(String[] args) {
        ApplicationContext ac = new AnnotationConfigApplicationContext(JavaConfig.class);
        for (String beanDefinitionName : ac.getBeanDefinitionNames()) {
            System.out.println("beanDefinitionName = " + beanDefinitionName);
        }
    }
}
  • 实现ImportSelector

    • 把需要添加到IOC容器的对象对应的全类路径加到字符串数组中,可以根据不同的业务需求添加不同的类型,更加灵活。
@Service
public class Sleep{}
@Service
public class Play{}

public class MyImportSelector implements ImportSelect{
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{Sleep.class.getName(),Play.class.getName()};  //将需要添加进来的bean,放到String[]中
    }
}

@Configuration
@Import(value={MyImportSelector.class})
public class PersonConfig{
    public static void main(String[] args) {
        ApplicationContext ac = new AnnotationConfigApplicationContext(JavaConfig.class);
        for (String beanDefinitionName : ac.getBeanDefinitionNames()) {
            System.out.println("beanDefinitionName = " + beanDefinitionName);
        }
    }
}
    • 实现ImportBeanDefinitionRegistrar
      • 在方法中提供了BeanDefinitionRegistry,自己在方法中实现注册。

@Service
public class Sleep{}
@Service
public class Play{}

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar{
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        // 将需要注册的对象封装为 RootBeanDefinition 对象
        RootBeanDefinition cache = new RootBeanDefinition(Sleep.class);
        registry.registerBeanDefinition("cache",cache);

        RootBeanDefinition logger = new RootBeanDefinition(Play.class);
        registry.registerBeanDefinition("logger",logger);
    }
}

@Configuration
@Import(value={MyImportBeanDefinitionRegistrar.class})
public class PersonConfig{
    public static void main(String[] args) {
        ApplicationContext ac = new AnnotationConfigApplicationContext(JavaConfig.class);
        for (String beanDefinitionName : ac.getBeanDefinitionNames()) {
            System.out.println("beanDefinitionName = " + beanDefinitionName);
        }
    }
}
  • @ComponentScan
    • 取代了
    • 默认扫描当前包以其子包下的类
package com.syc.demo
@Service
public class Demo{}

package com.syc.test
@Configuration
@ComponentScan(value={"com.syc.demo"})
public class PersonConfig{
    public static void main(String[] args) {
        ApplicationContext ac = new AnnotationConfigApplicationContext(JavaConfig.class);
        System.out.println("ac.getBean(Demo.class) = " + ac.getBean(Demo.class));
    }
}
package com.syc.test
@Service
public class Sleep{}

package com.syc.test.lala
public class Play{}

package com.syc.test
@Configuration
@ComponentScan
public class PersonConfig{
    public static void main(String[] args) {
        ApplicationContext ac = new AnnotationConfigApplicationContext(JavaConfig.class);
        System.out.println("ac.getBean(Play.class) = " + ac.getBean(Play.class));
        System.out.println("ac.getBean(Sleep.class) = " + ac.getBean(Sleep.class));
    }
}
  • @EnableXXXX
    • 系统中有很多定义好的功能独立的模块,与@Import 一起使用开启该模块
    • 自定义@EnableXXXX

@Configuration
public class PersonConfig{
    public Person person(){
        return new Person();
    }
}

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


@Configuration
@EnableCreatePerson
public class Test{
    public static void main(String[] args) {
        ApplicationContext ac = new AnnotationConfigApplicationContext(JavaMian.class);
        Person gouren = ac.getBean("person", Person.class);
        System.out.println("hello = " + gouren.getName());
    }
}
  • @Conditional (springboot自动装配会用到)
    • 按照条件给容器注册bean实例
Conditional源码:

// 该注解可以在 类和方法中使用
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {

    /**
     * 注解中添加的类型必须是 实现了 Condition 接口的类型
     */
    Class<? extends Condition>[] value();

}

 例子:

/**
 * 定义一个 Condition 接口的类,实现matches方法
 * 返回true注入bean,返回false不注入
 */
public class MyCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        return false; // 默认返回false
    }
}


@Configuration
public class JavaConfig {
    @Bean
    // 条件注解,添加的类型必须是 实现了 Condition 接口的类型
    // MyCondition的 matches 方法返回true 则注入,返回false 则不注入
    @Conditional(MyCondition.class)
    public StudentService studentService(){
        return new StudentService();
    }

    public static void main(String[] args) {
        ApplicationContext ac = new AnnotationConfigApplicationContext(JavaConfig.class);
        for (String beanDefinitionName : ac.getBeanDefinitionNames()) {
            System.out.println("beanDefinitionName = " + beanDefinitionName);
        }
    }
}

输出结果中没有StudentService

如果把MyConditional中的matches方法中的结果返回为true
输出结果中有StudentService
  • @Indexed
    • 为什么要引入@Indexed注解?
      • 在Springboot应用场景中,大量使用@ComponentScan扫描,使得Spring模式的注解解析时间变得很长,因此引入@Indexed,为Spring模式注解添加索引。
    • 引入@Indexed之后会怎样?
      • 在编译的过程中会自动生成META-INT/spring.components文件,当Spring执行ComponentScan扫描时,META-INT/spring.components文件将会被CadidateComponentsIndexLoader读取并加载,转换为CadidateComponentsIndex对象,这样就不需要在扫描@ComponentScan指定的package,而是直接从CadidateComponentsIndex对象中读取,从而提升性能。

3.什么是SPI?
  • Springboot自动装配机制中有用到了SPI,所以了解一下SPI对后面学习Springboot源码很有帮助。
  • SPI就是Service Provider Interface,是一种服务发现机制。
    • 通过在指定的路径中查找文件,自动加载文件里所定义的类。
  • 举例说明
    • JDBC
有Mysql、也有Oracle,各自的实现不同,我只需要提供一个接口,实现由Mysql或者Oracle来帮我实现,然后把实现的类的全路径名放到META-INF.services下,文件名为我提供的接口的全路径名的文件里。

创建一个A工程,先自定义一个公共接口
package com.syc.spi
public interface BaseData{
    public void baseURL();
}
打成一个jar包
mysql引入jar包,实现BaseData接口里的baseURL方法

创建一个mysql工程
package com.syc.spi.mysql
public class MysqlData implements BaseData{
    public void baseURL(){
        System.out.println("mysql.......");
    }
}

在MysqlData这个工程中的resources目录中创建META-INF.services/com.syc.spi.BaseData文件,文件中写入com.syc.spi.mysql.MysqlData
oracle引入jar包,实现BaseData接口里的baseURL方法

创建一个oracle工程
package com.syc.spi.oracle
public class OracleData implements BaseData{
    public void baseURL(){
        System.out.println("oracle.......");
    }
}

在OracleData这个工程中的resources目录中创建META-INF.services/com.syc.spi.BaseData文件,文件中写入com.syc.spi.oracle.OracleData

 A工程测试:

public static void main(String[] args) {
        ServiceLoader providers = ServiceLoader.load(BaseData.class);
        Iterator iterator = providers.iterator();
        while(iterator.hasNext()){
            BaseData next = iterator.next();
            next.baseURL();
        }
    }
    
    如果引入的依赖是mysql的,执行结果为mysql......
    如果引入的依赖是oracle的,执行结果为oracle......

 ServiceLoader源码:

首先看下ServiceLoader类的结构
    // 配置文件的路径
    private static final String PREFIX = "META-INF/services/";

    // 加载的服务  类或者接口
    private final Class service;

    // 类加载器
    private final ClassLoader loader;

    // 访问权限的上下文对象
    private final AccessControlContext acc;

    // 保存已经加载的服务类
    private LinkedHashMap providers = new LinkedHashMap<>();

    // 内部类,真正加载服务类
    private LazyIterator lookupIterator;
load ??load方法创建了一些属性,重要的是实例化了内部类,LazyIterator。

public final class ServiceLoader implements Iterable
    private ServiceLoader(Class svc, ClassLoader cl) {
        //要加载的接口
        service = Objects.requireNonNull(svc, "Service interface cannot be null");
        //类加载器
        loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
        //访问控制器
        acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
         reload();
        
    }
    public void reload() {
        //先清空
        providers.clear();
        //实例化内部类 
        LazyIterator lookupIterator = new LazyIterator(service, loader);
    }
}
查找实现类和创建实现类的过程,都在LazyIterator完成。当我们调用iterator.hasNext和iterator.next方法的时候,实际上调用的都是LazyIterator的相应方法。

private class LazyIterator implements Iterator{
    Class service;
    ClassLoader loader;
    Enumeration configs = null;
    Iterator pending = null;
    String nextName = null; 
    private boolean hasNextService() {
        //第二次调用的时候,已经解析完成了,直接返回
        if (nextName != null) {
            return true;
        }
        if (configs == null) {
            //META-INF/services/ 加上接口的全限定类名,就是文件服务类的文件
            //META-INF/services/com.viewscenes.netsupervisor.spi.SPIService
            String fullName = PREFIX + service.getName();
            //将文件路径转成URL对象
            configs = loader.getResources(fullName);
        }
        while ((pending == null) || !pending.hasNext()) {
            //解析URL文件对象,读取内容,最后返回
            pending = parse(service, configs.nextElement());
        }
        //拿到第一个实现类的类名
        nextName = pending.next();
        return true;
    }
}
创建实例对象,当然,调用next方法的时候,实际调用到的是,lookupIterator.nextService。它通过反射的方式,创建实现类的实例并返回。

private class LazyIterator implements Iterator{
    private S nextService() {
        //全限定类名
        String cn = nextName;
        nextName = null;
        //创建类的Class对象
        Class<?> c = Class.forName(cn, false, loader);
        //通过newInstance实例化
        S p = service.cast(c.newInstance());
        //放入集合,返回实例
        providers.put(cn, p);
        return p; 
    }
}
看到这儿,我想已经很清楚了。获取到类的实例,我们自然就可以对它为所欲为了!