Spring作为当下最流行的Java框架,相信很多小伙伴都在使用。我们都知道Spring中的Bean默认是单例的,也就是说整个Spring容器中只有一个实例,可以直接通过依赖注入使用,也可以在需要的地方直接从容器中获取。测试原型对于某些场景,我们可能需要对应的Bean作为原型。所谓原型,就是希望每次使用时都能得到一个新的对象实例,而不是单例。遇到这种情况,很多小伙伴不得不说不容易,只需要在对应的类上加上@scope注解,并将值设置为Prototype即可。如下所示:HelloService.javapackagecom.example.demo.service;importorg.springframework.beans.factory.config.ConfigurableBeanFactory;importorg.springframework.context.annotation.Scope;importorg.springframework.stereotype.Service;/***
*功能:
*作者:@authorziyou
*日期:2022-07-1721:20
*Desc:无
*/@Service@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)publicclassHelloService{publicStringsayHello(){return"hello:"+this.hashCode();}}HelloController.java代码如下:packagecom.example.demo.controller;importcom.example.demo.service.HelloService;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.beans.factory.annotation.Lookup;importorg.springframework.web.bind.annotation.GetMapping;importorg.springframework.web.bind.annotation.RestController;/***
*Function:
*作者:@authorziyou
*日期:2022-07-1715:43
*描述:无
*/@RestControllerpublicclassHelloController{@AutowiredprivateHelloService服务;@GetMapping(value="/hello")publicStringhello(){returnservice.sayHello();}}简要说明看一下上面的代码,这里我们对HelloService类使用了注解Scope,并将值设置为SCOPE_PROTOTYPE,表示它是一个原型类。在HelloController类中,我们调用了HelloService的sayHello方法,返回当前实例的hashcode我们通过访问http://127.0.0.1:8080/hello获取返回值。如果我们每次得到的值都不一样,说明上面的代码没有问题。每次我们获取它时,它都会使用一个新的HelloService实例。但是,在阿芬的电脑上,不管浏览器刷新多少次,最后的结果都没有改变。也就是说,这里引用的HelloService始终是一个,没有原型作用。那么问题来了,我们明明给HelloService类添加了原型注解,为什么这里没有效果呢?原因分析我们这样想。首先,我们通过浏览器访问界面时,访问的是HelloController类中的方法。那么,HelloController肯定是单例的,因为我们没有添加Scope的原型注解。示例HelloController中的HelloService属性是如何赋值的?当然,Spring在HelloController初始化的时候,通过依赖注入的方式帮我们赋值了。Spring注入依赖的赋值逻辑很简单,就是在创建bean的时候,如果发现有依赖注入,就会在容器中获取或者创建一个依赖bean。这时候属性对应的bean就是单例了。只会创建一个。如果对应的Bean是原型,则每次都会创建一个新的Bean,然后将创建的Bean赋给对应的属性。在我们的例子中,HelloService类是一个原型,因此在创建HelloControllerBean时,将创建一个HelloServiceBean并将其分配给服务属性;这里没有问题,但是因为我们的HelloControllerBean是单例的,初始化动作在整个生命周期只会发生一次,所以即使是HelloService类的原因,也只会被依赖注入一次,所以我们上面写达不到我们需要的效果。解决方案解决方案一写到这里,有些朋友就会想,如果我把HelloController类也设置为原型呢?这还不行吗?给HelloController加上注解@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)重启后,我们重新访问http://127.0.0.1:8080/hello,发现确实可以。也很好理解,因为此时HelloController是一个原型,所以每次访问都会创建一个新的实例,初始化时会在依赖中注入一个新的HelloService实例。但是不得不说这个方案很不优雅,而且把Controller类设置为原型也不友好,所以这里不推荐这个方案。方案二除了以HelloController为原型,我们还有其他方案。上面我们提到HelloController在初始化时会依赖于注入HelloService。能不能换个方式,让HelloController在创建的时候不依赖呢?注入HelloService,但是在真正需要的时候从容器中获取。如下:packagecom.example.demo.controller;importcom.example.demo.service.HelloService;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.context.ApplicationContext;importorg.springframework。web.bind.annotation.GetMapping;importorg.springframework.web.bind.annotation.RestController;/***
*功能:
*作者:@authorziyou
*日期:2022-07-1715:43
*描述:无
*/@RestControllerpublicclassHelloController{@自动装配私有ApplicationContextapplicationContext;@GetMapping(value="/hello")publicStringhello(){HelloServiceservice=getService();返回服务.sayHello();}publicHelloServicegetService(){returnapplicationContext.getBean(HelloService.class);}}也可以这样通过测试,每次从容器中取出来都会创建一个新的实例。方案三上述方案二比较常规。除了方案2,还有一个方案,就是使用Lookup注解。根据Spring官方文档,我们可以看到如下内容。简单的说,通过Lookup注解方法,可以被容器重写,然后通过BeanFactory返回一个指定类型的类实例,可以在单例类中使用,获取原型类。示例如下:packagecom.example.demo.controller;importcom.example.demo.service.HelloService;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.beans.factory.annotation。查找;导入org.springframework.context.ApplicationContext;导入org.springframework.web.bind.annotation.GetMapping;导入org.springframework.web.bind.annotation.RestController;/***
*功能:
*作者:@authorziyou
*日期:2022-07-1715:43
*描述:无
*/@RestControllerpublicclassHelloController{@GetMapping(value="/hello")publicStringhello(){HelloServiceservice=getService();返回服务.sayHello();}@LookuppublicHelloServicegetService(){返回空;第二个解决方案是相似的,除了它不是像我们展示的那样从容器中获取原型Bean实例,它使用Lookup注释来允许implementer帮我们重写对应的方法并返回一个原型实例对象。这里我们的getService方法可以直接返回null,因为里面的代码不会执行。我们打断点调试,会发现Lookup注解的方法最终走到了org.springframework.beans.factory.support.CglibSubclassingInstantiationStrategy.LookupOverrideMethodInterceptor#intercept这里。这里我们可以看到实例是从容器中动态获取的。但是我们需要注意一点,就是我们通过Lookup注解的方法是必须的,因为需要重写,所以我们只能对这个方法使用下面的时序定义,必须是public或者protected,这可以是抽象方法,方法不能有参数。
