spring源码(二)自定义作用域

spring的作用域

spring的作用域限定了Spring Bean的作用范围,在Spring配置文件定义Bean时,通过声明scope属性配置项,可以灵活定义Bean的作用范围。例如,当你希望每次IOC容器返回的Bean是同一个实例时,可以设置scope为singleton;当你希望每次IOC容器返回的Bean实例是一个新的实例时,可以设置scope为prototype。

scope配置项有5个属性,用于描述不同的作用域。

  1. singleton

使用该属性定义Bean时,IOC容器仅创建一个Bean实例,IOC容器每次返回的是同一个Bean实例。

  1. prototype

使用该属性定义Bean时,IOC容器可以创建多个Bean实例,每次返回的都是一个新的实例。

  1. request

该属性仅对HTTP请求产生作用,使用该属性定义Bean时,每次HTTP请求都会创建一个新的Bean,适用于WebApplicationContext环境。

  1. session

该属性仅用于HTTP Session,同一个Session共享一个Bean实例。不同Session使用不同的实例。

  1. global-session

该属性仅用于HTTP Session,同session作用域不同的是,所有的Session共享一个Bean实例。

spring在什么时候会根据作用域判断是否产生新的实例

当你在看spring主流程的代码时,你会走读到AbstractBeanFactory这个类的doGetBean方法,在这个方法中

  • 先判断在单例缓存中是否已经有了该实例,如果已经有了就不用在创建了
  • 如果还不存在该实例bean,就会根据bean的作用域去创建对象
    具体代码:
// Create bean instance.
				if (mbd.isSingleton()) {//单例作用域下
					sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
						@Override
						public Object getObject() throws BeansException {
							try {
								return createBean(beanName, mbd, args);
							}
							catch (BeansException ex) {
								// Explicitly remove instance from singleton cache: It might have been put there
								// eagerly by the creation process, to allow for circular reference resolution.
								// Also remove any beans that received a temporary reference to the bean.
								destroySingleton(beanName);
								throw ex;
							}
						}
					});
					bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
				}

				else if (mbd.isPrototype()) {//原型作用域下
					// It's a prototype -> create a new instance.
					Object prototypeInstance = null;
					try {
						beforePrototypeCreation(beanName);
						prototypeInstance = createBean(beanName, mbd, args);
					}
					finally {
						afterPrototypeCreation(beanName);
					}
					bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
				}

				else {//其他作用域下
					String scopeName = mbd.getScope();------------------------------------1
					final Scope scope = this.scopes.get(scopeName);---------------------------2
					if (scope == null) {
						throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
					}
					try {
						Object scopedInstance = scope.get(beanName, new ObjectFactory<Object>() {--------------------------------------3
							@Override
							public Object getObject() throws BeansException {
								beforePrototypeCreation(beanName);
								try {
									return createBean(beanName, mbd, args);
								}
								finally {
									afterPrototypeCreation(beanName);
								}
							}
						});
						bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
					}
					catch (IllegalStateException ex) {
						throw new BeanCreationException(beanName,
								"Scope '" + scopeName + "' 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",
								ex);
					}
				}

其实Singleton和Prototype的条件下他们都是调用一样的createBean方法,不同的是Singleton会再创建好了bean之后会放到单例缓存中,下次就直接获取了。
这里主要分析下其他作用域下是怎么创建bean的:

1.mbd.getScope();这个方法的意思是从beandefinition中获取作用域,其实就是我们在xm文件中配置的:

<bean name="lzlBean" class="com.lzl.springscope.LzlBean" scope="lzlScope"></bean>

2.this.scopes.get(scopeName);这个方法是从map中获取实现了Scope接口的对象,我看了下有好几个
在这里插入图片描述
分别对应这个request、session、globalSession、application

public static void registerWebApplicationScopes(ConfigurableListableBeanFactory beanFactory, ServletContext sc) {
        beanFactory.registerScope("request", new RequestScope());
        beanFactory.registerScope("session", new SessionScope(false));
        beanFactory.registerScope("globalSession", new SessionScope(true));
        if (sc != null) {
            ServletContextScope appScope = new ServletContextScope(sc);
            beanFactory.registerScope("application", appScope);
            sc.setAttribute(ServletContextScope.class.getName(), appScope);
        }

        beanFactory.registerResolvableDependency(ServletRequest.class, new WebApplicationContextUtils.RequestObjectFactory());
        beanFactory.registerResolvableDependency(ServletResponse.class, new WebApplicationContextUtils.ResponseObjectFactory());
        beanFactory.registerResolvableDependency(HttpSession.class, new WebApplicationContextUtils.SessionObjectFactory());
        beanFactory.registerResolvableDependency(WebRequest.class, new WebApplicationContextUtils.WebRequestObjectFactory());
        if (jsfPresent) {
            WebApplicationContextUtils.FacesDependencyRegistrar.registerFacesDependencies(beanFactory);
        }
    }

发现scopes这个map对象是AbstractBeanFactory的registerScope方法

