Spring @Lookup实现单例bean依赖注入原型bean

作者:simoscode

地址:https://www.jianshu.com/p/5254e1947d77

大多数场景,在Spring容器的大多数bean都是单例的.当一个单例bean A依赖另一个单例bean B,直接在A中定义一个属性与bean B类型一样,然后通过setter方法注入或者构造函数参数注入即可.但是当bean的生命周期不一样就会有问题。

比如一个单例bean A需要使用一个非单例(原型)bean B,A每次方法调用都需要一个新的bean B.容器只创建单例bean一次,这样只有一次机会设置这个值.容器不能给bean A提供一个新的bean B实例在bean A需要的时候.如何解决这个问题呢?

Spring 给我提供两种解决方法,如下:

*  一种解决的方法就是放弃依赖注入.你可以让bean A通过实现`ApplicationContextAware`接口并且在
    bean A每次需要bean B的时候通过调用getBean("B")向容器请求一个新的bean B实例
*  另外一种方法是使用`@Lookup`注解

考虑一下这个场景:假如我们有大量的消息需要推送,为了提高性能,我们会使用一个任务池去实现,每个需要推送的消息就是一个任务.从这个业务场景中,我们至少可以提取几个bean,一个是实现推送(阿里云移动推送,苹果apns等)的单例bean,发送消息任务原型bean,推送组件(任务池)单例bean,还有一个是业务逻辑层的推送单例bean(这个bean依赖推送组件bean).我们用两种方法实现。

实现推送(阿里云移动推送,苹果apns等)的单例bean

package com.simos.service;

import org.springframework.stereotype.Service;

/**
* Created by l2h on 18-4-25.
* Desc:模拟真正实现推送功能的底层类
* @author l2h
*/
  @Service
  public class PushService {
public void pushMsg(String msg){
    System.out.println(msg);
}
  }

发送消息任务原型bean

/**
* Created by l2h on 18-4-25.
* Desc: 推送消息任务
* @author l2h
*/
 @Service("task")
 @Scope(SCOPE_PROTOTYPE)
public class PushMsgTask implements  Runnable{
private String msg ;
public PushMsgTask(){
}
public PushMsgTask(String msg){
    this.msg = msg;
}
@Autowired
PushService pushService;
@Override
public void run() {
    pushService.pushMsg(msg);
}
public void  setMsg(String msg){
    this.msg = msg;
}
}   

通过实现ApplicationContextAware接口单例bean中获取原型bean

package com.simos.service;

import org.apache.tomcat.util.threads.TaskQueue;
import org.apache.tomcat.util.threads.TaskThreadFactory;
import org.apache.tomcat.util.threads.ThreadPoolExecutor;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

/**
 * Created by l2h on 18-4-25.
 * Desc:消息推送任务池组件.使用aware,这样业务代码就依赖了Spring框架
 * @author l2h
 */
@Service
public class AwarePushMsgPool implements ApplicationContextAware{
private ApplicationContext applicationContext;

@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    this.applicationContext = applicationContext;
}

/**
 * 线程池
 */
private ThreadPoolExecutor executorService;
/**
 * 任务队列
 */
private TaskQueue taskqueue ;
/**
 * 最大队列数量.通常配置在配置文件中.这里样例代码不加太多东西.
 * 简单点使用@value注入,复杂点像springboot一样@Configuration+@ConfigurationProperties
 */
private final int acceptCount = 10000;
/**
 *核心线程数
 */
private final int corePoolSize = 20;
/**
 * 最大线程数
 */
private final int maxPoolSize = 100;
/**
 * 线程保活时间
 */
private final int keepAliveTime =60;
public AwarePushMsgPool(){
    taskqueue = new TaskQueue(acceptCount);
    TaskThreadFactory tf =  new TaskThreadFactory("simos-pool-msg-",true,Thread.NORM_PRIORITY);
    executorService = new ThreadPoolExecutor(corePoolSize,maxPoolSize,keepAliveTime, TimeUnit.SECONDS,
            taskqueue, tf);
    executorService.setThreadRenewalDelay(org.apache.tomcat.util.threads.Constants.DEFAULT_THREAD_RENEWAL_DELAY);
    taskqueue.setParent(executorService);
}
public void pushMsg(String msg){
    if (msg!=null){
        try {
            //所需要的原型bean不是通过依赖注入的,而是直接bean容器拿到的,违反了IoC原则
            PushMsgTask task = pushMsgTask(msg);
            task.setMsg(msg);
            System.out.println("aware class:"+this.getClass());
            executorService.submit(task);
        }

        catch (Exception exception){
            System.out.println("推送失败,失败原因:"+exception.getMessage());
        }

    }
}
protected PushMsgTask pushMsgTask(String msg){
    PushMsgTask task = applicationContext.getBean("task",PushMsgTask.class);
    task.setMsg(msg);
    return task;
}
}

通过实现@Lookup接口单例bean中获取原型bean

package com.simos.service;

import org.apache.tomcat.util.threads.TaskQueue;
import org.apache.tomcat.util.threads.TaskThreadFactory;
import org.apache.tomcat.util.threads.ThreadPoolExecutor;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Lookup;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

/**
 * Created by l2h on 18-4-25.
 * Desc:消息推送任务池组件
 * @author l2h
 */
