5_Spring Bean Scope 失效分析

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);

    @PreDestroy
    public void destroy(){
        log.debug("destroy");
    }
}
@Scope("request")
@Component
public class BeanForRequest {
    private static final Logger log = LoggerFactory.getLogger(BeanForRequest.class);

    @PreDestroy
    public void destroy(){
        log.debug("destroy");
    }
}
@Scope("session")
@Component
public class BeanForSession {
    private static final Logger log = LoggerFactory.getLogger(BeanForSession.class);

    @PreDestroy
    public void destroy(){
        log.debug("destroy");
    }
}

编写controller然后使用浏览器进行访问:

@RestController
public class MyController {

    @Lazy
    @Autowired
    private BeanForRequest beanForRequest;

    @Lazy
    @Autowired
    private BeanForSession beanForSession;

    @Lazy
    @Autowired
    private 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;
    }
}

image-20240626191826404

再次刷新浏览器

image-20240626191848244

换一个浏览器重新访问:

image-20240626191919991

我们发现,不同的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 {

    @Autowired
    private 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@58294867

Process finished with exit code 0

这就是我们当从单例中获取多例属性对象时失效的问题。

3. scope 失效分析

对于单例对象而言,依赖注入仅会发生一次,后续再没有用到多例的F,因此 E 始终用的是第一次依赖注入的F

Snipaste_2024-06-26_19-37-33

解决思路

  • 仍然使用@Lazy注解生成代理
  • 代理对象虽然还是同一个,但当每次调用代理对象的任一方法时,由代理对象创建新的F对象

image-20240626193926862

  • 通过对象工厂 ObjectFactory 获取bean对象
  • 通过ApplicationContext获取bean对象

3.1 使用第一种方法(使用代理)

@Component
public class E {

    @Autowired
    @Lazy
    private 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@2d2acd89

Process finished with exit code 0

3.2 使用第二种方法(使用代理)

可以在@Scope注解中指定proxyMode为ScopedProxyMode.TARGET_CLASS

如:

@Component
public class E {

    @Autowired
    @Lazy
    private F1 f1;

    @Autowired
    private 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@834831b

Process finished with exit code 0	

3.3 使用第三种办法(不使用代理)

声明一个对象工厂类型的成员变量,在每次创建该对象时直接从工厂中创建即可:

@Component
public class E {
    
    @Autowired
    private 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@29a4f594

Process finished with exit code 0

3.4 使用第四种方法(不使用代理)

通过注入ApplicationContext对象获取多例对象

@Component
public class E {

    @Autowired
    private 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@58294867

Process finished with exit code 0

上述的四种解决方案同样适用于单例对象使用request scope, session scope 等失效的问题。

不管什么方法,都是在运行时推迟其它scope bean的获取!

posted @ 2024-06-26 20:11  LilyFlower  阅读(1)  评论(0编辑  收藏  举报