SpringBoot学习笔记三-原理分析
- SpringBoot自动装配
- 1.1 案例
- 1.2 通过注解方式管理Bean
- 1.3 小结
- 1.4 Enable注解
- 1.5 Import注解
- 1.5.1 ImportSelector实现类
- 1.5.2 导入ImportBeanDefinitionRegistrar
- 1.5 EnableAutoConfiguration
- 1.6 案例
SpringBoot自动装配
当再pom.xml中导入对应的依赖,那么就可以在SpringBoot的IOC容器中获取该依赖中相应的Bean实例:
比如导入redis依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
那么可以在IOC容器获取bean实例redisTemplate:
ConfigurableApplicationContext context = SpringApplication.run(SpringConditionApplication.class, args);
Object redisTemplate = context.getBean("redisTemplate");
System.out.println(redisTemplate);
1.1 案例
Condition案例:通过设定条件来决定手动创建的Bean是否导入IOC容器
条件:当Jedis在类路径上可用时,才能将创建的Bean注入IOC容器:
(1)首先编写condition类
package com.rql.condition;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
public class ClassCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
//1.判断Jedis坐标是否导入
boolean flag = true;
try {
Class<?> aClass = Class.forName("redis.clients.jedis.Jedis");
} catch (ClassNotFoundException e) {
flag=false;
}
return flag;
}
}
在配置类中,根据ClassCondition 中的matches方法的返回值来决定是否将Bean注入IOC容器
package com.rql.config;
import com.rql.condition.ClassCondition;
import com.rql.domain.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
@Configuration
public class UserConfig {
@Bean
@Conditional(ClassCondition.class)
public User user() {
return new User();
}
}
最后,获取注入的Bean实例:
ConfigurableApplicationContext context = SpringApplication.run(SpringConditionApplication.class, args);
Object user = context.getBean("user");
System.out.println(user);
可以发现,如果没有在pom.xml导入Jedis依赖的话,那么matches方法返回的值就为false,那么Bean User就会注入失败,最终结果会报错,找不到对应的Bean实例。
1.2 通过注解方式管理Bean
通过注解的方式可以让条件的复用性更强:
(1)首先,在配置类中,将注入user到IOC容器,这里设置了条件:
@Configuration
public class UserConfig {
@Bean
@ConditionOnClass("redis.clients.jedis.Jedis")
public User user() {
return new User();
}
}
(2)定义了一个自定义的注解(annotation)叫做 ConditionOnClass。注解在Java中是一种元数据(metadata)机制,它允许你为代码添加额外的信息,这些信息可以在运行时被读取和处理。ConditionOnClass 注解被用于条件性地创建或配置Spring框架中的Bean。
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(ClassCondition.class)
public @interface ConditionOnClass {
String[] value();
}
(3)创建ClassCondition 类需要实现 Condition 接口,并定义条件逻辑
public class ClassCondition implements Condition {
/**
*
* @param context 上下文对象,用于获取环境,IOC容器,ClassLoader对象
* @param metadata 注解元对象,可以用于获取注解定义的属性值
* @return
*/
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
//1.判断Jedis坐标是否导入
Map<String, Object> map = metadata.getAnnotationAttributes(ConditionOnClass.class.getName());
System.out.println(map);
String[] value = (String[]) map.get("value");
boolean flag = true;
for (String s : value) {
try {
Class.forName(s);
} catch (ClassNotFoundException e) {
flag=false;
}
}
return flag;
}
}
1.3 小结
1.4 Enable注解
下面主要演示在两个不同的module下,一个module使用另一个module的Bean实例:
(1)首先创建两个module
(2)在spring-enable-other中创建并注入bean user
package com.rql.domain;
public class User {
}
@Configuration
public class UserConfig {
@Bean
public User user() {
return new User();
}
}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(UserConfig.class)
public @interface EnableUser {
}
然后在spring-enable上使用user
@SpringBootApplication
//@Import(UserConfig.class)
@EnableUser
public class SpringEnableApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(SpringEnableApplication.class, args);
Object user = context.getBean("user");
System.out.println(user);
}
}
其实,这里本质还是使用了@Import注解,因为在EnableUser中使用了@Import导入了UserConfig类。所以在spring-enable中通过@EnableUser可以加载到user实例
1.5 Import注解
1.5.1 ImportSelector实现类
(1)导入ImportSelector实现类
创建类并实现ImportSelector接口:
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"com.rql.domain.User","com.rql.domain.Role"};
}
}
(2)直接通过Import注解导入,即可
@SpringBootApplication
//@Import(UserConfig.class)
//@EnableUser
@Import(MyImportSelector.class)
public class SpringEnableApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(SpringEnableApplication.class, args);
Object user = context.getBean("user");
System.out.println(user);
Object role = context.getBean("role");
System.out.println(role);
}
}
1.5.2 导入ImportBeanDefinitionRegistrar
(1)同样,创建类,继承ImportBeanDefinitionRegistrar接口
public class MyImportBeanDefinitionRegister implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(User.class).getBeanDefinition();
registry.registerBeanDefinition("user", beanDefinition);
//这里直接将Bean注册到IOC容器中
}
}
(2)在类中导入MyImportBeanDefinitionRegister ,即可获取Bean
@SpringBootApplication
//@Import(UserConfig.class)
//@EnableUser
//@Import(MyImportSelector.class)
@Import(MyImportBeanDefinitionRegister.class)
public class SpringEnableApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(SpringEnableApplication.class, args);
// Object user = context.getBean("user");
// System.out.println(user);
Object role = context.getBean("user");
System.out.println(role);
}
}
上述的四种用法主要目的都是将Bean注入到IOC容器,具体涉及到的应用场景后续再补充。
1.5 EnableAutoConfiguration
1.6 案例
(1)创建redis-spring-boot-autoconfigure模块
(2)创建redis-spring-boot-starter模块,并依赖于redis-spring-boot-autoconfigure模块
(3)在redis-spring-boot-autoconfigure模块初始化Jedis的Bean,并定义META-INF/spring.factories文件
RedisProperties:
@ConfigurationProperties(prefix = "redis")//用于将配置文件中的属性绑定到Java对象上。
public class RedisProperties {
private String host="127.0.0.1";
private Integer port=6379;
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public Integer getPort() {
return port;
}
public void setPort(Integer port) {
this.port = port;
}
@Override
public String toString() {
return "RedisProperties{" +
"host='" + host + '\'' +
", port=" + port +
'}';
}
}
spring.factories文件
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.rql.redis.config.RedisAutoConfiguration
Spring Boot在启动时自动加载并应用com.rql.redis.config.RedisAutoConfiguration这个类。这个类的bean会在Spring容器中自动注册。
(4)在测试模块导入自定义的redis-starter依赖,并获取Jedis的Bean,操作redis
首先,需要另一个模块导入自定义的redis-starter依赖:
<dependency>
<groupId>com.rql</groupId>
<artifactId>redis-spring-boot-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
然后获取Jedis的Bean,操作redis
Jedis jedis = context.getBean(Jedis.class);
System.out.println(jedis);
jedis.set("name","itcast");
String name = jedis.get("name");
System.out.println(name);
上述的过程其实就是在模拟SpringBoot自动装配的原理,通过自定义启动类并进行相应的配置。使用时,只需将自定义的启动类导入,即可获取自定义的Bean实例。