实际问题
项目中,报表导出涉及到了在同一个类的两个不同方法中,都有相同的查询数据库的操作,一个方法是用于获取内容,一个是用于获取条数的,大概类似于这样:
@Service
public class MyReportExporter extends AbstractReportExporter{
@Override
protected DataResp getData(Param param) {
List records = myService.queryList(param);//查询db
return wrapResp(records);
}
@Override
protected int getCount(Param param) {
return myService.queryList(param).size();//查询db
}
}
由于是继承的父类统一处理,因此没办法单独优化这个步骤。在父类的统一处理过程中,会多次调用getCount方法,这样每处理一次,就需要多次查询数据库。
这是会想到,可以用私有全局变量将查询结果存起来。
使用原型
在Spring中,@Service默认都是单例的。用了私有全局变量,若不想影响下次请求,就需要用到原型模式,即@Scope(“prototype”)
所谓单例,就是Spring的IOC机制只创建该类的一个实例,每次请求,都会用这同一个实例进行处理,因此若存在全局变量,本次请求的值肯定会影响下一次请求时该变量的值。
原型模式,指的是每次调用时,会重新创建该类的一个实例,比较类似于我们自己自己new的对象实例。
通过查看@Scope我们可以看到,默认的模式:singleton
public @interface Scope {
String value() default ConfigurableBeanFactory.SCOPE_SINGLETON;
...
}
通过如下方式,可以将该类设置为原型模式
@Service
@Scope("prototype")
public class MyReportExporterextends AbstractReportExporter{
...
}
prototype陷阱
在进行以上改动后,运行发现并没有生效,依然是一个实例。这说明只加一个@Scope注解还不够。
在调用改service的controller层,是这样注入的:
@Autowired
private MyReportExporter myReportExporter;
而controller同样是默认单例的,因此只实例化了一个controller对象,在其中依赖注入的MyReportExporter对象也就只会实例化一次。
在不想改变controller单例模式的情况下,可以如下修改:
放弃使用@Autowired方式,改用getBean方式:
private static ApplicationContext applicationContext;
MyReportExporter myReportExporter = applicationContext.getBean(MyReportExporter.class);
可以自己写个Spring工厂类,如下:
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import com.quhuhu.cesar.common.utils.LogUtils;
public class SpringBeanFactory implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
/**
* 获取某个Bean的对象
*/
public static <T> T getBean(Class<T> clazz) {
try {
return applicationContext.getBean(clazz);
} catch (Exception e) {
LogUtils.errorMail("Spring getBean:" + clazz, e);
}
return null;
}
}
然后,通过如下方式调用:
SpringBeanFactory.getBean(MyReportExporter.class).doSth()
修改后,运行OK,达到自己想要的结果。
小结
Spring中依赖注入的默认对象为单例形式,@Scope(“prototype”)注解可以将其改变为原型模式。
改变底层(如service层)的对象为原型时,同时改变上层调用层(如controller层)的调用方式,原型模式才会生效。
注:多谢评论中指出,应该是有更好的方式:lookup-method ,专门就是为了解决这个问题的,就是看起来bean的配置会多不少,大家可以移步。