前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >你有没有掉进去过这些Spring的“陷阱“(下)

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

作者头像
RiemannHypothesis
发布2022-08-19 16:36:51
4230
发布2022-08-19 16:36:51
举报
文章被收录于专栏:Elixir

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

  1. 一级缓存:用于存放完全初始化好的Bean
  2. 二级缓存:存放原始的Bean对象(未填充属性),用于解决循环依赖
  3. 三级缓存:存放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的生命周期中,但是场景和作用不同

四、@Transactional

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-01-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、Bean注入异常
  • 二、Bean循环依赖
  • 三、BeanPostProcessor和BeanFactoryPostProcessor
  • 四、@Transactional
相关产品与服务
文件存储
文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档