关于spring:一起来读官方文档SpringIOC05

45次阅读

共计 17318 个字符,预计需要花费 44 分钟才能阅读完成。

1.5。bean 作用域

创立 bean 定义时,将通过一个配方来创立该 bean 的理论实例。把 bean 定义解释称配方这一思维跟重要,因为它意味着与类一样,您能够从一个配方中创立许多对象实例。

bean definition 
是用来形容 bean 的一个构造体,就像每个人体检指标一样,进出一趟医院能拿到体检单,bean 在进入 Spring 流程中也会拿到属于本人的体检单。尽管医院不能依据体检单发明出人类,然而 Spring 能依据 bean definition 发明出实例

您不仅能够管制从 bean definition 创立的对象中的各种依赖项和配置值,还能够管制从特定 bean 定义创立的对象的作用域。
这种办法功能强大且灵便,因为您能够抉择通过配置创立的对象的作用域,而不用在 Java 类档次上解决对象的范畴。
能够将 bean 定义为部署在多种作用域中的一种。
Spring 框架反对 6 种作用域,其中 4 种作用域只有在应用反对 web 的 ApplicationContext 时才可用。
您还能够创立自定义作用域。

Bean 作用域范畴 形容
singleton (默认)单例对象,容器中只存在一个对象。
prototype 每次应用都会实例化新的对象
request 将单个 bean 定义的范畴限定为单个 HTTP 申请的生命周期。
也就是说,每个 HTTP 申请都有一个 bean 实例。
仅在反对 web 的 Spring ApplicationContext 上下文中无效。
session 每一次 HTTP 申请都会产生一个新的 bean,
同时该 bean 仅在以后 HTTP session 内无效。
仅在可感知网络的 Spring 上下文中无效 ApplicationContext。
application 将单个 bean 定义的作用域限定为的生命周期 ServletContext。
仅在可感知网络的 Spring 上下文中无效 ApplicationContext。
websocket 将单个 bean 定义的作用域限定为的生命周期 WebSocket。
仅在可感知网络的 Spring 上下文中无效 ApplicationContext。
(这个 scope 没在源码中找到,暂不分明实现逻辑,
但应该和之前的大同小异)

从 Spring 3.0 开始,线程作用域可用,但默认状况下未注册。无关更多信息,请参见的文档 SimpleThreadScope。无关如何注册此自定义范畴或任何其余自定义范畴的阐明,请参阅 应用自定义范畴。

说一下大抵流程(非文档内容)

https://gitee.com/cclookeme/tocmat-main
贴一个 git 地址,这个工程演示如何用最简略的代码启动一个 Spring 利用,非 SpringBoot

拿进去大抵一说
1.Tomcat 启动会查找 ServletContainerInitializer 实现类并执行其中的 onStartup 办法。2.Spring-web 模块存在 ServletContainerInitializer 实现类,所以 Tomcat 启动会调用 Spring-web 的代码。3. 然而咱们用 Spring 框架的话不须要实现这个接口,实现一个 Spring 的接口 WebApplicationInitializer。4. 就能够由 Tomcat 调用 Spring-web 的 ServletContainerInitializer 实现类,再由 Spring-web 模块调用咱们的 WebApplicationInitializer 实现类

//WebApplicationInitializer 实现形式如下(SpringMvc 官网提供地址如下)//https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/web.html#spring-web
public class MyWebApplicationInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext servletCxt) {

        // Load Spring web application configuration
        AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();
        ac.register(AppConfig.class);
        //ac.refresh(); 官网文档提供了这样一行代码然而咱们为了应用 application Scope 并不需要这个代码

        // Create and register the DispatcherServlet
        DispatcherServlet servlet = new DispatcherServlet(ac);
        ServletRegistration.Dynamic registration = servletCxt.addServlet("app", servlet);
        registration.setLoadOnStartup(1);
        registration.addMapping("/app/*");
    }
}

