Spring文档笔记二(bean的范围和自定义bean)

Spring文档笔记二(bean的范围和自定义bean)

bean范围

表格 1_.Bean 范围

范围描述
[singleton](file:///C:/C:/C:/My Websites/spring/www.docs4dev.com/docs/zh/spring-framework/4.3.21.RELEASE/reference/beans.html#beans-factory-scopes-custom)(默认)根据 Spring IoC 容器将单个 bean 定义范围限定为单个 object 实例。
[原型](file:///C:/C:/C:/My Websites/spring/www.docs4dev.com/docs/zh/spring-framework/4.3.21.RELEASE/reference/beans.html#beans-factory-scopes-singleton)(prototype)为任意数量的 object 实例定义单个 bean 定义。
[请求](file:///C:/C:/C:/My Websites/spring/www.docs4dev.com/docs/zh/spring-framework/4.3.21.RELEASE/reference/beans.html#beans-factory-scopes-prototype)将单个 bean 定义范围限定为单个 HTTP 请求的生命周期;也就是说,每个 HTTP 请求都有自己的 bean 实例,它是在单个 bean 定义的后面创建的。仅在 web-aware Spring ApplicationContext的 context 中有效。
[session](file:///C:/C:/C:/My Websites/spring/www.docs4dev.com/docs/zh/spring-framework/4.3.21.RELEASE/reference/beans.html#beans-factory-scopes-request)将单个 bean 定义范围限定为 HTTP Session的生命周期。仅在 web-aware Spring ApplicationContext的 context 中有效。
[应用](file:///C:/C:/C:/My Websites/spring/www.docs4dev.com/docs/zh/spring-framework/4.3.21.RELEASE/reference/beans.html#beans-factory-scopes-global-session)将单个 bean 定义范围限定为ServletContext的生命周期。仅在 web-aware Spring ApplicationContext的 context 中有效。
[WebSocket](file:///C:/C:/C:/My Websites/spring/www.docs4dev.com/docs/zh/spring-framework/4.3.21.RELEASE/reference/websocket.html#beans-factory-scopes-application)将单个 bean 定义范围限定为WebSocket的生命周期。仅在 web-aware Spring ApplicationContext的 context 中有效。
singleton 范围

只管理 singleton bean 的一个共享实例,并且对具有与该 bean 定义匹配的 id 或 id 的 beans 的所有请求都会导致 Spring 容器返回一个特定的 bean 实例。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C8d0PTf7-1576661816524)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20191110121020116.png)]

原型范围(prototype)

每次对特定bean提出请求时,bean部署的非单一原型范围都会导致创建一个新bean实例。也就是说,该Bean被注入到另一个Bean中,或者您通过getBean()容器上的方法调用来请求它。通常,应将原型作用域用于所有有状态Bean,将单例作用域用于无状态Bean。

下图说明了Spring原型范围:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qtUR6LCs-1576661816525)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20191110121111914.png)]

以下示例将bean定义为XML中的原型:

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

与其他作用域相比,Spring不能管理原型(prototype)Bean的完整生命周期。容器实例化,配置或组装原型对象,然后将其交给客户端,而无需对该原型实例的进一步记录。因此,尽管在不考虑作用域的情况下在所有对象上都调用了初始化生命周期回调方法,但在原型的情况下,不会调用已配置的销毁生命周期回调。客户端代码必须清除原型作用域内的对象并释放原型Bean拥有的昂贵资源。为了使Spring容器释放原型作用下的bean所拥有的资源,请尝试使用自定义bean后处理器,其中包含对需要清理的bean的引用。

在某些方面,Spring容器在原型作用域bean方面的角色是Java new运算符的替代。超过该时间点的所有生命周期管理必须由客户端处理。(有关Spring容器中bean生命周期的详细信息,请参阅Lifecycle Callbacks。)

request,session,application和WebSocket范围

requestsessionapplication,和websocket范围只有当你使用一个基于web的SpringApplicationContext可实现(例如 XmlWebApplicationContext)。如果您将这些作用域与常规的Spring IoC容器(例如ClassPathXmlApplicationContext)一起使用,则会抛出IllegalStateException抱怨未知bean作用域的。

初始Web配置

