Spring Bean

16 篇文章 0 订阅

Spring Bean

Bean定义

bean是构成应用程序的支柱,由spring IOC容器管理。bean是一个被实例化、组装、并通过Spring Ioc容器所管理的对象。

bean有如下属性

属性描述
class强制的,指定用来创建bean 的bean类
name这个属性指定唯一的 bean 标识符,在基于XML的配置中,你可以使用ID和/或name属指定bean标识符
scope定义创建的对象的作用域
constructor-arg注入依赖关系
autowiring mode注入依赖关系的
lazy-initialization mode延迟初始化的 bean 告诉 IoC 容器在它第一次被请求时,而不是在启动时去创建一个 bean 实例。
initialization 方法在 bean 的所有必需的属性被容器设置之后,调用回调方法。
destruction 方法当包含该 bean 的容器被销毁时,使用回调方法

Spring 配置元数据

Spring IoC 容器完全由实际编写的配置元数据的格式解耦。有下面三个重要的方法把配置元数据提供给 Spring 容器:

  • 基于 XML 的配置文件
  • 基于注解的配置
  • 基于 Java 的配置

基于XML的配置文件

<bean id="exampleBean" class="examples.ExampleBean"/>

<bean name="anotherExample" class="examples.ExampleBeanTwo"/>

下面看一个稍复杂的例子

// 等效于ClientService实现的内容
//工厂bean
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
    <!-- inject any dependencies required by this locator bean -->
</bean>
//通过工厂bean创建的bean
<bean id="clientService"
    factory-bean="serviceLocator"
    factory-method="createClientServiceInstance"/>

<bean id="accountService"
    factory-bean="serviceLocator"
    factory-method="createAccountServiceInstance"/>

//--------------------

//返回一个clientService对象
public class ClientService {
    private static ClientService clientService = new ClientService();
    private ClientService() {}

    public static ClientService createInstance() {
        return clientService;
    }
    public AccountService createAccountServiceInstance() {
        return accountService;
    }
}

<bean id="serviceLocator" class="examples.DefaultServiceLocator">
    <!-- inject any dependencies required by this locator bean -->
</bean>

<bean id="clientService"
    factory-bean="serviceLocator"
    factory-method="createClientServiceInstance"/>

<bean id="accountService"
    factory-bean="serviceLocator"
    factory-method="createAccountServiceInstance"/>

这里先介绍基于XML的配置,其他的后面介绍

Bean的作用域

ScopeDescription
singleton(默认的) 在spring IoC容器仅存在一个Bean实例,Bean以单例方式存在。
prototype每次从容器中调用Bean时,都返回一个新的实例,即每次调用getBean()时,相当于执行newXxxBean()
request每次HTTP请求都会创建一个新的Bean,该作用域仅适用于WebApplicationContext环境
session同一个HTTP Session共享一个Bean,不同Session使用不同的Bean,该作用域仅适用于WebApplicationContext环境
application将单个bean定义范围限定为WebSocket的生命周期。该作用域仅适用于WebApplicationContext环境
websocket将单个bean定义范围限定为WebSocket的生命周期。该作用域仅适用于WebApplicationContext环境
singleton 作用域

Singleton是单例类型,就是在创建起容器时就同时自动创建了一个bean的对象,不管你是否使用,他都存在了,每次获取到的对象都是同一个对象。注意,Singleton作用域是Spring中的缺省作用域(默认的)。

singleton作用域

在bean中配置singleon作用域

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

<!-- 以下是等效的配置方式 -->
<bean id="accountService" class="com.something.DefaultAccountService" scope="singleton"/>

实例

在HelloWorld的基础上,我们将配置改一下

 <!-- Beans.xml的内容 -->
<bean id="helloWorld" class="com.tutorialspoint.HelloWorld" 
     scope="singleton">
 </bean>
<!-- 注意,这里的scope可以不写,因为默认就是 singleton 的作用域 -->
package com.tutorialspoint;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

public class MainApp {
	public static void main(String[] arg) {
		//得到ApplicationContext对象
		ApplicationContext context = new FileSystemXmlApplicationContext("src/Beans.xml");
		
        //向context 申请,一个实例
		HelloWorld obj1 = (HelloWorld)context.getBean("helloWorld");
		obj1.setMessage("I am message!");
		System.out.println("obj1 message:" + obj1.getMessage());
		
        //再次 向 context 申请,一个实例
		HelloWorld obj2 = (HelloWorld)context.getBean("helloWorld");
		System.out.println("obj2 message:" + obj2.getMessage());
		
	}
}
/*输出
obj1 message:I am message!
obj2 message:I am message!
*/

可以看到,尽管向 context 申请了两次,但是,他们都是同一个对象

prototype 作用域