@Service
public class LookupPushMsgPool {
/**
 * 线程池
 */
private ThreadPoolExecutor executorService;
/**
 * 任务队列
 */
private TaskQueue taskqueue ;
/**
 * 最大队列数量.通常配置在配置文件中.这里样例代码不加太多东西.
 * 简单点使用@value注入,复杂点像springboot一样@Configuration+@ConfigurationProperties
 */
private final int acceptCount = 10000;
/**
 *核心线程数
 */
private final int corePoolSize = 20;
/**
 * 最大线程数
 */
private final int maxPoolSize = 100;
/**
 * 线程保活时间
 */
private final int keepAliveTime =60;
public LookupPushMsgPool(){
    taskqueue = new TaskQueue(acceptCount);
    TaskThreadFactory tf =  new TaskThreadFactory("simos-pool-msg-",true,Thread.NORM_PRIORITY);
    executorService = new ThreadPoolExecutor(corePoolSize,maxPoolSize,keepAliveTime, TimeUnit.SECONDS,
            taskqueue, tf);
    executorService.setThreadRenewalDelay(org.apache.tomcat.util.threads.Constants.DEFAULT_THREAD_RENEWAL_DELAY);
    taskqueue.setParent(executorService);
}
public void pushMsg(String msg){
    if (msg!=null){
        try {
            System.out.println("lookup class:"+this.getClass());
            PushMsgTask task = pushMsgTask(msg);
            executorService.submit(task);
        }

        catch (Exception exception){
            System.out.println("推送失败,失败原因:"+exception.getMessage());
        }

    }
}
@Lookup
protected PushMsgTask pushMsgTask(String msg){
    return  new PushMsgTask(msg);
}
}

通过对比LookupPushMsgPool与AwarePushMsgPool实现可以看出,AwarePushMsgPool通过实现ApplicationContextAware接口,从而得到动态获取容器里面bean的能力,违反了依赖注入的原则,业务代码耦合了Spring框架,实现了Spring框架的接口,通常我们业务bean不应该去实现Spring的接口,这种方法虽然实现了功能,但是不建议这么使用.而通过@Lookup方法注入,就是依赖注入,不需要去实现特定接口什么的.

@Lookup方法注入实现简介

   @Lookup
protected PushMsgTask pushMsgTask(String msg){
    return  new PushMsgTask(msg);
   }

   protected PushMsgTask pushMsgTask(String msg){
    PushMsgTask task = applicationContext.getBean("task",PushMsgTask.class);
    task.setMsg(msg);
    return task;
}

通过对比发现,被@Lookup注解的pushMsgTask(String msg)方法帮我们实现的功能就是等价于AwarePushMsgPool的pushMsgTask(String msg).包含@Lookup注解方法的类,容器初始化的时候会通过cglib字节码库动态生成一个LookupPushMsgPool的子类,并且会覆盖父类的实现,子类的pushMsgTask方法实现等价于AwarePushMsgPool的pushMsgTask(String msg).下图是打印结果说明了这一点.

样例代码传送门:https://github.com/simos-code/springboot-quick-start/tree/lookup

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Linyb极客之路

SpringBoot自定义Starter

3) 输入GroupId、ArtifactId 和 Version 信息,点击Finish

33540
来自专栏A周立SpringCloud

Light Security 1.0.1发布

Light Security是一款简洁而不简单的权限控制框架,基于 jwt ,支持与 Spring Boot 配合使用。

11020
来自专栏Java技术栈

Spring Cloud Eureka 常用配置详解,建议收藏!

前几天,栈长分享了 《Spring Cloud Eureka 注册中心集群搭建,Greenwich 最新版!》,今天来分享下 Spring Cloud Eure...

25920
来自专栏Linyb极客之路

Spring Boot中如何干掉过多的if else!

这里虚拟一个业务需求,让大家容易理解。假设有一个订单系统,里面的一个功能是根据订单的不同类型作出不同的处理。

16420
来自专栏Java那些事

3月Github最热门的10个Java开源项目

•Github 地址: https://github.com/Snailclimb/JavaGuide[1]•Star: 32.9k (6,196 stars ...

26120
来自专栏A周立SpringCloud

Spring Cloud Kubernetes 指南

当我们构建微服务解决方案时,SpringCloud和Kubernetes都是最佳解决方案,因为它们为解决最常见的挑战提供组件。但是,如果我们决定选择Kubern...

25810
来自专栏java达人

透过源码学习设计模式3—BeanFactory、FactoryBean和工厂模式

简单工厂模式的实质是由一个工厂类根据传入的参数,动态决定应该创建哪一个产品类,简单工厂是“固定的”,因为只有一个实现,没有子类。它不属于23种设计模式之一,但在...

23720
来自专栏猿天地

开源 Spring Boot 中 Mongodb 多数据源扩展框架

在日常工作中,我们通过Spring Data Mongodb来操作Mongodb数据库,在Spring Boot中只需要引入spring-boot-starte...

25420
来自专栏Java那些事

Java学习必备书籍推荐终极版!

很早就想把书单更新一下了,昨晚加今天早上花了几个时间对之前的书单进行了分类和补充完善。虽是终极版,但一定还有很多不错的 Java 书籍我没有添加进去,会继续完善...

29620
来自专栏java达人

透过源码学习设计模式2—Spring ProxyFactory和代理模式

代理就像我们生活中的房产中介,你不直接与房主,银行接触,而是通过中介与他们沟通联系。 代理的结构如图所示:

18840

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励