为了支持豆的范围界定在requestsessionapplication,和 websocket(即具有web作用域bean),定义你的bean之前需要做少量的初始配置。(对于标准示波器,不需要此初始设置:singletonprototype。)

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

如果您在Spring Web MVC中访问作用域化的bean,实际上是在Spring处理的请求中,则DispatcherServlet不需要特殊的设置。 DispatcherServlet已经公开了所有相关状态。

request范围

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

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

Spring容器LoginAction通过loginAction为每个HTTP请求使用bean定义来创建bean 的新实例。也就是说, loginActionbean的作用域位于HTTP请求级别。您可以根据需要更改创建实例的内部状态,因为从同一loginActionbean定义创建的其他实例看不到这些状态变化。它们特定于单个请求。当请求完成处理时,将限制作用于该请求的Bean。

使用注释驱动的组件或Java配置时,@RequestScope可以使用注释将组件分配给合并request范围。以下示例显示了如何执行此操作:

@RequestScope
@Component
public class LoginAction {
    // ...
}
session范围

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

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

基本介绍和request范围一致 生命周期不同

@SessionScope
@Component
public class UserPreferences {
    // ...
}
application 范围

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

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

在应用程序范围内,容器为每个web应用程序运行时创建一个实例。它几乎类似于单例范围,只有两个不同之处。即:

  • 应用程序作用域bean是每个ServletContext的单例对象,而单例作用域bean是每个ApplicationContext的单例对象。请注意,单个应用程序可能有多个应用程序上下文。

  • 应用程序作用域bean作为ServletContext属性可见。

使用注释驱动的组件或Java配置时,可以使用 @ApplicationScope注释将组件分配给application作用域。以下示例显示了如何执行此操作:

@ApplicationScope
@Component
public class AppPreferences {
    // ...
}
范围bean作为依赖项

Spring IoC容器不仅管理对象(bean)的实例化,而且还管理协作者(或依赖项)的连接。如果要将(例如)HTTP请求范围的Bean注入(例如)另一个作用域更长的Bean,则可以选择注入AOP代理来代替作用域的Bean。也就是说,您需要注入一个代理对象,该对象公开与范围对象相同的公共接口,但也可以从相关范围(例如HTTP请求)中检索实际目标对象,并将方法调用委托给实际对象

您也可以在作为singleton的 beans 之间使用,然后 reference 将通过可序列化的中间代理,因此能够在反序列化时 re-obtain 目标 singleton bean。 当对范围`prototype`的 bean 声明时,共享代理上的每个方法调用都将导致创建一个新的目标实例,然后该调用将被转发到该目标实例。
此外,范围代理不是以 lifecycle-safe 方式从较短范围访问 beans 的唯一方法。您也可以简单地将注入点(i.e.constructor/setter 参数或自动装配字段)声明为ObjectFactory,允许getObject()调用在每次需要时按需检索当前实例 - 无需保留实例或单独存储它。
作为扩展变体,您可以声明ObjectProvider,它提供了几个额外的访问变体,包括getIfAvailablegetIfUnique
这个 JSR-330 变体称为Provider,与Provider声明一起使用,并且每次检索尝试都使用相应的get()调用。有关 JSR-330 整体的更多详细信息,请参阅这里

以下 example 中的 configuration 只有一行,但了解“为什么”以及它背后的“如何”非常重要。

    <!-- an HTTP Session-scoped bean exposed as a proxy -->
    <bean id="userPreferences" class="com.foo.UserPreferences" scope="session">
        <!-- instructs the container to proxy the surrounding bean -->
        <aop:scoped-proxy/>
    </bean>

    <!-- a singleton-scoped bean injected with a proxy to the above bean -->
    <bean id="userService" class="com.foo.SimpleUserService">
        <!-- a reference to the proxied userPreferences bean -->
        <property name="userPreferences" ref="userPreferences"/>
    </bean>
理解

假设有bean两个,A和B,A的scope设置为request,B的scope随意,在spring配置文件中如下:



此时,B要注入A这个属性,如下:
public class B{
@Autowired
private A a;
//…
}
如果就这么使用,spring会抛异常(是什么异常在这里不重要,反正是你写错了)

