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这个注解可以取代之前的<context:component-scan>标签 ,真正的可以脱离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文件中声明<bean>标签,直接就可以在类上加入响应的注解,就可以纳入springIOC容器管理。简化了配置和维护工作。
只需要在xml中添加扫描的路径就ok了。
xml中仍需配置:<context:component-scan base-package="com.xxx.xxx"/> ,没有完全摆脱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
- 取代了<context:component-scan base-package="com.xxx.xxx"/>
- 默认扫描当前包以其子包下的类
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<BaseData> providers = ServiceLoader.load(BaseData.class); Iterator<BaseData> 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<S> service; // 类加载器 private final ClassLoader loader; // 访问权限的上下文对象 private final AccessControlContext acc; // 保存已经加载的服务类 private LinkedHashMap<String,S> providers = new LinkedHashMap<>(); // 内部类,真正加载服务类 private LazyIterator lookupIterator;
load
load方法创建了一些属性,重要的是实例化了内部类,LazyIterator。
public final class ServiceLoader<S> implements Iterable<S> private ServiceLoader(Class<S> 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<S>{ Class<S> service; ClassLoader loader; Enumeration<URL> configs = null; Iterator<String> 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<S>{ 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; } }
看到这儿,我想已经很清楚了。获取到类的实例,我们自然就可以对它为所欲为了!
萝莉身,御姐心。。。。。