zl程序教程

您现在的位置是:首页 >  后端

当前栏目

你有没有掉进去过这些Spring的“陷阱“(下)

Spring 这些 有没有 陷阱 掉进去
2023-06-13 09:11:16 时间

一、Bean注入异常

多实例Bean注入异常的"陷阱"

增加Redis依赖

<!--redis-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

启动本机的redis服务,不需编写配置文件,Spring Boot会默认读取本机的Redis

@Autowire默认按照类型注入

配置多个redis数据源,增加config包,新建RedisConfig,配置多个Redis数据源

@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

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

@Autowired
@Qualifier("bravoRedisTemplate")
private RedisTemplate redisTemplate;

再次执行测试并查看Redis中的数据

name序列化为String,value序列化为二进制方式,符合RedisConfig中设置的BravoRedisTemplate的序列化方式

@Resource按照Bean Name注入

将@Autowire注解修改为@Resource注解,修改变量名称为alphaRedisTemplate

@Resource
private RedisTemplate alphaRedisTemplate;

再次执行测试并查看Redis中的数据

key与value的序列化方式都是AlphaRedisTemplate设置的String,因此AlphaRedisTemplate被成功注入到容器中

@Primary指定优先注入的 Bean

修改测试类中自动注入RedisTemplate属性,并在RedisConfig中的getAplhaRedisTempalte方法上增加@Primary注解,即优先注入AlphaRedisTemplate

@Autowired
private RedisTemplate redisTemplate;

执行测试类,并查看Redis中的数据

key与value的序列化方式都是AlphaRedisTemplate设置的String,因此AlphaRedisTemplate被成功注入到容器中

Bean注入的"陷阱"

在service包中新增一个PorscheService接口并定义个print()方法,在TeslaControllerTest中注入PorscheService接口

@Service
public interface PorscheService {

    void print();
}

新增一个测试类PorscheServiceTest,增加测试方法

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方法,打印出简单类名

@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

@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());
    }
}
@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

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使用三级缓存策略来解决循环依赖的问题,只能解决单例模式下的循环依赖

  1. 一级缓存:用于存放完全初始化好的Bean
  2. 二级缓存:存放原始的Bean对象(未填充属性),用于解决循环依赖
  3. 三级缓存:存放Bean工程对象,用于解决循环依赖

三、BeanPostProcessor和BeanFactoryPostProcessor

完成一个需求,根据视频编码类型选择不同的解码器进行解码 新增一个player包,增加一个枚举类VideoType

@Getter
@AllArgsConstructor
public enum VideoType {
    MP4("MP4"),
    WMV("WMV");

    private String desc;
}

新增一个接口IDecoder,定义两个方法type和decoder

public interface IDecoder {

    VideoType type();

    String decode(String data);
}

增加MP4Decoder和WMVDecoder两个类实现IDecoder接口,并用注解将这两个类标记为Spring Bean

@Service
public class WMVDecoder implements IDecoder {
    @Override
    public VideoType type() {
        return VideoType.WMV;
    }

    @Override
    public String decode(String data) {
        return this.type().getDesc() + ":" + data;
    }
}
@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

@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接口

@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

@Autowired
private DecoderManager decoderManager;

@Test
public void testUseDecoderManager() {

    log.info(decoderManager.decode(getRandomVideoType(), "video"));
}

执行测试方法

BeanPostProcessor是Bean的后置处理器,在Bean实例化之后执行,有两个回调方法

  • postProcessBeforeInitialization:Bean对象初始化之前回调
  • postProcessAfterInitializatioin:Bean对象初始化之后回调

在player包下增加一个ThirdPartyClass,第三方的类

@Service
public class ThirdPartyClass {
}

新增测试方法

@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,实现多实例

@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的生命周期中,但是场景和作用不同

四、@Transactional