根据前面贴出的关于request和session的官方文档解释,应该知道这两个作用域的一些知识了。
在这里A的scope为request,说明每个请求拥有一个A的实例,
而B默认是单例的,那么,当A注入到B中,岂不是B的a属性只被注入为web应用的第一个request对象?之后的新的request不会再注入给a了。B是单例的,

spring自然有解决之道:
只要修改spring配置文件(也就是把bean A修改一下)



aop:scoped-proxy/


就仅仅在A中加了aop:scoped-proxy/标签,就OK了。

为什么加aop:scoped-proxy/标签就可以了呢?
因为这个标签就是提示spring给A这个bean创建一个代理对象。
当 B要注入A时,如下:
public class B{
@Autowired
private A a;
//…
}
其实注入的是spring给A创建的代理对象。
每个请求都会创建一个A对象,然后由A的代理对象来判断当前使用的实际对象时哪个。(太拗口了)

大概原理是这样吧:

当你使用代理对象的任何方法时,会根据你当前所处线程获得对应的请求(每个请求都是开启一个线程,用ThreadLocal来做)

这样spring就可以通过 你当前所处线程来获得对应的请求。

再从请求中获得实际使用的bean,再调用该bean中与代理对象对应的方法。

这样,代理对象在整个spring上下文中只有一个,但通过代理对象使用的实际对象是属于当前请求范围内的。

选择要创建的代理类型

默认情况下,当Spring容器为使用<aop:scoped-proxy/>元素标记的bean创建代理时,将创建基于CGLIB的类代理。

CGLIB代理仅拦截公共方法调用!不要在这样的代理上调用非公共方法。它们没有被委派给实际的作用域目标对象。

另外,您可以通过指定元素falseproxy-target-class属性值,将Spring容器配置为为此类作用域的Bean创建基于标准JDK接口的代理<aop:scoped-proxy/>。使用基于JDK接口的代理意味着您不需要应用程序类路径中的其他库即可影响此类代理。但是,这也意味着作用域Bean的类必须实现至少一个接口,并且作用域Bean注入到其中的所有协作者必须通过其接口之一引用该Bean。以下示例显示了基于接口的代理:

<!-- DefaultUserPreferences implements the UserPreferences interface -->
<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>

有关选择基于类或基于接口的代理的详细信息,请参阅代理机制

自定义范围

bean的作用域机制是可扩展的。您可以定义自己的作用域,甚至重新定义现有作用域,尽管后者被认为是不好的做法,您不能覆盖内置作用域singletonprototype作用域。

查看自定义范围相关博客https://waylau.com/custom-scope-in-spring/

自定义bean的性质

生命周期回调

为了与容器对bean生命周期的管理进行交互,可以实现Spring InitializingBeanDisposableBean接口。容器要求 afterPropertiesSet()前者和destroy()后者使bean在初始化和销毁bean时执行某些操作。

通常,JSR-250 @PostConstruct@PreDestroy注释被认为是在现代Spring应用程序中接收生命周期回调的最佳实践。使用这些注释意味着您的bean没有耦合到特定于Spring的接口。有关详细信息,请参见@PostConstruct和@PreDestroy。如果您不想使用JSR-250批注,但仍希望删除耦合,请考虑使用init-method和destroy-method对象定义元数据。

在内部,Spring Framework使用BeanPostProcessor实现来处理它可以找到的任何回调接口并调用适当的方法。如果您需要自定义功能或其他生命周期行为,Spring不会提供现成的功能,则您可以BeanPostProcessor自己实现。有关更多信息,请参见 容器扩展点

除了初始化和销毁回调,Spring管理的对象还可以实现该Lifecycle接口,以便这些对象可以在容器自身生命周期的驱动下参与启动和关闭过程。

初始化回调

org.springframework.beans.factory.InitializingBean容器在bean上设置了所有必需的属性后,该接口使bean可以执行初始化工作。该InitializingBean接口指定一个方法:

void afterPropertiesSet() throws Exception;

我们建议您不要使用该InitializingBean接口,因为它不必要地将代码耦合到Spring。另外,我们建议使用@PostConstruct注释或指定POJO初始化方法。对于基于XML的配置元数据,可以使用init-method属性指定具有无效无参数签名的方法的名称。通过Java配置,您可以使用的initMethod属性 @Bean。请参阅接收生命周期回调。考虑以下示例:

<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>
public class ExampleBean { 
        public void init() {
                // do some initialization work
        }
}

…与…完全相同

<bean id="exampleInitBean" class="examples.AnotherExampleBean"/> 
public class AnotherExampleBean implements InitializingBean {
        public void afterPropertiesSet() {
                // do some initialization work
        }
}

但不会将代码耦合到Spring。

销毁回调

org.springframework.beans.factory.DisposableBean当包含该接口的容器被销毁时,实现该接口可使Bean获得回调

我们建议您不要使用DisposableBean回调接口,因为它不必要地将代码耦合到Spring。另外,我们建议使用@PreDestroy注释或指定bean定义支持的通用方法。使用基于XML的配置元数据时,您可以在destroy-method上使用属性``。通过Java配置,您可以使用的destroyMethod属性@Bean。请参阅 接收生命周期回调。考虑以下定义:

<bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="cleanup"/>
一次配置多个初始化
<beans default-init-method="init">

    <bean id="blogService" class="com.something.DefaultBlogService">
        <property name="blogDao" ref="blogDao" />
    </bean>

</beans>

顶层元素属性上存在default-init-method属性,导致Spring IoC容器将bean上称为init的方法识别为初始化方法回调。 在创建和组装bean时,如果bean类具有这种方法,则会在适当的时间调用它。

您可以通过使用顶级元素上的default-destroy-method属性类似地(在XML中)配置destroy方法回调。

如果现有的Bean类已经具有按约定命名的回调方法,则可以通过使用的init-method和destroy-method属性指定(在XML中)方法名称来覆盖默认值 本身。

*初始化和aop拦截器

😓暂时还不完全懂

Spring容器保证在为bean提供所有依赖项后立即调用配置的初始化回调。 因此,在原始bean引用上调用了初始化回调,这意味着AOP拦截器等尚未应用于bean。 首先完全创建目标bean然后 应用具有其拦截器链的AOP代理(例如)。 如果目标Bean和代理分别定义,则您的代码甚至可以绕过代理与原始目标Bean进行交互。 因此,将拦截器应用于init方法将是不一致的,因为这样做会将目标Bean的生命周期与其代理/拦截器耦合在一起,并且当您的代码直接与原始目标Bean交互时会留下奇怪的语义。

结合生命周期机制

从 Spring 2.5 开始,您有三个控制 bean 生命周期行为的选项:继承InitializingBeanDisposableBean回调接口;自定义init()destroy()方法然后注明;和@PostConstruct 和 @PreDestroy 注释。您可以组合这些机制来控制给定的 bean。

//	详情点击上面的链接
public class CachingMovieLister { 
        @PostConstruct
        public void populateMovieCache() {
                // populates the movie cache upon initialization...
        }

        @PreDestroy
        public void clearMovieCache() {
                // clears the movie cache upon destruction...
        }
}

如果为 bean 配置了多个生命周期机制,并且每个机制都配置了不同的方法 name,则每个配置的方法都在下面列出的 order 中执行。但是,如果为多个这些生命周期机制配置了相同的方法 name(对于 example,init()表示初始化方法),则该方法将执行一次,如上一节中所述。

为同一 bean 配置的多个生命周期机制具有不同的初始化方法,如下所示:

  • @PostConstruct注释的方法
  • afterPropertiesSet()InitializingBean回调接口定义
  • 自定义配置的init()方法

Destroy 方法在同一个 order 中调用:

  • @PreDestroy注释的方法
  • destroy()DisposableBean回调接口定义
  • 自定义配置的destroy()方法
启动和关闭回调(暂时不太理解)

Lifecycle接口为具有自己的生命周期要求(例如启动和停止某些后台进程)的任何对象定义了基本方法:

public interface Lifecycle {

    void start();

    void stop();

    boolean isRunning();
}

任何Spring管理的对象都可以实现该Lifecycle接口。然后,当 ApplicationContext自身接收到启动和停止信号时(例如,对于运行时的停止/重新启动场景),它会将那些调用级联到Lifecycle在该上下文中定义的所有实现。它通过委派给一个LifecycleProcessor,以实现此目的,如以下清单所示:

