Scope作用域
1. Scope类型有哪些
截至目前为止,Spring 目前有如下几种scope:
- singleton: 从ioc容器中返回的都是同一个对象
- prototype: 从ioc容器中可以返回多个对象
- request: 该类型的bean的生命周期就和request请求一样,每当有request请求发送过来,就会创建一个bean对象放入request域,请求结束之后bean生命周期会结束
- session: 会话域,会话开始,bean的生命周期开始;会话结束,bean的生命周期结束
- application: 应用程序域,程序启动,该bena会被创建;程序结束后,该bean会被销毁,在spring boot中,应用程序域指的是web servlet context
分别编写不同作用域的bean
@Scope("application")
@Component
public class BeanForApplication {private static final Logger log = LoggerFactory.getLogger(BeanForApplication.class);@PreDestroypublic void destroy(){log.debug("destroy");}
}
@Scope("request")
@Component
public class BeanForRequest {private static final Logger log = LoggerFactory.getLogger(BeanForRequest.class);@PreDestroypublic void destroy(){log.debug("destroy");}
}
@Scope("session")
@Component
public class BeanForSession {private static final Logger log = LoggerFactory.getLogger(BeanForSession.class);@PreDestroypublic void destroy(){log.debug("destroy");}
}
编写controller然后使用浏览器进行访问:
@RestController
public class MyController {@Lazy@Autowiredprivate BeanForRequest beanForRequest;@Lazy@Autowiredprivate BeanForSession beanForSession;@Lazy@Autowiredprivate BeanForApplication beanForApplication;@GetMapping(value = "/test", produces = "text/html")public String test(HttpServletRequest request, HttpSession session) throws JsonProcessingException {ServletContext context = request.getServletContext();String sb = "<ul>" +"<li>" + "request scope:"+ beanForRequest +"</li>"+"<li>" + "session scope:"+ beanForSession +"</li>"+"<li>" + "application scope:"+ beanForApplication +"</li>"+"</ul>";return sb;}
}
再次刷新浏览器
换一个浏览器重新访问:
我们发现,不同的request请求,其request scope是不一样的;不同的ssession会话,其session scope是不一样的。
如果把对应的@Lazy注解去掉之后重新启动,我们发现控制台会报错:
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'myController': Unsatisfied dependency expressed through field 'beanForRequest': Error creating bean with name 'beanForRequest': Scope 'request' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton
2. 在singleton中使用其它几种scope的注意事项
为了分析上面由于没有加@Lazy注解导致的报错,这里首先举一个例子
编写一个Bean
@Component
public class E {@Autowiredprivate F1 f1;public F1 getF1(){return f1;}
}@Scope("prototype")
@Component
class F1{}
在主方法中手动从IOC容器中多次获取bean对象
@SpringBootApplication
public class SpringScopeApplication {private static final Logger log = LoggerFactory.getLogger(SpringScopeApplication.class);public static void main(String[] args) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringScopeApplication.class);E e = context.getBean(E.class);log.info("{}",e.getF1());log.info("{}",e.getF1());log.info("{}",e.getF1());context.close();}
}
运行结果如下:
19:32:22.045 [main] INFO com.cherry.springscope.SpringScopeApplication -- com.cherry.springscope.bean.F1@58294867
19:32:22.048 [main] INFO com.cherry.springscope.SpringScopeApplication -- com.cherry.springscope.bean.F1@58294867
19:32:22.048 [main] INFO com.cherry.springscope.SpringScopeApplication -- com.cherry.springscope.bean.F1@58294867Process finished with exit code 0
这就是我们当从单例中获取多例属性对象时失效的问题。
3. scope 失效分析
对于单例对象而言,依赖注入仅会发生一次,后续再没有用到多例的F,因此 E 始终用的是第一次依赖注入的F
解决思路
- 仍然使用@Lazy注解生成代理
- 代理对象虽然还是同一个,但当每次调用代理对象的任一方法时,由代理对象创建新的F对象
- 通过对象工厂 ObjectFactory 获取bean对象
- 通过ApplicationContext获取bean对象
3.1 使用第一种方法(使用代理)
@Component
public class E {@Autowired@Lazyprivate F1 f1;public F1 getF1(){return f1;}
}@Scope("prototype")
@Component
class F1{ }
运行结果如下:
19:40:44.861 [main] INFO com.cherry.springscope.SpringScopeApplication -- com.cherry.springscope.bean.F1@44cb460e
19:40:44.866 [main] INFO com.cherry.springscope.SpringScopeApplication -- com.cherry.springscope.bean.F1@59546cfe
19:40:44.867 [main] INFO com.cherry.springscope.SpringScopeApplication -- com.cherry.springscope.bean.F1@2d2acd89Process finished with exit code 0
3.2 使用第二种方法(使用代理)
可以在@Scope注解中指定proxyMode为ScopedProxyMode.TARGET_CLASS
如:
@Component
public class E {@Autowired@Lazyprivate F1 f1;@Autowiredprivate F2 f2;public F1 getF1(){return f1;}public F2 getF2(){return f2;}
}@Scope("prototype")
@Component
class F1{}@Scope(value = "prototype",proxyMode = ScopedProxyMode.TARGET_CLASS)
@Component
class F2{ }
@SpringBootApplication
public class SpringScopeApplication {private static final Logger log = LoggerFactory.getLogger(SpringScopeApplication.class);public static void main(String[] args) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringScopeApplication.class);E e = context.getBean(E.class);log.info("{}",e.getF1());log.info("{}",e.getF1());log.info("{}",e.getF1());log.info("{}",e.getF2());log.info("{}",e.getF2());log.info("{}",e.getF2());context.close();}
}
运行结果如下:
19:48:26.218 [main] INFO com.cherry.springscope.SpringScopeApplication -- com.cherry.springscope.bean.F1@29a69a35
19:48:26.223 [main] INFO com.cherry.springscope.SpringScopeApplication -- com.cherry.springscope.bean.F1@4d48bd85
19:48:26.223 [main] INFO com.cherry.springscope.SpringScopeApplication -- com.cherry.springscope.bean.F1@58a120b0
19:48:26.223 [main] INFO com.cherry.springscope.SpringScopeApplication -- com.cherry.springscope.bean.F2@5c748168
19:48:26.226 [main] INFO com.cherry.springscope.SpringScopeApplication -- com.cherry.springscope.bean.F2@6441c486
19:48:26.226 [main] INFO com.cherry.springscope.SpringScopeApplication -- com.cherry.springscope.bean.F2@834831bProcess finished with exit code 0
3.3 使用第三种办法(不使用代理)
声明一个对象工厂类型的成员变量,在每次创建该对象时直接从工厂中创建即可:
@Component
public class E {@Autowiredprivate ObjectFactory<F3> f3;public F3 getF3(){return f3.getObject();}
}@Scope(value = "prototype")
@Component
class F3{ }
@SpringBootApplication
public class SpringScopeApplication {private static final Logger log = LoggerFactory.getLogger(SpringScopeApplication.class);public static void main(String[] args) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringScopeApplication.class);E e = context.getBean(E.class);log.info("{}", e.getF3());log.info("{}", e.getF3());log.info("{}", e.getF3());context.close();}
}
测试如下:
19:55:21.144 [main] INFO com.cherry.springscope.SpringScopeApplication -- com.cherry.springscope.bean.F3@2301b75
19:55:21.160 [main] INFO com.cherry.springscope.SpringScopeApplication -- com.cherry.springscope.bean.F3@6e1d4137
19:55:21.160 [main] INFO com.cherry.springscope.SpringScopeApplication -- com.cherry.springscope.bean.F3@29a4f594Process finished with exit code 0
3.4 使用第四种方法(不使用代理)
通过注入ApplicationContext对象获取多例对象
@Component
public class E {@Autowiredprivate ApplicationContext context;public F4 getF4(){return context.getBean(F4.class);}}@Scope(value = "prototype")
@Component
class F4{
}
@SpringBootApplication
public class SpringScopeApplication {private static final Logger log = LoggerFactory.getLogger(SpringScopeApplication.class);public static void main(String[] args) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringScopeApplication.class);E e = context.getBean(E.class);log.info("{}", e.getF4());log.info("{}", e.getF4());log.info("{}", e.getF4());context.close();}
}
20:02:07.541 [main] INFO com.cherry.springscope.SpringScopeApplication -- com.cherry.springscope.bean.F4@5327a06e
20:02:07.548 [main] INFO com.cherry.springscope.SpringScopeApplication -- com.cherry.springscope.bean.F4@57d0fc89
20:02:07.548 [main] INFO com.cherry.springscope.SpringScopeApplication -- com.cherry.springscope.bean.F4@58294867Process finished with exit code 0
上述的四种解决方案同样适用于单例对象使用request scope, session scope 等失效的问题。
不管什么方法,都是在运行时推迟其它scope bean的获取!