有了如上代码,咱们就有了一个 ApplicationContext 环境。Tomcat 启动见 git 我的项目就能够了,那个不重要。咱们就假设当初 Tomcat 曾经启动,启动并执行到咱们自定义的 WebApplicationInitializer 中了
在这个自定义办法中咱们实例化了 AnnotationConfigWebApplicationContext 这个对象,把这个对象带进了 DispatcherServlet,以至于 ServletContext 中以备后续应用,AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();
ac.register(AppConfig.class);
上边两行代码仅仅是指定了一个 Config 类,这个类上有很多注解相当于 SpringBoot 的启动类
还有其余的参数须要 Spring 本人来进行解决,所有后续会有这个办法来丰盛 ApplicationContext
protected WebApplicationContext initWebApplicationContext() {
        WebApplicationContext rootContext =
                WebApplicationContextUtils.getWebApplicationContext(getServletContext());
        WebApplicationContext wac = null;

        if (this.webApplicationContext != null) {
            // A context instance was injected at construction time -> use it
            wac = this.webApplicationContext;
            if (wac instanceof ConfigurableWebApplicationContext) {ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
                // 这个办法只看这个中央 如果之前咱们自定义代码外面执行了 ac.refresh()
                // 那么这个中央的 active 会是 true,那就没法进入 if 外面
                // 而 if 外面则是给 ApplicationContext 赋值了 ServletContext 
                // 最初也会调用 refresh 办法
                // 而 ServletContext 的作用就是咱们的 application  Scope
                // 如果不注入 ServletContext 则就无奈启用这个 Scope
                if (!cwac.isActive()) {if (cwac.getParent() == null) {cwac.setParent(rootContext);
                    }
                    configureAndRefreshWebApplicationContext(cwac);
                }
            }
        }
        .
        .
        .
        .
        return wac;
    }
    
    
在看一个 refresh()里执行的代码
public static void registerWebApplicationScopes(ConfigurableListableBeanFactory beanFactory,
            @Nullable ServletContext sc) {beanFactory.registerScope(WebApplicationContext.SCOPE_REQUEST, new RequestScope());
        beanFactory.registerScope(WebApplicationContext.SCOPE_SESSION, new SessionScope());
        // 这里判断了 sc sc 就是上边咱们要注入的 ServletContext 没注入的话这里就不注册解析 application Scope 的 bean
        if (sc != null) {ServletContextScope appScope = new ServletContextScope(sc);
            beanFactory.registerScope(WebApplicationContext.SCOPE_APPLICATION, appScope);
            sc.setAttribute(ServletContextScope.class.getName(), appScope);
        }
        .
        .
        .
    }

