• Spring bean的作用域及作用域代理和对应示例


    bean的作用域

    spring组件的注解Scope大约有singleton、prototype、request、session、global session 这么几种常用的场景。该注解可以配合@Component和@Bean一起使用。这里需要特别说明一下,根据源代码显示 Scope注解分为ConfigurableBeanFactory和WebApplicationContext两个大类,

    ConfigurableBeanFactory包含(singleton、prototype)两种

    WebApplicationContext有(ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,request,session,application,servletContext,contextParameters,contextAttributes)这么几种Scope

    • ConfigurableBeanFactory.SCOPE_PROTOTYPE,即“prototype”
    • ConfigurableBeanFactory.SCOPE_SINGLETON,即“singleton”
    • WebApplicationContext.SCOPE_REQUEST,即“request”
    • WebApplicationContext.SCOPE_SESSION,即“session”

    他们的含义是:

    • singleton和prototype分别代表单例和多例(原型);
    • request表示请求,即在一次http请求中,被注解的Bean都是同一个Bean,不同的请求是不同的Bean;
    • session表示会话,即在同一个会话中,被注解的Bean都是使用的同一个Bean,不同的会话使用不同的Bean。

    使用session和request产生了一个新问题,生成controller的时候需要service作为controller的成员,但是service只在收到请求(可能是request也可能是session)时才会被实例化,controller拿不到service实例。为了解决这个问题,@Scope注解添加了一个proxyMode的属性,有两个值ScopedProxyMode.INTERFACES和ScopedProxyMode.TARGET_CLASS,前一个表示表示Service是一个接口,后一个表示Service是一个类。

    作用域代理

    对于bean的作用域,有一个典型的电子商务应用:需要有一个bean代表用户的购物车。
    如果购物车是单例,那么将会导致所有的用户都往一个购物车中添加商品。
    如果购物车是原型作用域的,那么在应用中某个地方往购物车中添加商品,然后到应用中的另外一个地方可能就没法使用了,因为在这里被注入了另外一个原型作用域的的购物车。
    就购物车bean而言,会话作用域是最合适的,因为他与给定用户的关联性最大。

    @Component
    @Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode =ScopedProxyMode.INTERFACES)
    public class ShoppingCart {
        //todo: dosomething
    }
    

    这里我们将value设置成了WebApplicationContext.SCOPE_SESSION常量。这会告诉Spring 为Web应用的每个会话创建一个ShoppingCart。这会创建多个ShoppingCart bean的实例。但是对于给定的会话只会创建一个实例,在当前会话各种操作中,这个bean实际上相当于单例的。

    注意的是,@Scope中使用了proxyMode属性,被设置成了ScopedProxyMode.INTERFACES。这个属性是用于解决将会话或请求作用域的bean注入到单例bean中所遇到的问题。上小结已部分描述,下面将详细阐述其过程。

    假设我们将ShoppingCart bean注入到单例StoreService bean的setter方法中:

    @Component
    public class StoreService {
        
        private ShoppingCart shoppingCart;
            
        public void setShoppingCart(ShoppingCart shoppingCart) {
            this.shoppingCart = shoppingCart;
        }
        //todo: dosomething
    }
    

    因为StoreService 是个单例bean,会在Spring应用上下文加载的时候创建。当它创建的时候,Spring会试图将ShoppingCart bean注入到setShoppingCart()方法中。但是ShoppingCart bean是会话作用域,此时并不存在。直到用户进入系统创建会话后才会出现ShoppingCart实例。

    另外,系统中会有多个ShoppongCart 实例,每个用户一个。我们并不希望注入固定的ShoppingCart实例,而是希望当StoreService 处理购物车时,它所使用的是当前会话的ShoppingCart实例。

    Spring并不会将实际的ShoppingCart bean注入到StoreService,Spring会注入一个ShoppingCart bean的代理。这个代理会暴露与ShoppingCart相同的方法,所以StoreService会认为它就是一个购物车。但是,当StoreService调用ShoppingCart的方法时,代理会对其进行懒解析并将调用委任给会话作用域内真正的ShoppongCart bean。

    • 在上面的配置中,proxyMode属性,被设置成了ScopedProxyMode.INTERFACES,这表明这个代理要实现ShoppingCart接口,并将调用委托给实现bean。
    • 但如果ShoppingCart是一个具体的类而不是接口的话,Spring就没法创建基于接口的代理了。此时,它必须使用CGLib来生成基于类的代理。所以,如果bean类型是具体类的话我们必须要将proxyMode属性,设置成ScopedProxyMode.TARGET_CLASS,以此来表明要以生成目标类扩展的方式创建代理。

    请求作用域的bean应该也以作用域代理的方式进行注入。

    如果你需要使用xml来声明会话或请求作用域的bean,那么就需要使用<aop:scoped-proxy />元素来指定代理模式。

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
           xmlns:aop="http://www.springframework.org/schema/aop"
           xsi:schemaLocation="http://www.springframework.org/schema/aop
            http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
            http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
            
      <bean id="cart" class="com.xx.ShoppingCart" scope="session"/>
      <aop:scoped-proxy />
      
    </beans>
    
    <aop:scoped-proxy />
    

    是与@Scope注解的proxyMode属性相同的xml元素。它会告诉Spring为bean创建一个作用域代理。默认情况下,它会使用CGLib创建目标类的代理,如果要生成基于接口的代理可以将proxy-target-class属性设置成false,如下:

    <bean id="cart" class="com.xx.ShoppingCart" scope="session"/>
    <aop:scoped-proxy proxy-target-class="false"/>
    

    request和session作用域示例

    TestScopeApplication.java

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    @SpringBootApplication
    public class TestScopeApplication {
    
    	public static void main(String[] args) {
    		// TODO Auto-generated method stub
    		SpringApplication.run(TestScopeApplication.class, args);
    	}
    
    }
    

    TestScopeController.java

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class TestScopeController {
    
    	@Autowired
    	TestScopeSessionService testScopeSessionService;
    
    	@Autowired
    	TestScopeRequestService testScopeRequestService1;
    
    	@Autowired
    	TestScopeRequestService testScopeRequestService2;
    
    	@RequestMapping(value = "scope/session/{username}", method = RequestMethod.GET)
    	public void testScopeSession(@PathVariable("username") String username) {
    		String id = testScopeSessionService.getId();
    		System.out.println("scope-->session-->" + Thread.currentThread().getId() + "-->" + id);
    
    	}
    
    	@RequestMapping(value = "scope/request/{username}", method = RequestMethod.GET)
    	public void testScopeRequest(@PathVariable("username") String username) {
    		String id = testScopeRequestService1.getId();
    		System.out.println("scope-->request-->" + Thread.currentThread().getId() + "-->" + id);
    
    		id = testScopeRequestService2.getId();
    		System.out.println("scope-->request-->" + Thread.currentThread().getId() + "-->" + id);
    
    	}
    
    }
    

    TestScopeRequestService .java

    public interface TestScopeRequestService {
    	
    	public String getId();
    }
    

    TestScopeRequestServiceImpl.java

    import java.util.UUID;
    
    import org.springframework.context.annotation.Scope;
    import org.springframework.context.annotation.ScopedProxyMode;
    import org.springframework.stereotype.Component;
    import org.springframework.web.context.WebApplicationContext;
    @Component
    @Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.INTERFACES)
    public class TestScopeRequestServiceImpl implements TestScopeRequestService {
    
    	private UUID uuid;
    
    	public TestScopeRequestServiceImpl() {
    		uuid = UUID.randomUUID();
    	}
    
    	public String getId() {
    		return uuid.toString();
    	}
    }
    

    TestScopeSessionService .java

    public interface TestScopeSessionService {
    	
    	public String getId();
    }
    

    TestScopeSessionServiceImpl .java

    import java.util.UUID;
    
    import org.springframework.context.annotation.Scope;
    import org.springframework.context.annotation.ScopedProxyMode;
    import org.springframework.stereotype.Component;
    import org.springframework.web.context.WebApplicationContext;
    @Component
    @Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.INTERFACES)
    public class TestScopeSessionServiceImpl implements TestScopeSessionService {
    
    	private UUID uuid;
    
    	public TestScopeSessionServiceImpl() {
    		uuid = UUID.randomUUID();
    	}
    
    	public String getId() {
    		return uuid.toString();
    	}
    }
    

    测试

    • 访问http://localhost:8043/scope/request/aa、http://localhost:8043/scope/request/aa

    scope–>request–>20–>496c46d6-b9b1-42db-9820-35ea662b5501
    scope–>request–>20–>496c46d6-b9b1-42db-9820-35ea662b5501
    scope–>request–>24–>1438d13f-760d-4774-aa04-c643003c2dee
    scope–>request–>24–>1438d13f-760d-4774-aa04-c643003c2dee

    早一次http请求中,被注解的Bean都是同一个Bean,因此id值相同

    • 访问http://localhost:8043/scope/prototype/aa、http://localhost:8043/scope/prototype/bb
    • 切换其他浏览器访问http://localhost:8043/scope/prototype/cc

    scope–>session–>27–>52198050-c53f-46da-bb08-010adaf326d5
    scope–>session–>18–>8e661356-452b-44b7-b723-52b7be6b4a78
    scope–>session–>25–>8e661356-452b-44b7-b723-52b7be6b4a78

    在同一个会话中,被注解的Bean都是使用的同一个Bean,不同的会话使用不同的Bean

    prototype作用域示例

    import org.springframework.context.annotation.Scope;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    @Scope("prototype")
    public class TestScopePrototypeController {
    
    	private int index = 0; // 非静态
    
    	@RequestMapping(value = "scope/prototype/{username}", method = RequestMethod.GET)
    	public void testScopePrototype(@PathVariable("username") String username) {
    		System.out.println("scope-->prototype-->" + Thread.currentThread().getId() + "-->" + index++);
    	}
    }
    
    • 访问http://localhost:8043/scope/prototype/aa五次

    scope–>prototype–>27–>0
    scope–>prototype–>18–>0
    scope–>prototype–>20–>0
    scope–>prototype–>21–>0
    scope–>prototype–>24–>0

    每次注入或者通过上下文获取的时候,都会创建一个新的bean实例

    TestBean.java

    import org.springframework.context.annotation.Scope;
    import org.springframework.stereotype.Component;
    
    @Component
    @Scope("prototype")
    public class TestBean {
    
    	
    }
    

    TestConfiguration.java

    
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    @ComponentScan(basePackages = "com.gm.test")
    public class TestConfiguration {
    
    	public TestConfiguration() {
    		System.out.println("---	TestConfiguration --- 初始化完成");
    	}
    
    }
    

    TestMain.java

    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    
    public class TestMain {
    
    	public static void main(String[] args) {
    		// TODO Auto-generated method stub
    		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    		context.register(TestConfiguration.class);
    		context.refresh();
    
    		// 获取bean
    		TestBean tb = (TestBean) context.getBean("testBean");
    		System.out.println(tb.toString());
    
    		// 获取bean
    		TestBean tb2 = (TestBean) context.getBean("testBean");
    		System.out.println(tb2.toString());
    
    	}
    
    }
    

    在这里插入图片描述

    • 原型的特殊情况

    假如 Service是多例的,但是Controller是单例的。如果给一个组件加上@Scope(“prototype”)注解,每次请求它的实例,spring的确会给返回一个新的。问题是这个多例对象Service是被单例对象Controller依赖的。而单例服务Controller初始化的时候,多例对象Service就已经注入了;当你去使用Controller的时候,Service也不会被再次创建了(注入时创建,而注入只有一次)。

  • 相关阅读:
    Linux 定时任务
    VIM快速使用
    ulimit 命令
    PG语句
    linux下的PGSQL安装步骤
    linux下安装mysql 5.5和5.6.32
    linux下安装达梦数据库(DM8)x86版本
    redis配置
    sql server 2008安装
    linux下安装部署oracle
  • 原文地址:https://www.cnblogs.com/gmhappy/p/13457044.html
Copyright © 2020-2023  润新知