​ 当一个bean的作用域为Prototype,表示一个bean定义对应多个对象实例。Prototype作用域的bean会导致在每次对该bean请求(将其注入到另一个bean中,或者以程序的方式调用容器的getBean()方法)时都会创建一个新的bean实例。==Prototype是原型类型,它在我们创建容器的时候并没有实例化,而是当我们获取bean的时候才会去创建一个对象,而且我们每次获取到的对象都不是同一个对象。==根据经验,对有状态的bean应该使用prototype作用域,而对无状态的bean则应该使用singleton作用域。

prototpe作用域

在bean中配置prototype作用域

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

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

实例

我们接着在上一个项目的基础上继续修改

 <!-- Beans.xml的内容 -->
<bean id="helloWorld" class="com.tutorialspoint.HelloWorld" 
     scope="prototype">
 </bean>

MainApp.java不做修改

obj1 message:I am message!
obj2 message:null

可以看到,obj1,设置了值,所以 输出了 I am message!,而 obj2 没有设置值,message为null;obj1 和 obj2 是两个不同的实例对象

request session application websocket作用域

我们先要配置WebApplicationContext

  • 如果是在spring MVC中,那么不需要特别的配置,spring的DispatchServlet已经帮你处理好了。

  • 如果你用的是web2.5,org.springframework.web.context.request.RequestContextListener ServletRequestListener,要监听这个接口

    <!-- web.xml -->
    <web-app>
        ...
        <listener>
            <listener-class>
                org.springframework.web.context.request.RequestContextListener
            </listener-class>
        </listener>
        ...
    </web-app>
    
  • 如果使用的环境没有监听接口,可以用过滤器

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

配置好后,这些作用域可以这么写

reqeust

<!-- Beans.xml -->
<bean id="loginAction" class="com.something.LoginAction" scope="request"/>

或者是

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

​ Spring容器通过对每个HTTP请求使用loginAction bean定义来创建LoginAction bean的新实例。也就是说,loginAction bean的范围是HTTP请求级别。

session

<!-- Beans.xml -->
<bean id="userPreferences" class="com.something.UserPreferences" scope="session"/>

或者是

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

​ Spring容器通过在单个HTTP会话的生存期内使用userPreferences bean定义来创建UserPreferences bean的新实例。换句话说,userPreferences bean在HTTP会话级别有效地作用域。

application

<!-- Beans.xml -->
<bean id="appPreferences" class="com.something.AppPreferences" scope="application"/>

或者是

@ApplicationScope
@Component
public class AppPreferences {
    // ...
}

​ Spring容器通过对整个Web应用程序使用appPreferences bean定义一次来创建AppPreferences bean的新实例。也就是说,appPreferences bean的作用域是ServletContext级别,并存储为常规的ServletContext属性。这有点类似于Spring单例bean,但在两个重要方面有所不同:它是每个ServletContext的单例,而不是每个Spring的’ApplicationContext’(在任何给定的Web应用程序中可能有几个),它实际上是暴露的,因此作为ServletContext属性可见。

这里没有看懂,先过(这部分,是来自官方参考文档中的内容)

用户自定义的作用域

需要继承org.springframework.beans.factory.config.Scope接口

//接口方法

Object get(String name, ObjectFactory objectFactory);

Object remove(String name);

void registerDestructionCallback(String name, Runnable destructionCallback);

String getConversationId()
//注册自定义作用域
void registerScope(String scopeName, Scope scope);


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

然后就可以使用了

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

也可以使用xml来配置


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


这里只是简略说一下,具体参考官方文档

要注意的地方

​ 对应bean的作用域,如果将一个作用域大的bean对象,注入到作用域小的对象,此时发生的行为就可能不是我们预期的样子,就比如说 将一个 session-scope bean作为依赖注入singleton bean ,这种会发生非预期的结果。

​ 因此,我们可以用代理,来配置

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

这将创建一个基于 CGLIB 的代理,这种代理只拦截公共的方法调用

如果想拦截私有的,那么需要修改 <aop:scoped-proxy proxy-target-class="false" 成这样子,使用jdk标准的代理(这意味着作用域bean的类必须至少实现一个接口)

具体的参看官方文档spring core 的1.5

Bean 生命周期

​ 当一个 bean 被实例化时,它可能需要执行一些初始化使它转换成可用状态。同样,当 bean 不再需要,并且从容器中移除时,可能需要做一些清除工作。

​ 为了定义安装和拆卸一个 bean,我们只要声明带有 init-method 和/或 destroy-method 参数的 。init-method 属性指定一个方法,在实例化 bean 时,立即调用该方法。同样,destroy-method 指定一个方法,只有从容器中移除 bean 之后,才能调用该方法。