public void registerScope(String scopeName, Scope scope) {
		Assert.notNull(scopeName, "Scope identifier must not be null");
		Assert.notNull(scope, "Scope must not be null");
		if (SCOPE_SINGLETON.equals(scopeName) || SCOPE_PROTOTYPE.equals(scopeName)) {
			throw new IllegalArgumentException("Cannot replace existing scopes 'singleton' and 'prototype'");
		}
		Scope previous = this.scopes.put(scopeName, scope);
		if (previous != null && previous != scope) {
			if (logger.isInfoEnabled()) {
				logger.info("Replacing scope '" + scopeName + "' from [" + previous + "] to [" + scope + "]");
			}
		}
		else {
			if (logger.isDebugEnabled()) {
				logger.debug("Registering scope '" + scopeName + "' with implementation [" + scope + "]");
			}
		}
	}

既然是beanFactory的方法,我突然想到接口BeanFactoryPostProcessor有一个方法可以获得beanFactory的实例,我们用这个实例去调用注册作用域不就可以把自己定义的作用域注册进去。

public interface BeanFactoryPostProcessor {

	/**
	 * Modify the application context's internal bean factory after its standard
	 * initialization. All bean definitions will have been loaded, but no beans
	 * will have been instantiated yet. This allows for overriding or adding
	 * properties even to eager-initializing beans.
	 * @param beanFactory the bean factory used by the application context
	 * @throws org.springframework.beans.BeansException in case of errors
	 */
	void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;

}

3.作用域的scope.get方法是需要我们为自己的作用定义逻辑的地方,根据自己的逻辑判断去创建bean(即调用ObjectFactory的getObject()方法)

现在开始实现自己的作用域

创建自定义的作用域类,每访问5次创建一个新实例

public class LzlScope implements Scope {
    static AtomicInteger count = new AtomicInteger(0);
    static ConcurrentHashMap<String,Object> cache = new ConcurrentHashMap<>(64);
    @Override
    public Object get(String name, ObjectFactory<?> objectFactory) {
        Object result = null;
        int temp = count.getAndIncrement();
        if(temp%5 == 0){
            result = objectFactory.getObject();
            cache.put(name,result);
        }
        if(null == result){
            result = cache.get(name);
        }
        return result;
    }

    @Override
    public Object remove(String name) {
        return null;
    }

    @Override
    public void registerDestructionCallback(String name, Runnable callback) {

    }

    @Override
    public Object resolveContextualObject(String key) {
        return null;
    }

    @Override
    public String getConversationId() {
        return null;
    }
}

注册自定义作用域

public class LzlBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        beanFactory.registerScope("lzlScope",new LzlScope());
        beanFactory.createBean(Test.class);
    }
}

创建一个bean类

public class LzlBean {
    static AtomicInteger integer = new AtomicInteger(0);
    private String name;
    private String id;

    public LzlBean(){
        System.out.println("instance has create="+integer.incrementAndGet());
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }
}

创建自定义作用域的bean配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
      ">
    <bean class="com.lzl.springscope.LzlBeanFactoryPostProcessor"/>
    <!--<bean class="com.lzl.springscope.LzlContext"/>-->
    <bean name="lzlBean" class="com.lzl.springscope.LzlBean" scope="lzlScope"></bean>
</beans>

测试作用域代码

public class Test {
    public static int x=10;
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:application.xml");
        for(int i=0;i<20;i++){
            System.out.println(context.getBean("lzlBean"));
        }
//        System.out.println(context==LzlContext.context);
//        Test s= (Test)context.getBean("com.lzl.springscope.LzlBeanFactoryPostProcessor#0");
//        System.out.println((s).x);
        context.close();
    }
}

测试结果:

九月 02, 2019 6:01:52 下午 org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@685f4c2e: startup date [Mon Sep 02 18:01:52 CST 2019]; root of context hierarchy
九月 02, 2019 6:01:52 下午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from class path resource [application.xml]
instance has create=1
com.lzl.springscope.LzlBean@5056dfcb
com.lzl.springscope.LzlBean@5056dfcb
com.lzl.springscope.LzlBean@5056dfcb
com.lzl.springscope.LzlBean@5056dfcb
com.lzl.springscope.LzlBean@5056dfcb
instance has create=2
com.lzl.springscope.LzlBean@6574b225
com.lzl.springscope.LzlBean@6574b225
com.lzl.springscope.LzlBean@6574b225
com.lzl.springscope.LzlBean@6574b225
com.lzl.springscope.LzlBean@6574b225
instance has create=3
com.lzl.springscope.LzlBean@2669b199
com.lzl.springscope.LzlBean@2669b199
com.lzl.springscope.LzlBean@2669b199
com.lzl.springscope.LzlBean@2669b199
com.lzl.springscope.LzlBean@2669b199
instance has create=4
com.lzl.springscope.LzlBean@2344fc66
com.lzl.springscope.LzlBean@2344fc66
com.lzl.springscope.LzlBean@2344fc66
com.lzl.springscope.LzlBean@2344fc66
com.lzl.springscope.LzlBean@2344fc66
九月 02, 2019 6:01:53 下午 org.springframework.context.support.ClassPathXmlApplicationContext doClose
信息: Closing org.springframework.context.support.ClassPathXmlApplicationContext@685f4c2e: startup date [Mon Sep 02 18:01:52 CST 2019]; root of context hierarchy

很明显,自定义作用域是可行的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值