public interface LifecycleProcessor extends Lifecycle {

    void onRefresh();

    void onClose();
}

请注意,LifecycleProcessor本身是Lifecycle 接口的扩展。它还添加了其他两种方法来对正在刷新和关闭的上下文做出反应。

请注意,常规org.springframework.context.Lifecycle接口是用于显式启动和停止通知的普通协议,并不意味着在上下文刷新时自动启动。为了对特定bean的自动启动进行精细控制(包括启动阶段),请考虑实施org.springframework.context.SmartLifecycle。另外,请注意,不能保证会在销毁之前发出停止通知。在常规关闭时,Lifecycle在传播常规销毁回调之前,所有Bean首先都会收到停止通知。但是,在上下文生命周期中进行热刷新或中止刷新尝试时,仅调用destroy方法。

启动和关闭调用的 order 非常重要。如果任何两个 object 之间存在“depends-on”关系,则依赖方将在其依赖之后启动,并且它将在其依赖之前停止。但是,有时直接依赖性是未知的。您可能只知道某种类型的 objects 应该在另一种类型的 objects 之前开始。在这些情况下,SmartLifecycle接口定义了另一个选项,即,Phased上定义的getPhase()方法。

public interface Phased {
    int getPhase();
}
public interface SmartLifecycle extends Lifecycle, Phased {

    boolean isAutoStartup();

    void stop(Runnable callback);
}

********

在非Web应用程序中正常关闭Spring IoC容器

本节仅适用于非Web应用程序。Spring的基于Web的 ApplicationContext实现已经有了相应的代码,可以在相关Web应用程序关闭时正常关闭Spring IoC容器。

如果您在非Web应用程序环境中使用Spring的IoC容器,例如,在富客户端桌面环境中;您向JVM注册了一个关闭挂钩。这样做可以确保正常关机,并在您的Singleton bean上调用相关的destroy方法,以便释放所有资源。当然,您仍然必须正确配置和实现这些destroy回调。

要注册关闭挂钩,请调用registerShutdownHook()ConfigurableApplicationContext接口上声明的方法:

import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public final class Boot {

        public static void main(final String[] args) throws Exception {
                ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");

                // add a shutdown hook for the above context...
                ctx.registerShutdownHook();

                // app runs here...

                // main method exits, hook is called prior to the app shutting down...
        }
}
Aware interfaces

看到Spring源码中接口以Aware结尾的接口(XXXAware)在Spring中表示对XXX可以感知,通俗点解释就是:如果在某个类里面想要使用spring的一些东西,就可以通过实现XXXAware接口告诉Spring,Spring看到后就会给你送过来,而接收的方式是通过实现接口唯一方法set-XXX.比如:有一个类想要使用当前的ApplicationContext,那么我们只需要让它实现ApplicationContextAware接口,然后实现接口中的唯一方法

void setApplicationContext(ApplicationContext applicationContext)1

就可以了,spring会自动调用这个方法将applicationContext传给我们,我们只需要接受就可以了,并可以用接收到的内容做一些业务逻辑

注意:除了通过实现Aware结尾接口获取spring内置对象,也可以通过@Autowired注解直接注入相关对象,如下:
(如果需要用到静态方法中,如工具方法,还是采用实现接口的方式)

口以Aware结尾的接口(XXXAware)在Spring中表示对XXX可以感知,通俗点解释就是:如果在某个类里面想要使用spring的一些东西,就可以通过实现XXXAware接口告诉Spring,Spring看到后就会给你送过来,而接收的方式是通过实现接口唯一方法set-XXX.比如:有一个类想要使用当前的ApplicationContext,那么我们只需要让它实现ApplicationContextAware接口,然后实现接口中的唯一方法

void setApplicationContext(ApplicationContext applicationContext)1

就可以了,spring会自动调用这个方法将applicationContext传给我们,我们只需要接受就可以了,并可以用接收到的内容做一些业务逻辑

注意:除了通过实现Aware结尾接口获取spring内置对象,也可以通过@Autowired注解直接注入相关对象,如下:
(如果需要用到静态方法中,如工具方法,还是采用实现接口的方式)

略。。。。。。。

1.8 Container Extension Point

略 。。。。。。。。。。。。。。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值