Bean的生命周期可以表达为:Bean的定义——Bean的初始化——Bean的使用——Bean的销毁

初始化回调

org.springframework.beans.factory.InitializingBean 接口指定一个单一的方法:

void afterPropertiesSet() throws Exception;

不建议使用接口来初始化,回调,这样会增加不必要的耦合代码到spring

建议使用注解的方式或者是xml配置中的init-Method

<!-- 在Beans.xml中指定初始化方法 -->
<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>
public class ExampleBean {
    public void init() {
        // do some initialization work
    }
}

或者是 耦合到spring中的写法(不建议)

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

org.springframework.beans.factory.DisposableBean 接口指定一个单一的方法:

void destroy() throws Exception;

不建议使用接口来销毁,回调,这样会增加不必要的耦合代码到spring

建议使用注解的方式或者是xml配置中的destroy-Method

<bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="cleanup"/>
public class ExampleBean {

    public void cleanup() {
        // do some destruction work (like releasing pooled connections)
    }
}

或者是,耦合到spring中的写法(不建议)

<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements DisposableBean {

    public void destroy() {
        // do some destruction work (like releasing pooled connections)
    }
}
设置一个标准化的回调

我们可以提供一个标准化的 初始化回调 和 销毁回调 方式,作用于所有的bean

在Beans.xml中做如下配置

<!-- 可以看到,在顶层的 beans 上设置默认的回调方法 -->
<beans default-init-method="init" default-destroy-method="destroy">

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

</beans>

当bean中有 init 初始化方法,spring容器会在适当的时候,调用,如果没有,就不调用

public class DefaultBlogService implements BlogService {

    private BlogDao blogDao;

    public void setBlogDao(BlogDao blogDao) {
        this.blogDao = blogDao;
    }

    // this is (unsurprisingly) the initialization callback method
    public void init() {
        if (this.blogDao == null) {
            throw new IllegalStateException("The [blogDao] property must be set.");
        }
    }
    
    public void destroy() {
        if (this.blogDao != null) {
            throw new IllegalStateException("The [blogDao] property must be destroy.");
        }
    }
}
其他回调

当然,还会有其他的回调方式

<bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor">
    <!-- timeout value in milliseconds -->
    <property name="timeoutPerShutdownPhase" value="10000"/>
</bean>
<!-- 基于时间的 -->

具体参考官方文档,这里就不列举了

示例

依然在helloWorld的基础上

在Beans.xml中指定初始化和销毁回调

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="helloWorld" class="com.tutorialspoint.HelloWorld" init-method="init" destroy-method="destroy">   
    	
    </bean>

</beans>

HelloWorld.java中增加初始化和销毁方法

package com.tutorialspoint;

public class HelloWorld {
	private String message;

	public String getMessage() {
		return message;
	}

	public void setMessage(String message) {
		this.message = message;
	}
	
	public void init() {
		System.out.println("bean 初始化 回调,调用了init()");
	}
	
	public void destroy() {
		System.out.println("bean 销毁 回调,调用了destroy()");
	}
}

MainApp.java

package com.tutorialspoint;

import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

public class MainApp {
	public static void main(String[] arg) {
		
		AbstractApplicationContext  context = new FileSystemXmlApplicationContext("src/Beans.xml");
		
		HelloWorld obj1 = (HelloWorld)context.getBean("helloWorld");
		obj1.setMessage("I am message!");
		System.out.println("obj1 message:" + obj1.getMessage());
		
		HelloWorld obj2 = (HelloWorld)context.getBean("helloWorld");
		System.out.println("obj2 message:" + obj2.getMessage());
		
		context.registerShutdownHook();
	}
}
/*
结果
bean 初始化 回调,调用了init()
obj1 message:I am message!
obj2 message:I am message!
bean 销毁 回调,调用了destroy()
*/

可以看到,在创建bean 的时候,调用了 我们自定义的init方法,销毁的时候,也调用了 自定义的销毁方法

Spring Bean 后置处理器

Bean 后置处理器允许在调用初始化方法前后对 Bean 进行额外的处理。

BeanPostProcessor 接口定义回调方法,你可以实现该方法来提供自己的实例化逻辑,依赖解析逻辑等。你也可以在 Spring 容器通过插入一个或多个 BeanPostProcessor 的实现来完成实例化,配置和初始化一个bean之后实现一些自定义逻辑回调方法。

你可以配置多个 BeanPostProcessor 接口,通过设置 BeanPostProcessor 实现的 Ordered 接口提供的 order 属性来控制这些 BeanPostProcessor 接口的执行顺序。

BeanPostProcessor 可以对 bean(或对象)实例进行操作,这意味着 Spring IoC 容器实例化一个 bean 实例,然后 BeanPostProcessor 接口进行它们的工作。

