Scope, scope proxy and corresponding examples of spring beans

Keywords: Session Spring Java xml

Scope of bean

The annotation Scope of spring Component has several common scenarios, such as singleton, prototype, request, session and global session. This annotation can be used with @ Component and @ Bean. In particular, Scope annotation can be divided into ConfigurableBeanFactory and WebApplicationContext according to the source code,

ConfigurableBeanFactory includes (singleton, prototype)

WebApplicationContext Yes(ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,request,session,application,servletContext,contextParameters,contextAttributes)So many kinds Scope

  • Configurablebeanfactory.scope'prototype
  • Configurablebeanfactory.scope'singleton '
  • Webapplicationcontext.scope'request, i.e. "request"
  • Webapplicationcontext.scope & session, i.e. "session"

They mean:

  • singleton and prototype respectively represent single case and multiple cases (prototype);
  • Request means request, that is, in an http request, the annotated beans are the same Bean, and different requests are different beans;
  • Session means session, that is, in the same session, the annotated beans are the same Bean, and different sessions use different beans.

A new problem arises when using session and request. When generating a controller, service is required to be a member of the controller. However, service is only instantiated when a request (maybe a request or a session) is received, and the controller cannot get a service instance. To solve this problem, @ Scope annotation adds a proxyMode attribute, which has two values, ScopedProxyMode.INTERFACES and scopedproxymode.target'class. The former indicates that service is an interface, and the latter indicates that service is a class.

Scope agent

For the scope of beans, there is a typical e-commerce application: there needs to be a shopping cart where beans represent users.
If the shopping cart is a single example, it will cause all users to add items to a shopping cart.
If the shopping cart is prototype scoped, adding items to the shopping cart somewhere in the application and then going to another place in the application may not be able to use it, because the shopping cart with another prototype scope is injected here.
As far as shopping cart bean s are concerned, session scope is the most appropriate because it is most relevant to a given user.

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

Here we set value to the webapplicationcontext.scope_sessionconstant. This tells Spring to create a ShoppingCart for each session of the Web application. This creates multiple instances of the ShoppingCart bean. But for a given session, only one instance will be created. In various operations of the current session, this bean is actually equivalent to a single instance.

Note that the proxyMode property is used in @ Scope and is set to ScopedProxyMode.INTERFACES. This property is used to solve the problem of injecting session or request scoped beans into singleton beans. The above summary has been described in part, and its process will be described in detail below.

Suppose we inject the ShoppingCart bean into the setter method of a single StoreService bean:

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

Because StoreService is a singleton bean, it will be created when the Spring application context is loaded. When it is created, Spring attempts to inject the ShoppingCart bean into the setsshoppingcart () method. However, the ShippingCart bean is session scoped and does not exist at this time. The ShippingCart instance does not appear until the user enters the system creation session.

In addition, there will be multiple ShippongCart instances in the system, one for each user. We do not want to inject a fixed ShoppingCart instance, but we want to use the ShoppingCart instance of the current session when StoreService processes the shopping cart.

Spring does not inject the actual ShoppingCart bean into the StoreService, and spring will inject a proxy for the ShoppingCart bean. This agent will expose the same method as ShoppingCart, so StoreService will think of it as a shopping cart. However, when StoreService calls ShoppingCart's method, the agent lazy resolves it and delegates the call to the real ShoppingCart bean in the session scope.

  • In the above configuration, the proxyMode property is set to ScopedProxyMode.INTERFACES, which indicates that the agent wants to implement the ShoppingCart interface and delegate the call to the implementation bean.
  • But if ShoppingCart is a concrete class rather than an interface, Spring can't create an interface based proxy. At this point, it must use CGLib to generate class based proxies. Therefore, if the bean type is a specific class, we must set the proxyMode property to scopedproxymode.target_classto indicate that we want to create the proxy in the way of generating the target class extension.

Beans requesting scopes should also be injected as scope proxies.

If you need to use xml to declare session or request scoped bean s, you need to use the < AOP: scoped proxy / > element to specify the proxy pattern.

<?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 />

Is the same xml element as the proxyMode attribute of the @ Scope annotation. It tells Spring to create a Scope proxy for the bean. By default, it uses CGLib to create a proxy of the target class. If you want to generate an interface based proxy, you can set the proxy target class property to false, as follows:

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

Sample request and session scopes

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

test

  • Visit 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

In an earlier http request, the annotated beans are the same Bean, so the id value is the same

  • Visit http://localhost:8043/scope/prototype/aa, http://localhost:8043/scope/prototype/bb
  • Switch other browsers to 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

In the same session, all annotated beans use the same Bean, and different sessions use different beans

Sample prototype scope

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; // Non static state

	@RequestMapping(value = "scope/prototype/{username}", method = RequestMethod.GET)
	public void testScopePrototype(@PathVariable("username") String username) {
		System.out.println("scope-->prototype-->" + Thread.currentThread().getId() + "-->" + index++);
	}
}
  • Visit http://localhost:8043/scope/prototype/aa five times

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

A new bean instance is created each time it is injected or retrieved through the context

  • Special cases of prototypes

If the Service is multi instance, but the Controller is single instance. If you annotate a component with @ Scope("prototype"), spring will indeed return a new instance every time it requests an instance. The problem is that the multi instance object Service is dependent on the single instance object Controller. When a single Service Controller is initialized, multiple object services have been injected; when you use the Controller, the Service will not be created again (created during injection, but only once).

275 original articles published, 155 praised, 1.08 million visitors+
His message board follow

Posted by olsrey on Tue, 25 Feb 2020 22:50:08 -0800