一、Bean注入异常
多实例Bean注入异常的"陷阱"
增加Redis依赖
代码语言:javascript复制<!--redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
启动本机的redis服务,不需编写配置文件,Spring Boot会默认读取本机的Redis
@Autowire默认按照类型注入
配置多个redis数据源,增加config包,新建RedisConfig,配置多个Redis数据源
代码语言:javascript复制@Configuration
public class RedisConfig {
private final RedisConnectionFactory redisConnectionFactory;
@Autowired
public RedisConfig(RedisConnectionFactory redisConnectionFactory){
this.redisConnectionFactory = redisConnectionFactory;
}
// 第一个数据源
@Bean(name = "alphaRedisTemplate")
public RedisTemplate<String, Object> getAlphaRedisTemplate(RedisConnectionFactory factory){
RedisTemplate<String, Object> template = new RedisTemplate<>();
// redis序列化方式
RedisSerializer<String> stringSerializer = new StringRedisSerializer();
template.setConnectionFactory(factory);
template.setKeySerializer(stringSerializer);
template.setValueSerializer(stringSerializer);
return template;
}
// 第二个数据源
@Bean(name = "bravoRedisTemplate")
public RedisTemplate<String, Object> getBravoRedisTemplate(RedisConnectionFactory factory){
RedisTemplate<String, Object> template = new RedisTemplate<>();
// redis序列化方式,与第一个不同
JdkSerializationRedisSerializer redisSerializer = new JdkSerializationRedisSerializer();
RedisSerializer<String> stringSerializer = new StringRedisSerializer();
template.setConnectionFactory(factory);
template.setKeySerializer(stringSerializer);
// 设置value序列化方式为jdk,即二进制代码
template.setValueSerializer(redisSerializer);
return template;
}
}
新增测试类RedisConfigTest
代码语言:javascript复制public class RedisConfigTest extends SpringTrapsApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
public void testAutowire(){
// 刷新容器
redisTemplate.getConnectionFactory().getConnection().flushAll();
redisTemplate.opsForValue().set("name","stark");
}
}
执行测试,查看redis数据库,数据存储成功
key和value的序列化方式并不是RedisConfig中设置的两个Redis数据源的序列化方式,因此获取的RedisTemplate是Spring Boot默认注入的RedisTemplate
@Autowire默认按照类型注入,如果类型有多个,则会按照符合变量名的Bean Name注入,将@Autowire注入的RedisTemplate的变量名改为alphaRedisTemplate,再次执行测试并查看redis中的数据
key和value的序列化方式都是String,说明自动注入的RedisTemplate是RedisConfig中配置的AlphaRedisTemplate
@Autowire @Qualifier指定Bean Name注入
修改测试类,ResdisTemplate属性上增加@Qualifier注解,指定注入的Bean的Name
代码语言:javascript复制@Autowired
@Qualifier("bravoRedisTemplate")
private RedisTemplate redisTemplate;
再次执行测试并查看Redis中的数据
name序列化为String,value序列化为二进制方式,符合RedisConfig中设置的BravoRedisTemplate的序列化方式
@Resource按照Bean Name注入
将@Autowire注解修改为@Resource注解,修改变量名称为alphaRedisTemplate
代码语言:javascript复制@Resource
private RedisTemplate alphaRedisTemplate;
再次执行测试并查看Redis中的数据
key与value的序列化方式都是AlphaRedisTemplate设置的String,因此AlphaRedisTemplate被成功注入到容器中
@Primary指定优先注入的 Bean
修改测试类中自动注入RedisTemplate属性,并在RedisConfig中的getAplhaRedisTempalte方法上增加@Primary注解,即优先注入AlphaRedisTemplate
代码语言:javascript复制@Autowired
private RedisTemplate redisTemplate;
执行测试类,并查看Redis中的数据
key与value的序列化方式都是AlphaRedisTemplate设置的String,因此AlphaRedisTemplate被成功注入到容器中
Bean注入的"陷阱"
在service包中新增一个PorscheService接口并定义个print()方法,在TeslaControllerTest中注入PorscheService接口
代码语言:javascript复制@Service
public interface PorscheService {
void print();
}
新增一个测试类PorscheServiceTest,增加测试方法
代码语言:javascript复制public class PorscheServiceTest extends SpringTrapsApplicationTests {
@Autowired
private PorscheService porscheService;
@Test
public void testAutowireInterface(){
System.out.println(porscheService);
}
}
执行测试方法
接口没有实现,所以会报错,@Autowire有required属性,设置required=false,再次执行测试,控制台不再报错,required=false允许的注入的对象为空
注入的Bean有多个实现类的"陷阱"
在service包中增加PorscheService的实现类TaycanService、MacanService、PanameraService,三个类都实现了print方法,打印出简单类名
代码语言:javascript复制@Service
public class TaycanService implements PorscheService {
@Override
public void print() {
System.out.println(this.getClass().getSimpleName());
}
}
再次执行测试
因为PorscheService接口有三个实现类,容器不确定要注入哪一个,所以报错。使用@Qualifier注解可以指定要注入的实现类的Bean的默认名称,在测试类中的@Autowire注解下面增加@Qualifier("taycanService"),再次执行测试
成功注入TaycanService 也可以在注入时指定注入的名称代替接口类的名称,同样可以让容器注入指定的实现类。或者可以使用@Resource注解指定注入实现类。
二、Bean循环依赖
循环依赖是指多个对象之间的依赖关系形成闭环 在service包中新建一个ProductService和ItemService
代码语言:javascript复制@Service
public class ProductService {
private final ItemService itemService;
@Autowired
public ProductService(ItemService itemService){
this.itemService = itemService;
}
public void printName(){
System.out.println(this.getClass().getSimpleName());
}
}
代码语言:javascript复制@Service
public class ItemService {
private final ProductService productService;
@Autowired
public ItemService(ProductService productService){
this.productService = productService;
}
public void printName(){
System.out.println(this.getClass().getSimpleName());
}
}
新建一个测试类ProductServiceTest
代码语言:javascript复制public class ProductServiceTest extends SpringTrapsApplicationTests {
@Autowired
private ProductService productService;
@Autowired
private ItemService itemService;
@Test
public void testCyclicDeps(){
productService.printName();
itemService.printName();
}
}
执行该测试类
这种循环依赖属于构造器循环依赖,JVM在实例化类时,需要先实例化构造器中的参数,由于参数无法提前实例化导致报错。
Spring 能解决循环依赖的问题,值得是解决属性依赖的问题,将上面两个类中构造起方法删除,使用@Autowire注解注入属性,改为属性依赖即可。
Spring使用三级缓存策略来解决循环依赖的问题,只能解决单例模式下的循环依赖
- 一级缓存:用于存放完全初始化好的Bean
- 二级缓存:存放原始的Bean对象(未填充属性),用于解决循环依赖
- 三级缓存:存放Bean工程对象,用于解决循环依赖
三、BeanPostProcessor和BeanFactoryPostProcessor
完成一个需求,根据视频编码类型选择不同的解码器进行解码 新增一个player包,增加一个枚举类VideoType
代码语言:javascript复制@Getter
@AllArgsConstructor
public enum VideoType {
MP4("MP4"),
WMV("WMV");
private String desc;
}
新增一个接口IDecoder,定义两个方法type和decoder
代码语言:javascript复制public interface IDecoder {
VideoType type();
String decode(String data);
}
增加MP4Decoder和WMVDecoder两个类实现IDecoder接口,并用注解将这两个类标记为Spring Bean
代码语言:javascript复制@Service
public class WMVDecoder implements IDecoder {
@Override
public VideoType type() {
return VideoType.WMV;
}
@Override
public String decode(String data) {
return this.type().getDesc() ":" data;
}
}
代码语言:javascript复制@Service
@Slf4j
public class MP4Decoder implements IDecoder, InitializingBean {
@Override
public VideoType type() {
return VideoType.MP4;
}
@Override
public String decode(String data) {
return this.type().getDesc() ":" data;
}
@Override
public void afterPropertiesSet() throws Exception {
log.info("Init MP4Decoder In InitializingBean");
}
}
编写一个测试类PlayerTest
代码语言:javascript复制@Slf4j
public class PlayerTest extends SpringTrapsApplicationTests {
@Autowired
private MP4Decoder mp4Decoder;
@Autowired
private WMVDecoder wmvDecoder;
// 获取随机的VideoType
private VideoType getRandomVideoType(){
return VideoType.values()[new Random().nextInt(VideoType.values().length)];
}
@Test
public void testEasyUseDecoder() {
// 获取视频类型,用于解码
VideoType type = getRandomVideoType();
switch (type) {
case MP4:
log.info(mp4Decoder.decode("video"));
break;
case WMV:
log.info(wmvDecoder.decode("video"));
break;
default:
log.info("error");
}
}
}
执行测试方法
第二种方法,实现BeanPostProcessor 在player包中增加一个DecoderManager类实现BeanPostProcessor接口
代码语言:javascript复制@Slf4j
@Service
public class DecoderManager implements BeanPostProcessor {
private static final Map<VideoType, IDecoder> videoTypeIndex = new HashMap<>(
VideoType.values().length
);
public String decode(VideoType type, String data) {
String result = null;
switch (type) {
case MP4:
result = videoTypeIndex.get(VideoType.MP4).decode(data);
break;
case WMV:
result = videoTypeIndex.get(VideoType.WMV).decode(data);
break;
default:
log.info("error");
}
return result;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
if (!(bean instanceof IDecoder)) {
return bean;
}
IDecoder decoder = (IDecoder) bean;
VideoType type = decoder.type();
if (videoTypeIndex.containsKey(type)) {
throw new IllegalStateException("重复注册");
}
log.info("Load Decoder {} for video type {}", decoder.getClass(),
type.getDesc());
videoTypeIndex.put(type, decoder);
return null;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
if (!(bean instanceof IDecoder)) {
return bean;
}
log.info("BeanPostProcessor after init: {}", bean.getClass());
return null;
}
}
在PlayerTest中注入DecoderManger,新增测试方法testUseDecoderManager
代码语言:javascript复制@Autowired
private DecoderManager decoderManager;
@Test
public void testUseDecoderManager() {
log.info(decoderManager.decode(getRandomVideoType(), "video"));
}
执行测试方法
BeanPostProcessor是Bean的后置处理器,在Bean实例化之后执行,有两个回调方法
- postProcessBeforeInitialization:Bean对象初始化之前回调
- postProcessAfterInitializatioin:Bean对象初始化之后回调
在player包下增加一个ThirdPartyClass,第三方的类
代码语言:javascript复制@Service
public class ThirdPartyClass {
}
新增测试方法
代码语言:javascript复制@Test
public void testCheckBeanFactoryPostProcessor() {
ThirdPartyClass class01 = ApplicationContextUtil.getBeanByClass(ThirdPartyClass.class);
ThirdPartyClass class02 = ApplicationContextUtil.getBeanByClass(ThirdPartyClass.class);
System.out.println(class01.hashCode());
System.out.println(class02.hashCode());
}
增加一个ThirdPartyBeanFactoryPostProcessor,实现多实例
代码语言:javascript复制@Component
public class ThirdPartyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
throws BeansException {
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(
"thirdPartyClass");
beanDefinition.setScope(BeanDefinition.SCOPE_PROTOTYPE);
}
}
BeanFactoryPostProcessor
- BeanFacotryPostProcessor是Spring容器加载xml文件之后,Bean实例化之前执行的
- Bean FactoryPostProcessor的执行顺序在BeanPostProcessor之前
- BeanFactoryPostProcessor与BeanPostProcessor都是服务于Bean的生命周期中,但是场景和作用不同