做的网站打开慢,自己什么建设网站,网站建设大熊猫点搜,app大全SpringBoot核心前置内容
1.Spring注解编程的发展过程 1.1 Spring 1.x
2004年3月24日#xff0c;Spring1.0 正式发布#xff0c;提供了IoC#xff0c;AOP及XML配置的方式。
在Spring1.x版本中提供的是纯XML配置的方式#xff0c;也就是在该版本中必须要提供xml的配置文件…SpringBoot核心前置内容
1.Spring注解编程的发展过程 1.1 Spring 1.x
2004年3月24日Spring1.0 正式发布提供了IoCAOP及XML配置的方式。
在Spring1.x版本中提供的是纯XML配置的方式也就是在该版本中必须要提供xml的配置文件在该文件中通过 bean 标签来配置需要被IoC容器管理的Bean。
?xml version1.0 encodingUTF-8?
beans xmlnshttp://www.springframework.org/schema/beansxmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdbean classcom.bobo.demo01.UserService /
/beans调试代码
public static void main(String[] args) {ApplicationContext ac new FileSystemXmlApplicationContext(classpath:applicationContext01.xml);System.out.println(ac.getBean(UserService.class) ac.getBean(UserService.class));
}输出结果 在Spring1.2版本的时候提供了Transaction (org.springframework.transaction.annotation )注解。简化了事务的操作. 1.2 Spring 2.x
在2006年10月3日 Spring2.0问世了在2.x版本中比较重要的特点是增加了很多注解
Spring 2.5之前
在2.5版本之前新增的有 Required Repository Aspect,同时也扩展了XML的配置能力提供了第三方的扩展标签比如 dubbo
Required
如果你在某个java类的某个set方法上使用了该注释那么该set方法对应的属性在xml配置文件中必须被设置否则就会报错
public class UserService {private String userName;public String getUserName() {return userName;}Requiredpublic void setUserName(String userName) {this.userName userName;}
}如果在xml文件中不设置对应的属性就会给出错误的提示。 设置好属性后就没有了错误提示了 源码中可以看到 Required从2.0开始提供 Repository
Repository 对应数据访问层Bean.这个注解在Spring2.0版本就提供的有哦大家可能没有想到。 Aspect
Aspect是AOP相关的一个注解用来标识配置类。
Spring2.5 之后
在2007年11月19日Spring更新到了2.5版本新增了很多常用注解大大的简化配置操作。
注解说明Autowired依赖注入Qualifier配置Autowired注解使用Component声明组件Service声明业务层组件Controller声明控制层组件RequestMapping声明请求对应的处理方法
在这些注解的作用下可以不用在xml文件中去注册没有bean这时只需要指定扫码路径然后在对应的Bean头部添加相关的注解即可这大大的简化了配置及维护工作。案例如下
在配置文件中只需要配置扫码路径即可
?xml version1.0 encodingUTF-8?
beans xmlnshttp://www.springframework.org/schema/beansxmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexmlns:contexthttp://www.springframework.org/schema/contextxsi:schemaLocationhttp://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsdcontext:component-scan base-packagecom.bobo //beans持久层代码
Repository
public class UserDao {public void query(){System.out.println(dao query ... );}
}业务逻辑层代码
Service
public class UserService {Autowiredprivate UserDao dao;public void query(){dao.query();}
}控制层代码
Controller
public class UserController {Autowiredprivate UserService service;public void query(){service.query();}
}测试代码
public class Demo02Main {public static void main(String[] args) {ApplicationContext ac new ClassPathXmlApplicationContext(applicationContext02.xml);UserController acBean ac.getBean(UserController.class);acBean.query();}
}虽然在Spring的2.5版本提供了很多的注解也大大的简化了开发但是任然没有摆脱XML配置驱动。
1.3 Spring 3.x
在2009年12月16日发布了Spring3.0版本这是一个注解编程发展的里程碑版本在该版本中全面拥抱Java5。提供了 Configuration注解目的就是去xml化。同时通过 ImportResource来实现Java配置类和XML配置的混合使用来实现平稳过渡。
/*** Configuration 标注的Java类 相当于 application.xml 配置文件*/
Configuration
public class JavaConfig {/*** Bean 注解 标注的方法就相当于 bean/bean 标签也是 Spring3.0 提供的注解* return*/Beanpublic UserService userService(){return new UserService();}
}在Spring3.1 版之前配置扫描路径还只能在 XML 配置文件中通过 component-scan 标签来实现在3.1之前还不能够完全实现去XML配置在3.1 版本到来的时候提供了一个 ComponentScan注解该注解的作用是替换掉 component-scan标签是注解编程很大的进步也是Spring实现无配置话的坚实基础。
ComponentScan
ComponentScan的作用是指定扫码路径用来替代在XML中的 component-scan标签默认的扫码路径是当前注解标注的类所在的包及其子包。
定义UserService
Service
public class UserService {
}创建对于的Java配置类
Configuration
ComponentScan
public class JavaConfig {public static void main(String[] args) {ApplicationContext ac new AnnotationConfigApplicationContext(JavaConfig.class);System.out.println(ac.getBean(UserService.class) ac.getBean(UserService.class));}
}输出的结果 当然也可以指定特定的扫描路径
Configuration
// 指定特定的扫描路径
ComponentScan(value {com.bobo.demo04})
public class JavaConfig {public static void main(String[] args) {ApplicationContext ac new AnnotationConfigApplicationContext(JavaConfig.class);System.out.println(ac.getBean(UserService.class) ac.getBean(UserService.class));}
}Import
Import注解只能用在类上作用是快速的将实例导入到Spring的IoC容器中将实例导入到IoC容器中的方式有很多种比如 Bean注解,Import注解可以用于导入第三方包。具体的使用方式有三种。 用在类上也能将类导入spring容器
静态导入
静态导入的方式是直接将需要导入到IoC容器中的对象类型直接添加进去即可。 这种方式的好处是简单直接但是缺点是如果要导入的比较多则不太方便而且也不灵活。
ImportSelector
Import注解中也可以添加一个实现了 ImportSelector接口的类型这时不会将该类型导入IOC容器中而是会调用 ImportSelector接口中定义的 selectImports方法将该方法的返回的字符串数组的类型添加到容器中。
定义两个业务类
public class Cache {
}
public class Logger {
}定义ImportSelector接口的实现,方法返回的是需要添加到ICC容器中的对象对应的类型的全类路径的字符串数组可以根据不同的业务需求而导入不同的类型会更加的灵活些。
public class MyImportSelector implements ImportSelector {Overridepublic String[] selectImports(AnnotationMetadata importingClassMetadata) {return new String[]{Logger.class.getName(),Cache.class.getName()};}
}导入测试案例
Configuration
Import(MyImportSelector.class)
public class JavaConfig {public static void main(String[] args) {ApplicationContext ac new AnnotationConfigApplicationContext(JavaConfig.class);for (String beanDefinitionName : ac.getBeanDefinitionNames()) {System.out.println(beanDefinitionName beanDefinitionName);}}
}输出结果 ImportBeanDefinitionRegistrar
除了上面所介绍的ImportSelector方式灵活导入以外还提供了 ImportBeanDefinitionRegistrar 接口,也可以实现相比 ImportSelector 接口的方式,ImportBeanDefinitionRegistrar 的方式是直接在定义的方法中提供了 BeanDefinitionRegistry ,自己在方法中实现注册。
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {// 将需要注册的对象封装为 RootBeanDefinition 对象RootBeanDefinition cache new RootBeanDefinition(Cache.class);registry.registerBeanDefinition(cache,cache);RootBeanDefinition logger new RootBeanDefinition(Logger.class);registry.registerBeanDefinition(logger,logger);}
}测试代码
Configuration
Import(MyImportBeanDefinitionRegistrar.class)
public class JavaConfig {public static void main(String[] args) {ApplicationContext ac new AnnotationConfigApplicationContext(JavaConfig.class);for (String beanDefinitionName : ac.getBeanDefinitionNames()) {System.out.println(beanDefinitionName beanDefinitionName);}}
}输出结果 EnableXXX
Enable模块驱动其实是在系统中先开发好各个功能独立的模块比如 Web MVC 模块 AspectJ代理模块Caching模块等。 案例说明先定义好功能模块
/*** 定义一个Java配置类*/
Configuration
public class HelloWorldConfiguration {Beanpublic String helloWorld(){return Hello World;}
}然后定义Enable注解
/*** 定义Enable注解* 在该注解中通过 Import 注解导入自定义的模块使之生效。*/
Target(ElementType.TYPE)
Retention(RetentionPolicy.RUNTIME)
Documented
Import(HelloWorldConfiguration.class)
public interface EnableHelloWorld {
}测试代码
Configuration
// 加载 自定义 模块
EnableHelloWorld
public class JavaMian {public static void main(String[] args) {ApplicationContext ac new AnnotationConfigApplicationContext(JavaMian.class);String helloWorld ac.getBean(helloWorld, String.class);System.out.println(helloWorld helloWorld);}
}效果 1.4 Spring 4.x
2013年11月1 日更新的Spring 4.0 完全支持Java8.这是一个注解完善的时代提供的核心注解是Conditional条件注解。Conditional 注解的作用是按照一定的条件进行判断满足条件就给容器注册Bean实例。
Conditional的定义为
// 该注解可以在 类和方法中使用
Target({ElementType.TYPE, ElementType.METHOD})
Retention(RetentionPolicy.RUNTIME)
Documented
public interface Conditional {/*** 注解中添加的类型必须是 实现了 Condition 接口的类型*/Class? extends Condition[] value();}Condition是个接口需要实现matches方法返回true则注入beanfalse则不注入。
案例讲解
/*** 定义一个 Condition 接口的是实现*/
public class MyCondition implements Condition {Overridepublic boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {verridepublic boolean matches(ConditionContext context,AnnotatedTypeMetadata metadata) {// 根据我们特定的业务需求来决定是否注入对应的对象try {boolean flage context.getRegistry().containsBeanDefinition(userService);if (flage) {Class.forName(com.bobo.test.test666);return flage;}} catch (ClassNotFoundException e) {e.printStackTrace();}return false;// 默认返回false}
}创建Java配置类
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);}}
}测试 但是将 matchs方法的返回结果设置为 true 则效果不同 所以Conditional的作用就是提供了对象导入IoC容器的条件机制这也是SpringBoot中的自动装配的核心关键。当然在4.x还提供一些其他的注解支持比如 EventListener,作为ApplicationListener接口编程的第二选择,AliasFor解除注解派生的时候冲突限制。CrossOrigin作为浏览器跨域资源的解决方案。
1.5 Spring 5.x
2017年9月28日Spring来到了5.0版本。5.0同时也是SpringBoot2.0的底层。注解驱动的性能提升方面不是很明显。在Spring Boot应用场景中大量使用ComponentScan扫描导致Spring模式的注解解析时间耗时增大因此5.0时代引入**Indexed**为Spring模式注解添加索引。
当在项目中使用了 Indexed之后编译打包的时候会在项目中自动生成 META-INT/spring.components文件。当Spring应用上下文执行 ComponentScan扫描时META-INT/spring.components将会被 CandidateComponentsIndexLoader 读取并加载转换为 CandidateComponentsIndex对象这样的话 ComponentScan不在扫描指定的package而是读取 CandidateComponentsIndex对象从而达到提升性能的目的。
dependencygroupIdorg.springframework/groupIdartifactIdspring-context-indexer/artifactId
/dependency使用Indexed注解 核心条件注解Conditional控制对象是否注入到容器中Import 注解中导入的类型 ImportSelector 接口不会将该类型注入到容器中而是会将selectImports的返回的类型的全类路径的字符串的数据注入到容器中动态注入类似于factoryBean ImportBeanDefinitionRegistrar 的方式是直接在定义的方法中提供了 BeanDefinitionRegistry ,自己在方法中实现注册。自己注册了bean定义接口类似于factoryBean Indexed 编译打包的时候会在项目中自动生成 META-INT/spring.components文件。当Spring应用上下文执行 ComponentScan扫描时META-INT/spring.components将会被 CandidateComponentsIndexLoader 读取并加载从而达到提升spring性能的目的。
2. 什么是SPI
为什么要讲SPI呢因为在SpringBoot的自动装配中其实有使用到SPI机制所以掌握了这部分对于SpringBoot的学习还是很有帮助的。
SPI 全称为 Service Provider Interface是一种服务发现机制。它通过在ClassPath路径下的META-INF/services文件夹查找文件自动加载文件里所定义的类。这一机制为很多框架扩展提供了可能比如在Dubbo、JDBC中都使用到了SPI机制。先通过一个很简单的例子来看下它是怎么用的。
案例介绍
先定义接口项目 然后创建一个扩展的实现先导入上面接口项目的依赖 dependenciesdependencygroupIdcom.bobo/groupIdartifactIdJavaSPIBase/artifactIdversion1.0-SNAPSHOT/version/dependency/dependencies然后创建接口的实现
/*** SPIMySQL对于 baseURL 的一种实现*/
public class MySqlConnection implements BaseData {Overridepublic void baseURL() {System.out.println(mysql 的扩展实现....);}
}然后在resources目录下创建 META-INF/services 目录然后在目录中创建一个文件名称必须是定义的接口的全类路径名称。然后在文件中写上接口的实现类的全类路径名称。 同样的再创建一个案例 然后在测试的项目中测试 public static void main(String[] args) {ServiceLoaderBaseData providers ServiceLoader.load(BaseData.class);IteratorBaseData iterator providers.iterator();while(iterator.hasNext()){BaseData next iterator.next();next.baseURL();}}根据不同的导入执行的逻辑会有不同 源码查看
ServiceLoader
首先来看下ServiceLoader的类结构 // 配置文件的路径private static final String PREFIX META-INF/services/;// 加载的服务 类或者接口private final ClassS service;// 类加载器private final ClassLoader loader;// 访问权限的上下文对象private final AccessControlContext acc;// 保存已经加载的服务类private LinkedHashMapString,S providers new LinkedHashMap();// 内部类真正加载服务类private LazyIterator lookupIterator;load
load方法创建了一些属性重要的是实例化了内部类LazyIterator。
public final class ServiceLoaderS implements IterableSprivate ServiceLoader(ClassS 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 IteratorS{ClassS service;ClassLoader loader;EnumerationURL configs null;IteratorString 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.SPIServiceString 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 IteratorS{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; }
}看到这儿已经很清楚了。获取到类的实例自然就可以对它为所欲为了
SpringBoot自动装配原理分析
自动装配源码分析
在前面的分析中Spring Framework一直在致力于解决一个问题就是如何让bean的管理变得更简单如何让开发者尽可能的少关注一些基础化的bean的配置从而实现自动装配。所以所谓的自动装配实际上就是如何自动将bean装载到Ioc容器中来。
实际上在spring 3.x版本中Enable模块驱动注解的出现已经有了一定的自动装配的雏形而真正能够实现这一机制还是在spirng 4.x版本中conditional条件注解的出现。看一下spring boot的自动装配是怎么一回事。
ImportSelector接口调用 ImportSelector接口中定义的 selectImports方法将该方法的返回的字符串数组的类型添加到容器中。 过滤检查候选配置类上的注解ConditionalOnClass如果要求的类不存在则这个候选类会被过滤不被加载 configurations this.getConfigurationClassFilter().filter(configurations);
自动装配的核心
本质上就是Spring容器的初始化 核心是自动配置类实现ImportSelect接口在selectImports方法中通过SPI机制在自动配置包下将META-INF下的spring.factory文件中把key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的值获取到并将value值转换为java类注入Spring容器当中
自动装配的演示 dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-redis/artifactId
/dependency spring:redis:host: 127.0.0.1 port: 6379Autowiredprivate RedisTemplateString,StringredisTemplate;按照下面的顺序添加starter然后添加配置使用RedisTemplate就可以使用了 那大家想没想过一个问题为什么RedisTemplate可以被直接注入它是什么时候加入到Ioc容器的呢 这就是自动装配。自动装配可以使得classpath下依赖的包相关的bean被自动装载到Spring Ioc容器中怎么做到的呢?
深入分析EnableAutoConfiguration
EnableAutoConfiguration的主要作用其实就是帮助springboot应用把所有符合条件的Configuration配置都加载到当前SpringBoot创建并使用的IoC容器中。
再回到EnableAutoConfiguration这个注解中我们发现它的import是这样
Import(AutoConfigurationImportSelector.class)
public interface EnableAutoConfiguration {
但是从EnableAutoCOnfiguration上面的import注解来看这里面并不是引入另外一个Configuration。而是一个ImportSelector。这个是什么东西呢
AutoConfigurationImportSelector是什么
Enable注解不仅仅可以像前面演示的案例一样很简单的实现多个Configuration的整合还可以实现一些复杂的场景比如可以根据上下文来激活不同类型的beanImport注解可以配置三种不同的class
第一种就是前面演示过的基于普通bean或者带有Configuration的bean进行诸如实现ImportSelector接口进行动态注入
实现ImportBeanDefinitionRegistrar接口进行动态注入
CacheService
public class CacheService { }
LoggerService
public class LoggerService { }
EnableDefineService
Target(ElementType.TYPE)
Retention(RetentionPolicy.RUNTIME)
Documented
Inherited --允许被继承
Import({MyDefineImportSelector.class})
public interface EnableDefineService {String[] packages() default ;
}MyDefineImportSelector
public class MyDefineImportSelector implements ImportSelector {Overridepublic String[] selectImports(AnnotationMetadata annotationMetadata) {//获得指定注解的详细信息。我们可以根据注解中配置的属性来返回不同的class//从而可以达到动态开启不同功能的目的annotationMetadata.getAllAnnotationAttributes(EnableDefineService.class.getName(),true).forEach((k,v) - {log.info(annotationMetadata.getClassName());log.info(k:{},v:{},k,String.valueOf(v));});return new String[]{CacheService.class.getName()};}
}EnableDemoTest
SpringBootApplication
EnableDefineService(name mashibing,value mashibing)
public class EnableDemoTest {public static void main(String[] args) {ConfigurableApplicationContext caSpringApplication.run(EnableDemoTest.class,args);System.out.println(ca.getBean(CacheService.class));System.out.println(ca.getBean(LoggerService.class));}
}了解了selector的基本原理之后后续再去分析AutoConfigurationImportSelector的原理就很简单了它本质上也是对于bean的动态加载。
EnableAutoConfiguration注解的实现原理
了解了ImportSelector和ImportBeanDefinitionRegistrar后对于EnableAutoConfiguration的理解就容易一些了
它会通过import导入第三方提供的bean的配置类AutoConfigurationImportSelector
Import(AutoConfigurationImportSelector.class)从名字来看可以猜到它是基于ImportSelector来实现基于动态bean的加载功能。之前我们讲过Springboot Enable*注解的工作原理ImportSelector接口selectImports返回的数组类的全类名都会被纳入到spring容器中。
那么可以猜想到这里的实现原理也一定是一样的定位到AutoConfigurationImportSelector这个类中的selectImports方法
selectImports
public String[] selectImports(AnnotationMetadata annotationMetadata) {if (!isEnabled(annotationMetadata)) {return NO_IMPORTS;}
// 从配置文件spring-autoconfigure-metadata.properties中加载 AutoConfigurationMetadataAutoConfigurationMetadata autoConfigurationMetadata AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
// 获取所有候选配置类EnableAutoConfigurationAutoConfigurationEntry autoConfigurationEntry getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
getAutoConfigurationEntry
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,AnnotationMetadata annotationMetadata) {if (!isEnabled(annotationMetadata)) {return EMPTY_ENTRY;}
//获取元注解中的属性AnnotationAttributes attributes getAttributes(annotationMetadata);
//使用SpringFactoriesLoader 加载classpath路径下META-INF\spring.factories中
//key org.springframework.boot.autoconfigure.EnableAutoConfiguration对应的valueListString configurations getCandidateConfigurations(annotationMetadata,attributes);
//去重configurations removeDuplicates(configurations);
//应用exclusion属性SetString exclusions getExclusions(annotationMetadata, attributes);checkExcludedClasses(configurations, exclusions);configurations.removeAll(exclusions);
//过滤检查候选配置类上的注解ConditionalOnClass如果要求的类不存在则这个候选类会被过滤不被加载configurations filter(configurations, autoConfigurationMetadata);//广播事件
fireAutoConfigurationImportEvents(configurations, exclusions);return new AutoConfigurationEntry(configurations, exclusions);
}
本质上来说其实EnableAutoConfiguration会帮助springboot应用把所有符合Configuration配置都加载到当前SpringBoot创建的IoC容器而这里面借助了Spring框架提供的一个工具类SpringFactoriesLoader的支持。以及用到了Spring提供的条件注解Conditional选择性的针对需要加载的bean进行条件过滤
SpringFactoriesLoader
为了给大家补一下基础我在这里简单分析一下SpringFactoriesLoader这个工具类的使用。它其实和java中的SPI机制的原理是一样的不过它比SPI更好的点在于不会一次性加载所有的类而是根据key进行加载。
首先SpringFactoriesLoader的作用是从classpath/META-INF/spring.factories文件中根据key来加载对应的类到spring IoC容器中。接下来带大家实践一下
创建外部项目jar
dependencygroupIdorg.springframework/groupIdartifactIdspring-context/artifactIdversion4.3.13.RELEASE/version
/dependency
创建bean以及config
public class mashibingCore {public String study(){System.out.println(good good study, day day up);return mashibingEdu.com;}
}
Configuration
public class mashibingConfig {Beanpublic mashibingCore mashibingCore(){return new mashibingCore();}
}
创建另外一个工程spring-boot
把前面的工程打包成jar当前项目依赖该jar包
dependencygroupIdcom.mashibingedu.practice/groupIdartifactIdmashibing-Core/artifactIdversion1.0-SNAPSHOT/version
/dependency
通过下面代码获取依赖包中的属性
运行结果会报错原因是mashibingCore并没有被Spring的IoC容器所加载也就是没有被EnableAutoConfiguration导入
SpringBootApplication
public class SpringBootStudyApplication {public static void main(String[] args) throws IOException {ConfigurableApplicationContext acSpringApplication.run(SpringBootStudyApplication.class, args);mashibingCore Mycac.getBean(mashibingCore.class);System.out.println(Myc.study());}
}解决方案
在mashibing-Core项目resources下新建文件夹META-INF在文件夹下面新建spring.factories文件文件中配置key为自定配置类EnableAutoConfiguration的全路径value是配置类的全路径
org.springframework.boot.autoconfigure.EnableAutoConfigurationcom.mashibingedu.practice.mashibingConfig重新打包重新运行SpringBootStudyApplication这个类。
可以发现我们编写的那个类就被加载进来了。
Spring Boot中的条件过滤
在分析AutoConfigurationImportSelector的源码时会先扫描spring-autoconfiguration-metadata.properties文件最后在扫描spring.factories对应的类时会结合前面的元数据进行过滤为什么要过滤呢 原因是很多的Configuration其实是依托于其他的框架来加载的如果当前的classpath环境下没有相关联的依赖则意味着这些类没必要进行加载所以通过这种条件过滤可以有效的减少configuration类的数量从而降低SpringBoot的启动时间。
修改mashibing-Core
在META-INF/增加配置文件spring-autoconfigure-metadata.properties。
com.mashibingedu.practice.mashibingConfig.ConditionalOnClasscom.mashibingedu.TestClass格式自动配置的类全名.条件值
上面这段代码的意思就是如果当前的classpath下存在TestClass则会对mashibingConfig这个Configuration进行加载
演示过程(spring-boot) 沿用前面spring-boot工程的测试案例直接运行main方法发现原本能够被加载的mashibingCore发现在ioc容器中找不到了。 public static void main(String[] args) throws IOException {ConfigurableApplicationContext acSpringApplication.run(SpringBootStudyApplication.class, args);mashibingCore Mycac.getBean(mashibingCore.class);System.out.println(Myc.study());
}在当前工程中指定的包com.mashibingedu下创建一个TestClass以后再运行上面这段代码程序能够正常执行
手写Starter
我们通过手写Starter来加深对于自动装配的理解
1.创建一个Maven项目quick-starter
定义相关的依赖
dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter/artifactIdversion2.1.6.RELEASE/version
/dependency
dependencygroupIdcom.alibaba/groupIdartifactIdfastjson/artifactIdversion1.2.56/version!-- 可选 --optionaltrue/optional
/dependency2.定义Formate接口
定义的格式转换的接口并且定义两个实现类
public interface FormatProcessor {/*** 定义一个格式化的方法* param obj* param T* return*/T String formate(T obj);
}
public class JsonFormatProcessor implements FormatProcessor {Overridepublic T String formate(T obj) {return JsonFormatProcessor: JSON.toJSONString(obj);}
}public class StringFormatProcessor implements FormatProcessor {Overridepublic T String formate(T obj) {return StringFormatProcessor: obj.toString();}
}3.定义相关的配置类
首先定义格式化加载的Java配置类
Configuration
public class FormatAutoConfiguration {ConditionalOnMissingClass(com.alibaba.fastjson.JSON)BeanPrimary // 优先加载public FormatProcessor stringFormatProcessor(){return new StringFormatProcessor();}ConditionalOnClass(namecom.alibaba.fastjson.JSON)Beanpublic FormatProcessor jsonFormatProcessor(){return new JsonFormatProcessor();}
}定义一个模板工具类
public class HelloFormatTemplate {private FormatProcessor formatProcessor;public HelloFormatTemplate(FormatProcessor processor){this.formatProcessor processor;}public T String doFormat(T obj){StringBuilder builder new StringBuilder();builder.append(Execute format : ).append(br);builder.append(Object format result: ).append(formatProcessor.formate(obj));return builder.toString();}
}再就是整合到SpringBoot中去的Java配置类
Configuration
Import(FormatAutoConfiguration.class)
public class HelloAutoConfiguration {Beanpublic HelloFormatTemplate helloFormatTemplate(FormatProcessor formatProcessor){return new HelloFormatTemplate(formatProcessor);}
}4.创建spring.factories文件
在resources下创建META-INF目录再在其下创建spring.factories文件
org.springframework.boot.autoconfigure.EnableAutoConfiguration\org.mashibingedu.autoconfiguration.HelloAutoConfigurationinstall 打包然后就可以在SpringBoot项目中依赖改项目来操作了。
5.测试
在SpringBoot中引入依赖
dependencygroupIdorg.example/groupIdartifactIdformat-spring-boot-starter/artifactIdversion1.0-SNAPSHOT/version
/dependency在controller中使用
RestController
public class UserController {Autowiredprivate HelloFormatTemplate helloFormatTemplate;GetMapping(/format)public String format(){User user new User();user.setName(BoBo);user.setAge(18);return helloFormatTemplate.doFormat(user);}
}6.自定义Starter关联配置信息
有些情况下我们可以需要用户在使用的时候动态的传递相关的配置信息比如Redis的Ip端口等等这些信息显然是不能直接写到代码中的这时我们就可以通过SpringBoot的配置类来实现。
首先引入依赖支持
dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-configuration-processor/artifactIdversion2.2.6.RELEASE/versionoptionaltrue/optional
/dependency然后创建对应的属性类
ConfigurationProperties(prefix HelloProperties.HELLO_FORMAT_PREFIX)
public class HelloProperties {public static final String HELLO_FORMAT_PREFIXmashibing.hello.format;private String name;private Integer age;private MapString,Object info;public MapString, Object getInfo() {return info;}public void setInfo(MapString, Object info) {this.info info;}public String getName() {return name;}public void setName(String name) {this.name name;}public Integer getAge() {return age;}public void setAge(Integer age) {this.age age;}
}然后再Java配置类中关联
Configuration
Import(FormatAutoConfiguration.class)
EnableConfigurationProperties(HelloProperties.class)
public class HelloAutoConfiguration {Beanpublic HelloFormatTemplate helloFormatTemplate(HelloProperties helloProperties,FormatProcessor formatProcessor){return new HelloFormatTemplate(helloProperties,formatProcessor);}
}调整模板方法
public class HelloFormatTemplate {private FormatProcessor formatProcessor;private HelloProperties helloProperties;public HelloFormatTemplate(HelloProperties helloProperties,FormatProcessor processor){this.helloProperties helloProperties;this.formatProcessor processor;}public T String doFormat(T obj){StringBuilder builder new StringBuilder();builder.append(Execute format : ).append(br);builder.append(HelloProperties:).append(formatProcessor.formate(helloProperties.getInfo())).append(br);builder.append(Object format result: ).append(formatProcessor.formate(obj));return builder.toString();}
}增加提示
在这个工程的META-INF/下创建一个additional-spring-configuration-metadata.json这个是设置属性的提示类型
{properties: [{name: mashibing.hello.format.name,type: java.lang.String,description: 账号信息,defaultValue: root},{name: mashibing.hello.format.age,type: java.lang.Integer,description: 年龄,defaultValue: 18}]
}protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {if (!isEnabled(annotationMetadata)) {return EMPTY_ENTRY;}AnnotationAttributes attributes getAttributes(annotationMetadata);// 加载当前系统下 META-INF/spring.factories 文件中声明的配置类ListString configurations getCandidateConfigurations(annotationMetadata, attributes);// 移除掉重复的configurations removeDuplicates(configurations);// 移除掉显示排除的SetString exclusions getExclusions(annotationMetadata, attributes);checkExcludedClasses(configurations, exclusions);configurations.removeAll(exclusions);// 过滤掉不需要载入的配置类configurations getConfigurationClassFilter().filter(configurations);fireAutoConfigurationImportEvents(configurations, exclusions);return new AutoConfigurationEntry(configurations, exclusions);}SpringBoot初始化核心流程源码
SpringBoot中的监听机制详解
SpringBoot中的属性文件加载原理
SpringBoot中的Tomcat容器加载
SpringBoot中的Acuator监控