ApplicationContext 会自动检测由 BeanPostProcessor 接口的实现定义的 bean,注册这些 bean 为后置处理器,然后通过在容器中创建 bean,在适当的时候调用它。

示例

在上一个代码的基础上新增和修改

新增一个实现了BeanPostProcessor 接口的类InitHelloWorld

package com.tutorialspoint;

import org.springframework.beans.factory.config.BeanPostProcessor;

public class InitHelloWorld implements BeanPostProcessor{
    //初始化前调用
	public Object postProcessBeforeInitialization(Object bean, String beanName) {
		
		System.out.println("BeforeInitialization:" + beanName);
		return bean;
	}
    //初始化后调用
	public Object postProcessAfterInitialization(Object bean, String beanName) {
		System.out.println("AfterInitialization:" + beanName);
		return bean;
	}
}

对于Beans.xml里的修改如下

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="helloWorld" class="com.tutorialspoint.HelloWorld" init-method="init" destroy-method="destroy">   </bean>
    
    <!-- 新增 -->
    <bean class="com.tutorialspoint.InitHelloWorld"></bean>

</beans>

MainApplication不变

可以看到结果如下:

BeforeInitialization:helloWorld
bean 初始化 回调,调用了init()
AfterInitialization:helloWorld
obj1 message:I am message!
obj2 message:I am message!
bean 销毁 回调,调用了destroy()

在对HelloWorld示例 初始化前,调用了postProcessBeforeInitialization,初始化后调用了postProcessAfterInitialization

Spring Bean 定义继承

bean 定义可以包含很多的配置信息,包括构造函数的参数,属性值,容器的具体信息例如初始化方法,静态工厂方法名,等等。

子 bean 的定义继承父定义的配置数据。子定义可以根据需要重写一些值,或者添加其他值。

Spring Bean 定义的继承与 Java 类的继承无关,但是继承的概念是一样的。你可以定义一个父 bean 的定义作为模板和其他子 bean 就可以从父 bean 中继承所需的配置。

当你使用基于 XML 的配置元数据时,通过使用父属性,指定父 bean 作为该属性的值来表明子 bean 的定义。

示例

继承关系可以在Beans.xml中定义

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="helloWorld" class="com.tutorialspoint.HelloWorld">   
    	<property name="message1" value="message1"></property>
    	<property name="message2" value="message2"></property>
    </bean>
    
    <!-- 这里定义了一个类,继承自 HelloWorld -->
    <bean id="helloIndia" class="com.tutorialspoint.HelloIndia" parent="helloWorld">
    	<property name="message1" value="重写了 message1"></property>
    	<property name="message3" value="增加了新的 message3"></property>
    </bean>

</beans>

我们在配置文件中可以定义继承

子类

package com.tutorialspoint;

public class HelloIndia {
	private String message1;
	private String message2;
	private String message3;
	public String getMessage1() {
		return message1;
	}
	public void setMessage1(String message1) {
		this.message1 = message1;
	}
	public String getMessage2() {
		return message2;
	}
	public void setMessage2(String message2) {
		this.message2 = message2;
	}
	public String getMessage3() {
		return message3;
	}
	public void setMessage3(String message3) {
		this.message3 = message3;
	}
}

父类

package com.tutorialspoint;

public class HelloWorld {
	private String message1;
	
	private String message2;

	public String getMessage1() {
		return message1;
	}

	public void setMessage1(String message1) {
		this.message1 = message1;
	}

	public String getMessage2() {
		return message2;
	}

	public void setMessage2(String message2) {
		this.message2 = message2;
	}
}

测试类

package com.tutorialspoint;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

public class MainApp {
	public static void main(String[] arg) {
		
		ApplicationContext  context = new FileSystemXmlApplicationContext("src/Beans.xml");
		
		HelloWorld obj1 = (HelloWorld)context.getBean("helloWorld");
		System.out.println("HelloWorld -》 message1:" + obj1.getMessage1());
		System.out.println("HelloWorld -》 message2:" + obj1.getMessage2());
		
		System.out.println("——---------------------------------------");
		HelloIndia obj2 = (HelloIndia)context.getBean("helloIndia");
		System.out.println("HelloIndia -》 message1:" + obj2.getMessage1());
		System.out.println("HelloIndia -》 message2:" + obj2.getMessage2());
		System.out.println("HelloIndia -》 message3:" + obj2.getMessage3());
	}
}

结果

HelloWorld -》 message1:message1
HelloWorld -》 message2:message2
——---------------------------------------
HelloIndia -》 message1:重写了 message1
HelloIndia -》 message2:message2
HelloIndia -》 message3:增加了新的 message3

可以看到在继承关系中,message1,被子类重写,message2被子类直接继承,子类中新增了message3属性

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值