springboot03_核心特性及设计思想
4.1.5.springboot核心特性及设计思想【上】
时长:55min
5.1.springboot注解驱动
5.1.1.spring4.X版本注解驱动
主要是注解装饰功能的一个完善。提供条件注解装配,即@Conditional注解使用。
5.1.1.1.什么是条件化装配?
其实,它是对是否进行bean装配的一个条件限制,如果条件返回true,则允许装配。否则,不允许。
下面通过示例代码来,说明条件装配的应用。
1.定义一个配置类
首先,创建一个springboot项目。然后创建配置类:
package com.wf.demo.springbootdemo.condition; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; /** * @ClassName SpringConfiguration * @Description 统一的配置类 * @Author wf * @Date 2020/7/6 18:09 * @Version 1.0 */ @Configuration public class SpringConfiguration { /** * 在某个环境下不装载 * 或不满足某个条件不装载 * 或者已经装载过了,不要重复装载 * @return */ @Conditional(MyCondition.class) @Bean public DemoService demoService(){ return new DemoService(); } }
说明:
配置类中,通过@Bean进行bean装配,然后加上@Conditional注解,就能实现条件装配。
@Conditional注解,传参为Condition接口子类Class对象,可以传参多个子类对象,多个对象之间是且的关系。
2.定义Condition子类
package com.wf.demo.springbootdemo.condition; import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.ConditionContext; import org.springframework.core.type.AnnotatedTypeMetadata; /** * @ClassName MyCondition * @Description 实现条件子类 * @Author wf * @Date 2020/7/6 18:14 * @Version 1.0 */ public class MyCondition implements Condition { @Override public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) { //TODO 这里可以写对应的判断逻辑 return false; } }
说明:
子类中实现match方法,进行条件判断,如果返回false,则不允许bean装配。
3.bean定义
package com.wf.demo.springbootdemo.condition; /** * @ClassName DemoService * @Description service bean类 * @Author wf * @Date 2020/7/6 18:10 * @Version 1.0 */ public class DemoService { }
4.测试bean实例获取
package com.wf.demo.springbootdemo.condition; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; /** * @ClassName ConditionMain * @Description 测试类 * @Author wf * @Date 2020/7/6 18:17 * @Version 1.0 */ public class ConditionMain { public static void main(String[] args) { ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfiguration.class); DemoService bean = context.getBean(DemoService.class); System.out.println(bean); } }
测试结果如下:
结果抛出异常,因为条件返回false,不允许装配。
下面修改match方法,返回true,如下所示:
再次运行测试,成功装配,如下所示:
5.1.2.spring5.X版本注解驱动
引入@Indexed注解,是用来提升性能的。当项目文件目录很多时,扫描@Component,@Service。。。这些注解时,
就会很耗时,而@Indexed注解,可以提升注解扫描的性能。
总结:
spring的注解驱动的发展,是为了bean装配更加简单。
下面通过springboot中整合redis来说明,注解驱动的简便性。
5.1.2.1.springboot整合redis示例
1.pom.xml中引入redis依赖
<!--整合redis--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
版本由springboot-parent来管理。
2.redis服务安装
在生产中会有专门的redis服务安装redis服务。而学习环境下,一般是在虚拟机上安装redis应用服务器。
3.controller中引用redis代码
package com.wf.demo.springbootdemo.web; import com.wf.demo.springbootdemo.dao.pojo.User; import com.wf.demo.springbootdemo.service.IUserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; /** * @ClassName HelloController * @Description 测试controller * @Author wf * @Date 2020/7/6 11:27 * @Version 1.0 */ @RestController public class HelloController { @Autowired private RedisTemplate<String,String> redisTemplate; //配置文件注入 // @Value("Mic") // private String name; @Autowired private IUserService userService; @GetMapping("hello") public String test(){ User user = new User(); user.setId(18); user.setName("MIc"); userService.insert(user); return "success"; } @GetMapping("/say") public String say(){ return redisTemplate.opsForValue().get("name"); } }
说明:
springboot已经为了我们使用redis封装了客户端工具类RedisTemplate。
只有使用它的api即可。
4.配置redis连接参数
spring.redis.host=192.168.216.128
#redis.port=6379默认
5.运行项目,测试
只需要启动main,浏览器端访问controller接口即可。
说明:
我们可以看到,springboot整合redis是如此之简单。
这里RedisTemplate实例,能够直接引用,说明spring IOC容器中已经完成bean的装配。
但是,我们是没有做这部分工作的,而是由springboot进行了装配。
【1】回顾spring4.X中bean装配方式
》xml配置
》@Configuration注解装配
》@Enable装配
现在,springboot提供了自动装配的功能。
5.2.springboot自动装配
自动装配的原理是什么?是如何实现的呢?
5.2.1.spring动态Bean的装配方案
主要有两种:
ImportSelector:
Registator
所谓动态装载,即根据上下文,或某些条件去装载一些配置类。
下面通过代码示例,来说明springboot实现批量扫描配置类的原理。
所谓批量扫描,就是一次性扫描所有jar包中的配置类。示例代码中以分包的形式,来模拟不同jar包的扫描。
5.2.1.1.实现ImportSelector的方式
1.在demo2包下创建redis配置类
package com.wf.demo.springbootdemo.demo2; /** * @ClassName RedisConfiguration * @Description redis配置类 * @Author wf * @Date 2020/7/6 19:25 * @Version 1.0 */ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class RedisConfiguration { @Bean public MyRedisTemplate myRedisTemplate(){ return new MyRedisTemplate(); } } package com.wf.demo.springbootdemo.demo2; /** * @ClassName MyRedisTemplate * @Description 自定义redis模板类 * @Author wf * @Date 2020/7/6 19:25 * @Version 1.0 */ public class MyRedisTemplate { }
2.在demo3包下创建Mybatis配置类
package com.wf.demo.springbootdemo.demo3; /** * @ClassName MySqlSessionFactory * @Description bean * @Author wf * @Date 2020/7/7 9:44 * @Version 1.0 */ public class MySqlSessionFactory { } package com.wf.demo.springbootdemo.demo3; /** * @ClassName MybatisConfiguration * @Description redis配置类 * @Author wf * @Date 2020/7/6 19:25 * @Version 1.0 */ import com.wf.demo.springbootdemo.demo2.MyRedisTemplate; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class MybatisConfiguration { @Bean MySqlSessionFactory mySqlSessionFactory(){ return new MySqlSessionFactory(); } }
3.scan包下进行springboot批量扫描处理
【1】定义一个Selector子类实现
package com.wf.demo.springbootdemo.scan; import com.wf.demo.springbootdemo.demo2.RedisConfiguration; import com.wf.demo.springbootdemo.demo3.MybatisConfiguration; import org.springframework.context.annotation.ImportSelector; import org.springframework.core.type.AnnotationMetadata; /** * @ClassName MyImportSelector * @Description 子类实现 * @Author wf * @Date 2020/7/6 19:30 * @Version 1.0 */ public class MyImportSelector implements ImportSelector { @Override public String[] selectImports(AnnotationMetadata annotationMetadata) { return new String[]{MybatisConfiguration.class.getName(), RedisConfiguration.class.getName()}; } }
说明:
实现子类中返回了多个配置类的全路径类名。【即可获取配置类的位置】
【2】定义Enable注解,来扫描Selector实现子类
package com.wf.demo.springbootdemo.scan; import org.springframework.context.annotation.Import; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Import(MyImportSelector.class) public @interface EnableConfiguration { }
【3】springboot启动入口main中bean获取测试
package com.wf.demo.springbootdemo; import com.wf.demo.example.demo2.MyRedisTemplate; import com.wf.demo.example.demo3.MySqlSessionFactory; import com.wf.demo.example.scan.EnableConfiguration; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; //@EnableConfiguration @SpringBootApplication public class SpringBootDemoApplication { public static void main(String[] args) { ConfigurableApplicationContext ca = SpringApplication.run(SpringBootDemoApplication.class, args); System.out.println(ca.getBean(MyRedisTemplate.class)); System.out.println(ca.getBean(MySqlSessionFactory.class)); } }
说明:
这里先注释掉@EnableConfiguration注解,测试结果,肯定会报错。如下所示:
然后,放开注解。bean注入成功,如下所示:
再次运行测试,结果如下:
总结:
为什么加上@EnableConfiguration注解,就能扫描到所有的配置类了呢?
因为这个注解里面,使用@Import注解,告知了spring所有配置类的位置所在。
其实是ImportSelector接口实现子类中,重写selectImports方法,告诉了spring要扫描的配置类的位置在哪里。
4.selectImports方法的实现
上面的实现中,传递的是配置类的全路径类名,其实也可以直接传递要装配bean的全路径类名。
方法实现修改示例如下:
然后,启动main,测试结果如下:
思考:
现在,已经基本弄清楚,批量扫描配置类的底层原理了。那么,就需要进行功能扩展,现在,只是通过传参两个类名,
然后就可以扫描两个配置类了。
如果有很多的第三方组件需要批量扫描,总不能一个个传参类名吧,这么做显然是不合理的。
所以,在selectImports方法中,一定可以通过某种机制去完成指定路径的配置类的扫描。
这里的某种机制,也体现了springboot 约定优于配置的设计思想。我们可以想到,第三方starter组件由不同团队进行开发,组件的名称
和包路径肯定是不一样的。
因此,springboot 就做出统一约定【定个标准】,你们第三方去开发starter组件,需要把配置类的说明【在某个路径下的全类名】要告诉
springboot引擎。
定义的标准是:
每一个starter组件,都需要定义路径classpath:META-INF/spring.factories文件【每一个jar包里面都要有】。有了这个文件,springboot
就很好处理了。只需要扫描并解析classpath*:META-INF/spring.factories文件,就可以获得相关配置类的全路径类名,然后传递到数组里面就可以了。
注意:
当我写完代码之后,针对包结构,做了如下调整:
5.2.2.springboot源码验证自动装配-批量实现原理
5.2.2.1.SpringBootApplication注解开始分析
通过注解类的定义,注解上引用@EnableAutoConfiguration注解,应该就是自动装配的作用。
1.分析@EnableAutoConfiguration注解
可以看到通过@Import注解,引入Selector接口的实现,下面实现子类的代码逻辑:
2.分析AutoConfigurationImportSelector动态装配实现逻辑
public class AutoConfigurationImportSelector implements DeferredImportSelector
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
跟踪this.getAutoConfigurationEntry方法:
通过debug启动main入口程序,进入断点处,可以获取到配置类的全路径类名。
再次跟踪getCandidateConfigurations方法:
显然,就是在解析classpath下META-INF/spring.factories文件。
5.2.2.2.springboot自动装配原理图解
如下图所示 :
4.1.6.springboot核心特性及设计思想【下】
5.2.3.springboot扫描并解析classpath下META-INF/spring.factories文件的具体实现
通过上面的源码分析,定位到getCandidateConfigurations方法。
org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#getCandidateConfigurations
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader()); Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct."); return configurations; }
方法实现逻辑中,调用SpringFactoriesLoader工具类。
5.2.3.1.SpringFactoriesLoader工具类
根据定义类名,就是加载spring.factories文件。这里调用SpringFactoriesLoader.loadFactoryNames()方法
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) { String factoryTypeName = factoryType.getName(); return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList()); }
返回一个List,再次调用 loadSpringFactories()方法,如下所示:
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) { MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader); //先从缓存中获取 if (result != null) { return result; //缓存中有数据,直接返回 } else { try {//这里会有多个jar包,就对应多个文件 Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories"); LinkedMultiValueMap result = new LinkedMultiValueMap(); while(urls.hasMoreElements()) { URL url = (URL)urls.nextElement(); UrlResource resource = new UrlResource(url); //得到文件资源路径 Properties properties = PropertiesLoaderUtils.loadProperties(resource); Iterator var6 = properties.entrySet().iterator(); while(var6.hasNext()) { Entry<?, ?> entry = (Entry)var6.next(); String factoryTypeName = ((String)entry.getKey()).trim(); String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue()); int var10 = var9.length; for(int var11 = 0; var11 < var10; ++var11) { String factoryImplementationName = var9[var11]; result.add(factoryTypeName, factoryImplementationName.trim()); } } } cache.put(classLoader, result); return result; } catch (IOException var13) { throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13); } } }
下面来看一个spring.factories文件是什么样子的,在springboot项目中,全局搜索spring.factories,如下所示:
然后,任意打开一个文件,如下所示:
# Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\ org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
可以看到,和properties文件很像,数据存储中key/value形式。
key为:org.springframework.boot.autoconfigure.EnableAutoConfiguration
value为:配置类全路径类名,通过,拼接成字符串
注意:
spring.factory文件可能是存储其他配置的,并不全是自动装配的配置。举例如下:
它是作为starter定义的配置文件。
5.2.3.2.SPI机制
Service provider interface【简称SPI】,这是一 种什么样的机制呢?它的核心思想是什么?
我们以java中数据库连接为例。jdk定义数据库连接的接口。但没有定义实现。而是数据库厂商提供实现方案。
如:mysql连接,我们会引入一个mysql的驱动包。【这就是一种实现方案】
然后,我在项目上下文环境,一般是application.properties文件中定义数据库连接参数。 如:jdbc.url/username/password...
在jdk环境中,提供对这些配置参数的解析。
当获得这些配置参数,这可以引用驱动包的服务, 建立通信。这就是一种扩展机制。这种扩展点设计,不是为了分隔接口定义
与实现分离,而是为功能的扩展。从而,提升框架的扩展性和灵活性。
下面以springboot中spring.factories文件,作为SPI机制的运用来加以说明:
每一个组件,打成一个jar包。在jar包中都会有一个spring.factories文件,对于自动装配来说,这个文件定义自动装配类的
实现。key代表着自动装配的定义,如下所示:
下面以示例代码来说明,这样一种SPI机制是什么样的。
1.创建一个maven项目,模拟jdk定义数据库连接的接口
项目结构如下所示:
【1】定义一个建立连接的接口
因为我们是站在框架设计的角度,我们没有具体的实现,不知道它的具体实现是什么样子的。
但是,我们可以定义一种规范,提供spi扩展点,让实现方根据我们定义的规范来做具体的实现。
代码如下所示:
package com.wf.spi; /** * @ClassName DataBaseDriver * @Description 驱动接口定义 * @Author wf * @Date 2020/7/7 14:12 * @Version 1.0 */ public interface DataBaseDriver { String buildConnect(String host); }
【2】使用maven install命令打成一个jar包
2.创建第二个maven项目,模拟mysql实现驱动包
【1】实现项目中引入接口项目的jar包依赖
修改pom.xml,如下所示:
<dependency> <groupId>com.wf.spi</groupId> <artifactId>DataBaseDriver</artifactId> <version>1.0-SNAPSHOT</version> </dependency>
【2】实现驱动服务
package com.wf.spi.mysql; import com.wf.spi.DataBaseDriver; /** * @ClassName MysqlDriver * @Description mysql驱动服务实现 * @Author wf * @Date 2020/7/7 14:31 * @Version 1.0 */ public class MysqlDriver implements DataBaseDriver { @Override public String buildConnect(String url) { return "mysql的驱动实现:"+url; } }
【3】定义扩展点
完成了这样一个mysql驱动实现,我怎么去使用它呢?
就需要定义规范,通常是由配置文件,进行相关规范定义。
这里,就需要创建resources资源文件夹,下面创建META-INF目录。再目录下创建services目录,
然后在services目录下创建text文件,文件命名为
驱动接口的全路径类名。文件的内容也配置为接口实现类的全路径类名。如下所示:
注意:
这个是针对所有jar包规范。必须按此规范配置。
【4】项目完成后,打包。
3.创建第三个maven项目,模拟用户使用jdk连接mysql
[1]pom.xml中引入接口包和实现包
<dependency> <groupId>com.wf.spi</groupId> <artifactId>DataBaseDriver</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>com.wf.spi.mysql</groupId> <artifactId>mysql-driver-connector</artifactId> <version>1.0-SNAPSHOT</version> </dependency>
【2】创建测试代码
package com.wf.spi.use; import com.wf.spi.DataBaseDriver; import java.util.ServiceLoader; public class App { public static void main( String[] args ) { //加载实现 ServiceLoader<DataBaseDriver> serviceLoader = ServiceLoader.load(DataBaseDriver.class); for (DataBaseDriver driver : serviceLoader) { System.out.println(driver.buildConnect("Test")); } } }
测试结果如下所示:
说明:
这就是jdk底层的SPI机制应用。这里使用jdk提供的ServiceLoader工具类,加载实现类。
4.java中SPI规范
java中要想实现SPI扩展,需要满足几个条件:
》需要在classpath下创建目录META-INF/services【必须完全一致】
》在该目录扩展点【定义接口】的text文件,文件名为定义接口全路径类名。
》文件内容,配置接口实现类的全类名。如果有多个实现,换行配置多个
》文件编码格式为UTF-8
》实现方式:使用jdk提供ServiceLoader工具类,加载实现类。
5.2.4.再了解springboot条件装配
我们发现有一个奇怪的现象:spring-boot与redis整合的starter包中没有spring.factories文件,如下所示:
同样,整合jdbc的starter包中,也没有,如下所示:
这到底是为什么呢?这就涉及到springboot官方定义starter包的规范问题。
5.2.4.1.springboot官方定义starter包的规范
springboot官方定义的starter包,存在一些规范:
》官方包命名规范,如:spring-boot-starter-XXX.jar
》第三方包命名规范,如:xxx-spring-boot-starter.jar
如:mybatis定义starter命名,mybatis-spring-boot-starter
springboot官方包,不存放配置类,而是专门定义自动装配的包,如下所示:
这个包里,会把springboot提供的所有自动装配的配置类信息,进行统一配置在spring.factories文件中。
这是一种违反常规的设计,那么,spring-boot是如何实现自动装配的呢?与常规的装配方式有什么区别吗?
下面以redis自动装配为例,来进行说明:
5.2.4.2.springboot自动装配再理解
以redis自动装配为例,搜索redis自动装配配置类RedisAutoConfiguration,代码实现如下:
@Configuration( proxyBeanMethods = false ) @ConditionalOnClass({RedisOperations.class}) @EnableConfigurationProperties({RedisProperties.class}) @Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class}) public class RedisAutoConfiguration { public RedisAutoConfiguration() { } @Bean @ConditionalOnMissingBean( name = {"redisTemplate"} ) public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException { RedisTemplate<Object, Object> template = new RedisTemplate(); template.setConnectionFactory(redisConnectionFactory); return template; } @Bean @ConditionalOnMissingBean public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException { StringRedisTemplate template = new StringRedisTemplate(); template.setConnectionFactory(redisConnectionFactory); return template; } }
1.@ConditionalOnClass({RedisOperations.class})
可以看到配置类上,声明@ConditionalOnClass({RedisOperations.class})注解,它有什么作用呢?
该注解,传入参数为RedisOperations类的class对象,这个类是定义在spring-data-redis整合包中的。定义如下所示:
这个注解的含义是:
如果传入参数所在包,未引入,导致条件不满足,就不允许装配。
所以,springboot官方starter包,其实是一种条件装配,不需要引入配置类。
思考:
如果条件传参对象,所在包不存在,怎么办?
其实,这就涉及到maven依赖的传递依赖。所谓传递依赖,当前项目与依赖jar包如果不传递,当前项目未引入依赖时不会报错。
下面以一个demo示例,来说明springboot自动装配的过程。
5.2.4.3.spring自动装配示例demo
1.创建第一个maven,模拟第三方组件
【1】pom.xml中引入spring-context依赖
<spring.version>5.2.7.RELEASE</spring.version> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency>
【2】创建配置类
package com.wf.autoconfig.demo; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @ClassName MyConfiguration * @Description 配置类 * @Author wf * @Date 2020/7/7 16:54 * @Version 1.0 */ @Configuration public class MyConfiguration { @Bean public MyCore myCore(){ return new MyCore(); } }
【3】创建bean类
package com.wf.autoconfig.demo; /** * @ClassName MyCore * @Description 核心类 * @Author wf * @Date 2020/7/7 16:52 * @Version 1.0 */ public class MyCore { public String study(){ System.out.println("I'm learning p6 lesson"); return "Gupaoedu.com"; } }
【4】打成一个jar包。
使用mvn install命令。
2.在springboot项目中使用自定义组件
【1】pom.xml中引入组件依赖
<!--引入自定义第三方组件依赖--> <dependency> <groupId>com.wf.autoconfig.demo</groupId> <artifactId>my-core-app</artifactId> <version>1.0-SNAPSHOT</version> </dependency>
【2】使用第三方组件中bean
要想以spring注入的方式使用,显然需要先完成自动装配。先在main入口方法,测试一下bean能否获取。
代码如下所示:
package com.wf.demo.springbootdemo; import com.wf.autoconfig.demo.MyCore; import com.wf.demo.example.demo2.MyRedisTemplate; import com.wf.demo.example.demo3.MySqlSessionFactory; import com.wf.demo.example.scan.EnableConfiguration; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; @EnableConfiguration @SpringBootApplication public class SpringBootDemoApplication { public static void main(String[] args) { ConfigurableApplicationContext ca = SpringApplication.run(SpringBootDemoApplication.class, args); System.out.println(ca.getBean(MyRedisTemplate.class)); System.out.println(ca.getBean(MySqlSessionFactory.class)); System.out.println(ca.getBean(MyCore.class)); //获取新定义第三方组件中bean实例 } }
然后,启动main,进行测试,显然,无法注入,会报错,如下所示:
3.修改组件,完成自动装配相关配置
这里基于SPI机制进行装配。
创建resources资源文件夹,然后创建META-INF目录,在目录创建spring.factories文件,文件配置如下:
具体配置内容如下:
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.wf.autoconfig.demo.MyConfiguration
然后,重新打包项目。
【1】再次在springboot项目中运行main
测试结果如下:
说明:
自定义第三方组件bean成功注入。
4.修改第三组件,实现条件注入
【1】引入springboot依赖starter包
因为@ConditionalOnClass注解是springboot特有注解。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <version>2.3.1.RELEASE</version> <optional>true</optional> </dependency>
<!--RedisOperations类依赖包-->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>2.3.1.RELEASE</version>
<optional>true</optional>
</dependency>
【2】修改配置类,添加条件装配
然后,重新打包项目。
【3】回到springboot项目,测试
注释掉spring-data-redis所在包依赖,如下所示:
说明:
因为未引入所依赖jar,就会导致条件不满足,无法进行bean装配。测试肯定会报错,如下所示:
反之,引入条件传参依赖包,条件满足,可完成自动装配。
5.修改自定义组件,使用第二种配置方式实现条件装配
在META-INF目录下,创建spring-autoconfigure-metadata.properties配置文件。如下所示:
配置内容如下:
com.wf.autoconfig.demo.MyConfiguration.ConditionalOnClass=com.wf.DemoClass
这里,其实是以配置文件的方式来完成条件注入。而上面是以注解的方式完成条件注入。
不同的是,这种配置文件的方式注入,如果在组件中能找到com.wf.DemoClass就表示条件满足,
可以在springboot项目中完成自动装配。
这里先不定义这个DemoClass类,让条件不满足,然后打包项目。
【1】回到springboot项目,测试bean获取
显然,是无法自动装配的。报错如下所示:
【2】修改自定义组件,定义条件类
如下所示:
然后,重新打包,回到springboot项目进行测试,如下所示:
总结:
使用配置文件实现条件装配,更为简单。不需要引入springboot的依赖包。
也不需要springboot项目是否导包来设置条件。