8. spring源码篇只Scope

简介

我们常见的Bean有单例Bean与多例Bean,但其实scope还有有一些其它的值,如果只是使用简单的spring就只有这些,但是要是使用SprigBoot或者SpringMvc,那么就就还会有一些其它的比较常用的值,例如
session,request

spring对Scope的判断

如果是一个单例bean,spring就只需要创建一次,多例每次都创建,还有一些其它的scope也是会在指定时候创建

以下是简化后的代码

if (mbd.isSingleton()) {
    // 如果是单例先从单例池拿,没有就执行createBean
    sharedInstance = getSingleton(beanName, () -> {
         return createBean(beanName, mbd, args);
    });
    // FactoryBean逻辑
    beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
} else if (mbd.isPrototype()) {
    // 每次都是执行createBean
    prototypeInstance = createBean(beanName, mbd, args);
    beanInstance = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
} else {
    // 获取Scope
    String scopeName = mbd.getScope();
    Scope scope = this.scopes.get(scopeName);
    // 先从scope中拿,没有再createBean
    Object scopedInstance = scope.get(beanName, () -> {
         return createBean(beanName, mbd, args);
    });
    beanInstance = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
}

可以看到,除了单例和原型,还会有其它的值,如果是其它的值,应该是会注册一个Scope,通过Scope的get方法获取Bean

RequestScope与SessionScope

上面代码的第三个分支是需要有Scope,那么scopes什么时候有值呢,在spring-web里就有注册一些Scope

public static void registerWebApplicationScopes(ConfigurableListableBeanFactory beanFactory,
@Nullable ServletContext sc) {

    beanFactory.registerScope(WebApplicationContext.SCOPE_REQUEST, new RequestScope());
    beanFactory.registerScope(WebApplicationContext.SCOPE_SESSION, new SessionScope());
    if (sc != null) {
        ServletContextScope appScope = new ServletContextScope(sc);
        beanFactory.registerScope(WebApplicationContext.SCOPE_APPLICATION, appScope);
        sc.setAttribute(ServletContextScope.class.getName(), appScope);
    }
}

上面代码可以看到注册了RequestScope,与SessionScope,可以想到SessionScope就是在一个session中都可以拿到Bean,RequestScope便是在一次请求中都可以拿到

两个scope获取方式差不多,源码如下

public Object get(String name, ObjectFactory<?> objectFactory) {
    RequestAttributes attributes = RequestContextHolder.currentRequestAttributes();
    // ServletRequestAttributes.getAttribute
    Object scopedObject = attributes.getAttribute(name, getScope());
    if (scopedObject == null) {
        scopedObject = objectFactory.getObject();
        // 拿到了塞进去
        attributes.setAttribute(name, scopedObject, getScope());
        
        Object retrievedObject = attributes.getAttribute(name, getScope());
        if (retrievedObject != null) {
            scopedObject = retrievedObject;
        }
    }
    // 可以拿到直接返回
    return scopedObject;
}

上面的attributes.getAttribute查看实现类 ServletRequestAttributes.getAttribute

原理就是使用的request与session暂存,如下所示

public Object getAttribute(String name, int scope) {
    if (scope == SCOPE_REQUEST) {
        return this.request.getAttribute(name);
    }
    else {
        HttpSession session = getSession(false);
        if (session != null) {
            try {
                Object value = session.getAttribute(name);
                if (value != null) {
                    this.sessionAttributesToUpdate.put(name, value);
                }
                return value;
            }
            catch (IllegalStateException ex) {
                // Session invalidated - shouldn't usually happen.
            }
        }
        return null;
    }
}

public void setAttribute(String name, Object value, int scope) {
    if (scope == SCOPE_REQUEST) {
        this.request.setAttribute(name, value);
    }
    else {
        HttpSession session = obtainSession();
        this.sessionAttributesToUpdate.remove(name);
        session.setAttribute(name, value);
    }
}

自定义scope

由上面代码分析,自定义scope只需要实现org.springframework.beans.factory.config.Scope就行

spring提供了一个SimpleThreadScope,我们就使用它,不重新写了

SimpleThreadScope代码如下

public class SimpleThreadScope implements Scope {

    private static final Log logger = LogFactory.getLog(SimpleThreadScope.class);

    private final ThreadLocal<Map<String, Object>> threadScope =
    new NamedThreadLocal<Map<String, Object>>("SimpleThreadScope") {
        @Override
        protected Map<String, Object> initialValue() {
            return new HashMap<>();
        }
    };


    @Override
    public Object get(String name, ObjectFactory<?> objectFactory) {
        Map<String, Object> scope = this.threadScope.get();
        Object scopedObject = scope.get(name);
        if (scopedObject == null) {
            scopedObject = objectFactory.getObject();
            scope.put(name, scopedObject);
        }
        return scopedObject;
    }

    @Override
    @Nullable
    public Object remove(String name) {
        Map<String, Object> scope = this.threadScope.get();
        return scope.remove(name);
    }

    @Override
    public void registerDestructionCallback(String name, Runnable callback) {
        logger.warn("SimpleThreadScope does not support destruction callbacks. " +
        "Consider using RequestScope in a web environment.");
    }

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

    @Override
    public String getConversationId() {
        return Thread.currentThread().getName();
    }

}

该代码其实就是保证在同一个线程上下文中拿到的对象都是同一个

使用SimpleThreadScope


@Component
// 需要注册
@Scope("thread")
public class UserBean {
    public UserBean() {
        System.out.println("实例化UserBean");
    }
}


public class Application {

    public static void main(String[] args) throws InterruptedException {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(AppConfig.class);
        // 注册Scope
        context.getBeanFactory().registerScope("thread", new SimpleThreadScope());
        context.refresh();

        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                System.out.println(context.getBean("userBean"));
            }
        }).start();

        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                System.out.println(context.getBean("userBean"));
            }
        }).start();

        TimeUnit.SECONDS.sleep(1);
    }
}


输出
实例化UserBean
实例化UserBean
com.shura.beans.UserBean@30f45b6
com.shura.beans.UserBean@1712a6b0
com.shura.beans.UserBean@1712a6b0
com.shura.beans.UserBean@1712a6b0
com.shura.beans.UserBean@1712a6b0
com.shura.beans.UserBean@1712a6b0
com.shura.beans.UserBean@30f45b6
com.shura.beans.UserBean@30f45b6
com.shura.beans.UserBean@30f45b6
com.shura.beans.UserBean@30f45b6

以上就是scope的分析了


欢迎关注,学习不迷路!

你可能感兴趣的:(spring,framework,spring,java)