在之后具体 Spring 启动代码咱们不必看了
咱们在看下 Spring 获取 bean 时候怎么解决的 Scope
// 如果是单例 间接获取单例池中的对象 单例池中不存在执行 createBean 办法
if (mbd.isSingleton()) {sharedInstance = getSingleton(beanName, () -> {
        try {return createBean(beanName, mbd, args);
        }
        catch (BeansException ex) {destroySingleton(beanName);
            throw ex;
        }
    });
    bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
else if (mbd.isPrototype()) {
    // 如果是原型间接就 createBean 从新生成对象
    Object prototypeInstance = null;
    try {beforePrototypeCreation(beanName);
        prototypeInstance = createBean(beanName, mbd, args);
    }
    finally {afterPrototypeCreation(beanName);
    }
    bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}
else {
    // 其余 Scope 
    // 首先获取以后 bean 的 Scope 
    String scopeName = mbd.getScope();
    if (!StringUtils.hasLength(scopeName)) {throw new IllegalStateException("No scope name defined for bean ´" + beanName + "'");
    }
    // 依据 Scope 获取解析以后 Scope 的 bean
    // 这里也就是给了咱们自定义 Scope 的可能 咱们能够本人定义 Scope
    // 再定义一个 Scope 的解析类就哦了
    Scope scope = this.scopes.get(scopeName);
    // 如果没找到解析类则报错 就像前边的 ServletContext 没注入一样
    if (scope == null) {throw new IllegalStateException("No Scope registered for scope name'" + scopeName + "'");
    }
    try {
       // 这里的 get 是能够自定已实现缓存了,比方同一个 request 用一个实例
       // 雷同 sessionId 用一个实例
       // 如果没找到对应的实例则 createBean 生成
        Object scopedInstance = scope.get(beanName, () -> {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);
    }
}
下边介绍各个 Scope 内容和用法(官网文档)
1.5.1。The Singleton Scope

换句话说,当您定义一个 bean 定义并且它的作用域是一个单例对象时,Spring IoC 容器会创立该 bean 定义定义的对象的一个实例。这个繁多实例存储在这样的单例 bean 的缓存中,对这个已命名 bean 的所有后续申请和援用都会返回缓存的对象。

    <bean id="myDao" class="..."></bean>
    
    <bean id="a" class="...">
        <property name="ff" ref="myDao"></property>
    </bean>    
    <bean id="b" class="...">
        <property name="ff" ref="myDao"></property>
    </bean>    
    <bean id="c" class="...">
        <property name="ff" ref="myDao"></property>
    </bean>

a,b,c 三个对象持有的 ff 属性对应的都是一个实例

Spring 的单例 bean 概念不同于“四人帮”(GoF)模式书中定义的单例模式。
GoF 单例对对象的作用域进行硬编码,这样每个类加载器都会创立一个且只有一个特定类的实例。
Spring 单例的作用域最好形容为单个容器的单个 bean。
这意味着,如果您在单个 Spring 容器中为特定类定义了一个 bean,那么 Spring 容器将创立由该 bean 定义定义的类的一个且仅一个实例。
单例作用域是 Spring 中的默认作用域。要在 XML 中定义一个单例的 bean,你能够像上面的例子那样定义一个 bean:

<bean id="accountService" class="com.something.DefaultAccountService"/>

<!-- 上面的办法是等效的,然而是多余的(单例范畴是默认的) -->
<bean id="accountService" class="com.something.DefaultAccountService" scope="singleton"/>
1.5.2。The Prototype Scope

bean 部署的非单例原型范畴导致每次对特定 bean 发出请求时都创立一个新的 bean 实例。也就是说,该 bean 被注入到另一个 bean 中,或者您通过容器上的 getBean()办法调用申请它。作为规定,您应该对所有有状态 bean 应用原型作用域,对无状态 bean 应用单例作用域。

    <bean id="myDao" class="..." scope="prototype"></bean>

    <bean id="a" class="..."  scope="prototype">
        <property name="ff" ref="myDao"></property>
    </bean>
    <bean id="b" class="..."  scope="prototype">
        <property name="ff" ref="myDao"></property>
    </bean>
    <bean id="c" class="..." scope="prototype">
        <property name="ff" ref="myDao"></property>
    </bean>

a,b,c 持有不同的 myDao 实例,前提 a,b,c 必须都不是单例
(数据拜访对象 (DAO) 通常不配置为原型,因为典型的 DAO 不持有任何会话状态。)

上面的例子用 XML 将 bean 定义为原型:

<bean id="accountService" class="com.something.DefaultAccountService" scope="prototype"/>

与其余作用域相比,Spring 不治理原型 bean 的残缺生命周期。容器实例化、配置和组装原型对象并将其交给客户端,而不进一步记录该原型实例。
因而,只管初始化生命周期回调办法在所有对象上都被调用,但在原型的状况下,配置的销毁生命周期回调不会被调用。
客户端代码必须清理原型范畴的对象并开释原型 bean 所持有的低廉资源。
要让 Spring 容器开释原型作用域 bean 所持有的资源,请尝试应用自定义 BeanPostProcessor,它持有对须要清理的 bean 的援用。

BeanPostProcessor 接口定义了回调办法,您能够实现这些办法来提供您本人的 (或笼罩容器的默认值) 实例化逻辑、依赖项解析逻辑等等。如果您想在 Spring 容器实现实例化、配置和初始化 bean 之后实现一些自定义逻辑,您能够插入一个或多个自定义 BeanPostProcessor 实现。您能够配置多个 BeanPostProcessor 实例,并且能够通过设置 order 属性来管制这些 BeanPostProcessor 实例运行的程序。只有当 BeanPostProcessor 实现了有序接口时,能力设置此属性。如果您编写本人的 BeanPostProcessor,也应该思考实现 Ordered 接口。以上是官网解释,艰深的说就是
BeanPostProcessor 有一个办法能够在 bean 实例化之后被执行,那时候能够拿到 bean 的援用,并且批改 bean
1.5.3。Singleton Bean 依赖 Prototype Bean

当您应用带有原型 bean 依赖项的单例范畴 bean 时,请留神依赖项是在实例化时解析的。
因而,如果您将一个原型作用域的 bean 注入到一个单例作用域的 bean 中,那么只有一次原型 bean 的实例化,而后注入到单例 bean 中。
实例化单例 bean 时生成的原型 bean 实例是提供给单例作用域 bean 的惟一实例。

然而,假如您心愿单例作用域 bean 在运行时反复取得原型作用域 bean 的新实例。
您不能依赖地将原型作用域的 bean 注入到单例 bean 中,因为该注入只在 Spring 容器实例化单例 bean 并解析和注入其依赖项时产生一次。
如果您不止一次地须要原型 bean 在运行时的新实例,请参阅 一起来读官网文档 —–SpringIOC(04)1.4.6 大节

1.5.4。request,session,application,和 websocket 范畴

在 request,session,application,和 websocket 范畴只有当你应用一个基于 web 的 Spring 可 ApplicationContext 实现(例如 XmlWebApplicationContext)。
如果您将这些作用域与惯例的 Spring IoC 容器一起应用,例如 ClassPathXmlApplicationContext,则会引发埋怨未知 bean 作用域的 IllegalStateException 异样。


    <bean id="serviceTwo" class="org.springframework.example.service.ServiceTwo" scope="application"/>
    
    Caused by: java.lang.IllegalStateException: No Scope registered for scope name 'application'
初始 Web 配置

为了反对 bean 的范畴界定在 request,session,application,和 websocket(即具备 web 作用域 bean),须要在定义你的 bean 之前做大量的初始配置。

如何实现此初始设置取决于您的特定 Servlet 环境。

如果您在 Spring Web MVC 中拜访作用域化的 bean,实际上是在 Spring 解决的申请中,则 DispatcherServlet 不须要非凡的设置。DispatcherServlet 曾经公开了所有相干状态。

对于 Servlet 3.0+,能够应用该 WebApplicationInitializer 接口以编程形式实现此操作。

public class LearnSpringMain implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {

        AnnotationConfigWebApplicationContext aa =
                new AnnotationConfigWebApplicationContext();
        aa.register(LearnSpringConfig.class);
        aa.setServletContext(servletContext);

        DispatcherServlet dispatcherServlet = new DispatcherServlet(aa);
        dispatcherServlet.setEnableLoggingRequestDetails(true);
        ServletRegistration.Dynamic ds = servletContext.addServlet("dispatcherServlet",dispatcherServlet);
        ds.addMapping("/*");
        ds.setLoadOnStartup(1);
    }
}

如果您应用 Servlet 2.5 Web 容器,并且在 Spring 之外解决申请 DispatcherServlet(例如,应用 JSF 或 Struts 时),则须要注册 org.springframework.web.context.request.RequestContextListener ServletRequestListener
或者,或者对于较旧的容器,将以下申明增加到 Web 应用程序的 web.xml 文件中:

<web-app>
    ...
    <listener>
        <listener-class>
            org.springframework.web.context.request.RequestContextListener
        </listener-class>
    </listener>
    ...
</web-app>

另外,如果您的监听器设置存在问题,请思考应用 Spring 的 RequestContextFilter。过滤器映射取决于四周的 Web 应用程序配置,因而您必须适当地对其进行更改。以下清单显示了 Web 应用程序的过滤器局部:

<web-app>
    ...
    <filter>
        <filter-name>requestContextFilter</filter-name>
        <filter-class>org.springframework.web.filter.RequestContextFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>requestContextFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    ...
</web-app>

DispatcherServlet,RequestContextListener 和 RequestContextFilter 都做完全相同的事件,行将 HTTP 申请对象绑定到 Thread 为该申请提供服务的对象。这使得在申请链和会话范畴内的 Bean 能够在调用链的更上游应用。

Request scope

思考以下 XML 配置来定义 bean:

<bean id="loginAction" class="com.something.LoginAction" scope="request"/>

Spring 容器通过为每个 HTTP 申请应用 LoginAction bean 定义来创立 LoginAction bean 的一个新实例。也就是说,loginAction bean 的作用域在 HTTP 申请级别。您能够随便更改所创立实例的外部状态,因为从雷同 loginAction bean 定义创立的其余实例不会看到这些状态更改。它们是针对个别申请的。当申请实现解决时,作用域为该申请的 bean 将被抛弃。

当应用正文驱动的组件或 Java 配置时,能够应用 @RequestScope 正文将组件调配到申请范畴。上面的例子展现了如何做到这一点:

@RequestScope
@Component
public class LoginAction {// ...}
Session Scope

思考以下 XML 配置来定义 bean:

<bean id="userPreferences" class="com.something.UserPreferences" scope="session"/>

Spring 容器通过应用单个 HTTP 会话生存期的 UserPreferences bean 定义来创立一个 UserPreferences bean 的新实例。换句话说,userPreferences bean 无效地限定了 HTTP 会话级别的范畴。与申请范畴内 bean 一样, 你能够扭转外部状态的实例创立尽可能多的你想要的, 晓得其余 HTTP 会话实例也应用雷同的实例创立 userPreferences bean 定义看不到这些变动状态, 因为他们是特定于一个独自的 HTTP 会话。当 HTTP 会话最终被抛弃时,作用域为该特定 HTTP 会话的 bean 也将被抛弃。

在应用正文驱动的组件或 Java 配置时,您能够应用 @SessionScope 正文来为会话范畴调配一个组件。

@SessionScope
@Component
public class UserPreferences {// ...}
Application Scope

思考以下 XML 配置来定义 bean:

<bean id="appPreferences" class="com.something.AppPreferences" scope="application"/>

Spring 容器为整个 web 应用程序应用一次 App Preferences bean 定义,从而创立 AppPreferences bean 的一个新实例。也就是说,appPreferences bean 的作用域在 ServletContext 级别,并存储为一个惯例的 ServletContext 属性。这有点相似于弹簧单例 bean, 但在两个重要方面不同: 它是一个单例每 ServletContext 不是每春天 ApplicationContext 的(可能有几个在任何给定的 web 应用程序), 它实际上是裸露, 因而可见 ServletContext 属性。

当应用正文驱动的组件或 Java 配置时,您能够应用 @ApplicationScope 正文来为应用程序范畴调配一个组件。
上面的例子展现了如何做到这一点:

@ApplicationScope
@Component
public class AppPreferences {// ...}
将带有 Scope 属性的的 bean 作为依赖项

Spring IoC 容器不仅治理对象 (bean) 的实例化,而且还治理协作者 (或依赖关系) 的连贯。如果您想将 (例如) 一个 HTTP 申请作用域的 bean 注入到另一个更长命作用域的 bean 中,您能够抉择注入一个 AOP 代理来代替作用域 bean。也就是说,您须要注入一个代理对象,该代理对象公开与作用域对象雷同的公共接口,但也能够从相干作用域检索理论指标对象(例如 HTTP 申请),并将办法调用委托给理论对象。

您还能够在作用域为单例的 bean 之间应用 <aop:scoped-proxy/>,而后通过一个可序列化的两头代理进行援用,从而可能在反序列化时从新取得指标单例 bean。当对作用域原型 bean 申明 <aop:scoped-proxy/> 时,对共享代理的每个办法调用都会创立一个新的指标实例,而后将调用转发到该指标实例。而且,作用域代理并不是解决长作用域 bean 接管短作用域 bean 的惟一办法。你也能够申明你的注入形式(也就是构造函数或 setter 参数或 autowired 的字段)
作为 ObjectFactory<MyTargetBean>,
容许 getObject()调用来检索以后实例对需要每次须要——没有别离持有实例或存储它。例如:@Autowired
    private ObjectFactory<ServiceTwo> serviceTwo;

    @RequestMapping("/b")
    @ResponseBody
    public String getName(){return serviceTwo.getObject().getServiceOneName();}

作为扩大变量,您能够申明 ObjectProvider<MyTargetBean>,它提供了几个额定的拜访变量,包含 getIfAvailable 和 getIfUnique。

以下示例中的配置仅一行,然而理解其背地的“起因”和“形式”很重要:

<?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/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--scope  为 session 的 bean-->
    <bean id="userPreferences" class="com.something.UserPreferences" scope="session">
        <!-- 减少以后标签后 每次 userService 申请该 bean 的实例都是申请的代理对象
        代理对象再去调用真是实例,就能够保障短期 Scope 能失常应用,而不至于使短期
        Scope 放弃和单例 Scope 一样长的生命周期 -->
        <aop:scoped-proxy/>!!!</bean>

    <!-- scope 为单例的 bean -->
    <bean id="userService" class="com.something.SimpleUserService">
        <property name="userPreferences" ref="userPreferences"/>
    </bean>
</beans>

要创立 !!! 处这样的代理,须要将子元素 <aop:scoped-proxy/> 插入作用域 bean 定义中。
为什么在 request,session 和自定义 Scope 的 bean 须要 <aop:scoped-proxy/> 元素?
看上面的例子和上边的做比照!

<bean id="userPreferences" class="com.something.UserPreferences" scope="session"/>

<bean id="userManager" class="com.something.UserManager">
    <property name="userPreferences" ref="userPreferences"/>
</bean>

在后面的示例中,单例 bean (userManager)被注入了对 HTTP 会话作用域 bean (userPreferences)的援用。
** 这里的重点是 userManager bean 是一个单例: 每个容器只实例化一次,
它的依赖项 (在本例中只有一个,即 userPreferences bean) 也只被注入一次。**
这意味着 userManager bean 只操作完全相同的 userPreferences 对象(即最后注入它的那个对象,因为 userPreferences 并没有达到 session Scope 应有的作用域的成果)。

当将较短的作用域 bean 注入到较长作用域 bean 中时 (例如,将 HTTP 会话作用域的合作 bean 作为依赖项注入到单例 bean 中),这不是您想要的行为。
相同,您仅仅只须要一个 userManager 对象,然而在 HTTP 会话的生命周期中,您须要一个特定于 HTTP 会话的 userPreferences 对象。
因而,容器创立一个对象,该对象公开与 UserPreferences 类完全相同的公共接口 (现实状况下,该对象是一个 UserPreferences 实例),它能够从作用域机制(HTTP 申请、会话等) 获取真正的 UserPreferences 对象。
容器将这个代理对象注入到 userManager bean 中,然而 userManager bean 不晓得这个 UserPreferences 援用是一个代理。
在本例中,当 UserManager 实例调用依赖注入的 UserPreferences 对象上的办法时,它实际上是在调用代理上的办法。代理而后从(在本例中探讨的是 HTTP 会话)HTTP 会话中获取实在的 UserPreferences 对象,并将办法调用委托给检索到的实在 UserPreferences 对象。

因而,你当注射须要满足以下(正确和残缺)的配置 request- 和 session-scoped 豆类为单干对象,如下例所示:

<bean id="userPreferences" class="com.something.UserPreferences" scope="session">
    <aop:scoped-proxy/>
</bean>

<bean id="userManager" class="com.something.UserManager">
    <property name="userPreferences" ref="userPreferences"/>
</bean>
抉择要创立的代理类型

默认状况下,当 Spring 容器为标记为 <aop:scoped-proxy/> 元素的 bean 创立代理时,将创立一个基于 cglib 的类代理。

CGLIB 代理仅拦挡公共办法调用! 不要在这样的代理上调用非公共办法。它们没有被委托给理论作用域的指标对象。

或者,您能够配置 Spring 容器,通过为 <aop:scoped-proxy/> 元素的 proxy-target-class 属性的值指定 false,为这种作用域 bean 创立规范的基于 JDK 接口的代理。
<aop:scoped-proxy/> 应用 JDK 基于接口的代理意味着您不须要在应用程序类门路中增加其余库即可应用这种代理。
然而,这也意味着作用域 bean 的类必须实现至多一个接口,并且所有注入作用域 bean 的协作者必须通过它的其中一个接口援用该 bean。
以下示例显示基于接口的代理:proxy-target-class<aop:scoped-proxy/>

<bean id="userPreferences" class="com.stuff.DefaultUserPreferences" scope="session">
    <aop:scoped-proxy proxy-target-class="false"/>
</bean>

<bean id="userManager" class="com.stuff.UserManager">
    <property name="userPreferences" ref="userPreferences"/>
</bean>
1.5.5。自定义作用域

Bean 作用域机制是可扩大的。您能够定义本人的作用域,甚至从新定义现有作用域,只管后者被认为是不好的做法,并且您不能笼罩内置作用域 singleton 和 prototype 作用域。

创立自定义范畴

要将您的自定义作用域集成到 Spring 容器中,您须要实现 org.springframework.bean .factory.config.Scope 接口。

Scope 接口有四个办法,用于从范畴中获取对象、从范畴中删除对象以及销毁它们。

例如,session scope 实现返回 session scope 的 bean(如果它不存在,则在将其绑定到会话以供未来援用之后,该办法返回该 bean 的一个新实例)。上面的办法从底层范畴返回对象:

Object get(String name, ObjectFactory<?> objectFactory)

session scope 的实现,例如,从根底会话中删除了 session scope 的 bean 并返回该对象。
如果找不到具备指定名称的对象,则能够返回 null。以下办法从根底范畴中删除该对象:

Object remove(String name)

以下办法注册一个回调,当销毁作用域或销毁作用域中的指定对象时,作用域应调用该回调:

void registerDestructionCallback(String name, Runnable destructionCallback)

以下办法获取根底范畴的会话标识符:

String getConversationId()

每个范畴的标识符都不雷同。对于会话范畴的实现,此标识符能够是会话标识符。

应用自定义范畴

在编写和测试一个或多个自定义 Scope 实现之后,您须要使 Spring 容器意识到您的新作用域。以下办法是 Scope 在 Spring 容器中注册新办法的次要办法:

void registerScope(String scopeName, Scope scope);

此办法在 ConfigurableBeanFactory 接口上申明,该接口可通过 Spring 附带的大多数具体 ApplicationContext 实现上的 BeanFactory 属性应用。

registerScope(..)办法的第一个参数是与作用域关联的惟一名称。此类名称在 Spring 容器自身中的例子有 singleton 和 prototype。
registerScope(..)办法的第二个参数是您心愿注册和应用的自定义范畴实现的一个理论实例。

假如您编写了自定义范畴实现,而后注册它,如下一个示例所示。

Scope threadScope = new SimpleThreadScope();
beanFactory.registerScope("thread", threadScope);

而后,您能够依照您的 custom 的作用域规定创立 bean 定义,Scope 如下所示:

<bean id="..." class="..." scope="thread">

应用自定义 Scope 实现,您不仅仅局限于循序渐进式的注册 bean。
您还能够 Scope 应用 CustomScopeConfigurer 该类以申明形式进行注册,如以下示例所示:

<?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/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
        <property name="scopes">
            <map>
                <entry key="thread">
                    <bean class="org.springframework.context.support.SimpleThreadScope"/>
                </entry>
            </map>
        </property>
    </bean>

    <bean id="thing2" class="x.y.Thing2" scope="thread">
        <property name="name" value="Rick"/>
        <aop:scoped-proxy/>
    </bean>

    <bean id="thing1" class="x.y.Thing1">
        <property name="thing2" ref="thing2"/>
    </bean>

</beans>

留神:
当您搁置 <aop:scoped-proxy/> 在 FactoryBean 实现中时,作用域是工厂 Bean 自身,而不是从中返回的对象 getObject()。

正文完
 0