文章目录
- 前言
- 1、IOC的作用
- 2、使用IOC
- 2.1、spring的配置方式
- 2.2、简单使用IOC
- 2.3、深入IOC
前言
IOC主要是为了将Bean之间的依赖抽取到框架层面进行管理。让代码达到面向接口编程是IOC的一个重要目的。
本文例子按照spring官方文档来编写,在此也建议各位,不用找一些spring应用的书籍来看,直接官网上面对着撸代码就完事了。(看过spring in action,确实不如官网来得爽)如果遇到一些不经常使用的特性(例如事件机制等)可以先在搜索引擎上面大致学习一下,然后再理解官网上的文档就轻松多了。
1、IOC的作用
一个例子
不使用IOC:
情景一:
package com.my.service;
import com.my.domain.Good;
/**
* @description: 订单service
* @author: lige
* @create: 2018-11-05
**/
public class OrderService {
/**
* @Description: 查看该订单是否合法
* @param: id
* @return: boolean
* @Author: lige
* @Date: 2018/11/5
*/
public boolean checkOrder(Order order) {
//商品服务
GoodsService goodsService = new GoodsServiceImpl();
//地址服务
AddressService addressService = new AddressServiceImpl();
//优惠券服务
DiscountService discountService = new DiscountServiceImpl();
...
Good good= goodsService.getGoodInfo(order.getGoodId());
Address address = addressService.getAddressInfo(order.getAddressId());
Discount discount = discountService.getDiscoutInfo(order.getDiscountId());
...
}
}
在一个订单信息检查的方法中,我们需要手动new 各种服务类,来实现我们的业务,可能你会讲这才只new了三个啊,假如后面还有其他服务呢?假如订单service每个方法都让你new一遍这些服务类呢?
情景二:
还是上面的例子,不过上面的GoodsService有重大的业务重构,实现类由GoodsServiceImpl变为了GoodsNewServiceImpl,这个时候要怎么处理呢?只能一个个找出引用GoodsServiceImpl的地方,替换成GoodsNewServiceImpl,工程量可想而知。
上面两个情景就引出了使用IOC的好处:
- 对象拿来就用,不需要自己组装,将类之间的依赖解耦
- 面向接口编程
- 管理对象的整个生命周期,并在对象的生命周期的各个环节提供了扩展点(生命周期后面会涉及)
2、使用IOC
org.springframework.beans和org.springframework.context包是Spring框架的IoC容器的基础。该 BeanFactory 接口提供了一种能够管理任何类型对象的高级配置机制。 ApplicationContext 是一个子界面BeanFactory。它增加了与Spring的AOP功能的更容易的集成; 消息资源处理(用于国际化),事件发布; 和特定于应用程序层的上下文,例如WebApplicationContext 在Web应用程序中使用的上下文。
2.1、spring的配置方式
- 基于XML的配置
- 基于注解的配置
- 基于java代码的配置
2.2、简单使用IOC
下面,我们简单配置一个bean,并实例化一个容器对象
spring配置文件
<?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
http://www.springframework.org/schema/beans/spring-beans.xsd">
<beans>
<bean id="good" class="com.my.domain.Good">
<property name="name" value="lige"/>
<property name="price" value="3.33"/>
</bean>
</beans>
</beans>
需要spring管理的bean
package com.my.domain;
import java.text.MessageFormat;
/**
* @description: 商品
* @author: lige
* @create: 2018-11-05
**/
public class Good {
private String name;
private float price;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public float getPrice() {
return price;
}
public void setPrice(float price) {
this.price = price;
}
@Override
public String toString() {
return MessageFormat.format("商品名为{0},商品价格为{1}",name,price);
}
}
test code
@Test
public void test1(){
ApplicationContext context = new ClassPathXmlApplicationContext("spring-app.xml");
Good good = context.getBean("good", Good.class);
System.out.println(good.toString());
}
2.3、深入IOC
Context初始化和使用
ApplicationContext是高级工厂的接口,能够维护不同bean及其依赖项的注册表。使用该方法,T getBean(String name, Class requiredType)您可以检索Bean的实例。
// 初始化
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
// 使用ApplicationContext 获取bean
PetStoreService service = context.getBean("petStore", PetStoreService.class);
// use configured instance
List<String> userList = service.getUsernameList();
2.3.1、bean概述
Spring IoC容器管理一个或多个bean。这些bean是使用您提供给容器的配置元数据创建的,例如,以XML 定义的形式 。
在容器本身内,这些bean定义表示为BeanDefinition 对象,其中包含以下元数据(以及其他信息):
- 包限定的类名:通常是正在定义的bean的实际实现类。
- Bean行为配置元素,说明bean在容器中的行为方式(范围,生命周期回调等)。
- 引用bean执行其工作所需的其他bean; 这些引用也称为协作者或依赖项。
- 要在新创建的对象中设置的其他配置设置,例如,在管理连接池的Bean中使用的连接数或池的大小限制。
此元数据转换为组成每个bean定义的一组属性。
- class 完整类名
- name bean名称
- scope 范围
- constructor arguments 构造器参数
- properties
- autowiring mode 自动装配模式
- lazy-initialization mode 延迟初始化模式
- initialization method 初始化方法
- destruction method 销毁对象方法
2.3.1.1、bean实例化方式
- 使用构造函数实例化
- 静态工厂方法实例化
- 实例工厂方法实例化
使用构造函数实例化
<bean id="good" class="com.my.domain.Good"></bean>
使用静态工厂方法实例化
定义使用静态工厂方法创建的bean时,可以使用该class 属性指定包含static工厂方法的类,以factory-method指定工厂方法本身的名称。您应该能够调用此方法(使用后面描述的可选参数)并返回一个活动对象,随后将其视为通过构造函数创建的对象。这种bean定义的一个用途是static在遗留代码中调用工厂。
<?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
http://www.springframework.org/schema/beans/spring-beans.xsd">
<beans>
<!--<bean id="good" class="com.my.domain.Good">-->
<!--<property name="name" value="lige"/>-->
<!--<property name="price" value="3.33"/>-->
<!--</bean>-->
<bean id="good2" class="com.my.factory.GoodFactory" factory-method="getGood">
</bean>
</beans>
</beans>
package com.my.factory;
import com.my.domain.Good;
/**
* @description: 商品工厂
* @author: lige
* @create: 2018-12-06
**/
public class GoodFactory {
/**
* 获取good实例方法
* @return
*/
public static Good getGood(){
Good good = new Good();
good.setName("iphone");
good.setPrice(333.33F);
return good;
}
}
使用实例工厂实例化
与通过静态工厂方法实例化类似,使用实例工厂方法进行实例化会从容器调用现有bean的非静态方法来创建新bean。要使用此机制,请将该class属性保留为空,并在factory-bean属性中指定当前(或父/祖先)容器中bean的名称,该容器包含要调用以创建对象的实例方法。使用factory-method属性设置工厂方法本身的名称。
<beans>
<bean id="factory" class="com.my.factory.AnotherGoodFactory" />
<bean id="good" factory-bean="factory" factory-method="getGood"/>
</beans>
package com.my.factory;
import com.my.domain.Good;
/**
* @description:
* @author: lige
* @create: 2018-12-06
**/
public class AnotherGoodFactory {
public Good getGood(){
Good good = new Good();
good.setName("vivo");
good.setPrice(444.44F);
return good;
}
}
2.3.1.2、bean范围
Spring Framework支持六个范围,其中四个范围仅在您使用Web感知时才可用ApplicationContext。
范围 | 描述 |
---|---|
singleton | (默认)将每个Spring IoC容器的单个bean定义范围限定为单个对象实例。 |
prototype | 将单个bean定义范围限定为任意数量的对象实例。 |
request | 将单个bean定义范围限定为单个HTTP请求的生命周期; 也就是说,每个HTTP请求都有自己的bean实例,它是在单个bean定义的后面创建的。仅在Web感知Spring的上下文中有效ApplicationContext。 |
session | 将单个bean定义范围限定为HTTP的生命周期Session。仅在Web感知Spring的上下文中有效ApplicationContext。 |
application | 将单个bean定义到一个生命周期的范围ServletContext。仅在Web感知Spring的上下文中有效ApplicationContext。 |
websocket | 将单个bean定义到一个生命周期的范围WebSocket。仅在Web感知Spring的上下文中有效ApplicationContext。 |
2.3.1.2.1、singleton范围
只管理单个bean的一个共享实例,并且对具有与该bean定义匹配的id或id的bean的所有请求都会导致Spring容器返回一个特定的bean实例。
换句话说,当您定义一个bean定义并且它的范围是一个单例时,Spring IoC容器只创建该bean定义定义的对象的一个实例。此单个实例存储在此类单例bean的缓存中,并且该命名Bean的所有后续请求和引用都将返回缓存对象。
<beans>
<bean id="good" class="com.my.domain.Good" scope="singleton">
<property name="name" value="good"/>
<property name="price" value="33.33"/>
</bean>
</beans>
@Test
public void test1(){
ApplicationContext context = new ClassPathXmlApplicationContext("spring-app.xml");
Good good1 = context.getBean("good",Good.class);
Good good2 = context.getBean("good",Good.class);
System.out.println(good1 == good2);
}
结果是true,说明两次获取的是同一个实例
2.3.1.2.2、prototype范围
bean的非单例原型范围部署导致每次发出对该特定bean的请求时都会创建一个新的bean实例。也就是说,将bean注入另一个bean,或者通过getBean()对容器的方法调用来请求它。通常,对所有有状态bean使用原型范围,对无状态bean使用单例范围。
下图说明了Spring原型范围。数据访问对象(DAO)通常不配置为原型,因为典型的DAO不保持任何会话状态。
以下示例将bean定义为XML中的原型:
<bean id="accountService" class="com.foo.DefaultAccountService" scope="prototype"/>
与其他作用域相比,Spring不管理原型bean的完整生命周期:容器实例化,配置和组装原型对象,并将其交给客户端,而不再记录该原型实例。因此,尽管无论范围如何都在所有对象上调用初始化生命周期回调方法,但在原型的情况下,不会调用已配置的销毁生命周期的回调。客户端代码必须清理原型范围的对象并释放原型bean所持有的昂贵资源。要让Spring容器释放原型范围的bean所拥有的资源,请尝试使用自定义bean后处理器,它包含对需要清理的bean的引用。
2.3.1.2.3、请求,会话,应用程序和WebSocket范围
在request,session,application,和websocket范围是仅如果使用基于web的Spring可ApplicationContext实现(如 XmlWebApplicationContext)。如果你将这些范围与常规的Spring IoC容器一起使用,例如ClassPathXmlApplicationContext,那么IllegalStateException就会抛出一个未知bean范围的异常。
初始化web配置
要使用request、session和 global session作用域的bean(即具有web作用域的bean), 在开始设置bean定义之前,还要做少量的初始配置。请注意,假如你只想要“常规的”作用域,(singleton和prototype),就不需要这一额外的设置。
在目前的情况下,根据你的特定servlet环境,有多种方法来完成这一初始设置…
如果你用Spring Web MVC,即用SpringDispatcherServlet或DispatcherPortlet来处理请求,则不需要做特别的配置:DispatcherServlet 和 DispatcherPortlet已经处理了所有有关的状态
当使用了Spring’s DispatcherServlet以外的Servlet 2.4及以上的Web容器时(如使用JSF或Struts),你需要在Web应用的’web.xml’文件中增加 javax.servlet.ServletRequestListener 定义。
<!--ServletRequestListener监听HTTP请求事件,Web服务器接收的每次请求都会通知该监听器。-->
<listener>
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>
针对servlet2.4以及之前版本的情况,使用Filter配置如下:
<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>
实例:这里只举request为例子
web.xml中配置
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<!--contextConfigLocation在 ContextLoaderListener类中的默认值是 /WEB-INF/applicationContext.xml-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:web-app.xml</param-value>
<!-- <param-value>classpath:applicationContext*.xml</param-value> -->
</context-param>
<!--ContextLoaderListener通过配置信息初始化容器-->
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
<listener>
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>
<servlet>
<servlet-name>hello</servlet-name>
<servlet-class>com.my.controller.HelloController</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
</web-app>
spring配置文件 web-app.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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<beans>
<bean id="good" class="com.my.domain.Good" scope="request"></bean>
<bean id="springContextUtil" class="com.my.SpringContextUtil"></bean>
</beans>
</beans>
非Spring容器管理的对象获取Spring管理的bean的工具类(具体可参考这篇文:https://blog.csdn.net/qq_21955179/article/details/84877691)
package com.my;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
/**
* @description:
* @author: lige
* @create: 2018-12-07
**/
public class SpringContextUtil implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringContextUtil.applicationContext = applicationContext;
}
public static ApplicationContext getApplicationContext() {
return SpringContextUtil.applicationContext;
}
}
HelloController.java
package com.my.controller;
import com.my.domain.Good;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import javax.security.sasl.SaslServer;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
* @description:
* @author: lige
* @create: 2018-12-07
**/
public class HelloController extends HttpServlet {
private Good good;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
PrintWriter writer = resp.getWriter();
// // 设置响应内容类型
// resp.setContentType("text/html");
// writer.write("HELLO WORLD~~");
// writer.flush();
ServletContext servletContext = getServletContext();
WebApplicationContext context =WebApplicationContextUtils.getWebApplicationContext(servletContext);
Good good =context.getBean("good",Good.class);
System.out.println(good);
}
}
附一张结果图:
2.3.1.2.4、自定义bean范围
spring支持自己定义bean的范围,具体可以参考官方文档:https://docs.spring.io/spring/docs/5.0.11.RELEASE/spring-framework-reference/core.html#beans-factory-scopes-custom
2.3.1.3、自定义bean
2.3.1.3.1、bean生命周期的回调
初始化回调
该org.springframework.beans.factory.InitializingBean接口允许bean在容器设置了bean的所有必需属性之后执行初始化工作。的InitializingBean接口规定了一个方法:
void afterPropertiesSet() throws Exception;
建议您不要使用该InitializingBean接口,因为它会不必要地将代码耦合到Spring。或者,使用@PostConstruct注释或指定POJO初始化方法。对于基于XML的配置元数据,可以使用该init-method属性指定具有void无参数签名的方法的名称。使用Java配置,您可以使用initMethod属性@Bean。
例子1 xml配置:
<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>
public class ExampleBean {
public void init() {
// do some initialization work
}
}
例子2 实现接口:
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements InitializingBean {
public void afterPropertiesSet() {
// do some initialization work
}
}
注意:如果配置了init-method,又调用了afterPropertiesSet,那么afterPropertiesSet方法在init-method指定的方法之前执行
销毁回调
实现org.springframework.beans.factory.DisposableBean接口允许bean在包含它的容器被销毁时获得回调。的 DisposableBean接口规定了一个方法:
void destroy() throws Exception;
建议您不要使用DisposableBean回调接口,因为它会不必要地将代码耦合到Spring。或者,使用@PreDestroy注释或指定bean定义支持的泛型方法。使用基于XML的配置元数据,您可以使用该< bean/>的destroy-method属性。使用Java配置,您可以使用destroyMethod属性@Bean。例如,以下定义:
<bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="cleanup"/>
public class ExampleBean {
public void cleanup() {
// do some destruction work (like releasing pooled connections)
}
}
与以下内容完全相同
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements DisposableBean {
public void destroy() {
// do some destruction work (like releasing pooled connections)
}
}
但不会将代码耦合到Spring。
生命周期机制
从Spring 2.5开始,您有三个控制bean生命周期行为的选项:
- InitializingBean和 DisposableBean回调接口
- 配置 init()和destroy()方法
- 使用@PostConstruct和@PreDestroy 注释
为同一个bean配置的多个生命周期机制,使用不同的初始化方法,初始化顺序如下所示:
- 注解 @PostConstruct 修饰的方法
- 由InitializingBean回调接口定义的afterPropertiesSet()方法
- 自定义配置的init()方法
Destroy方法以相同的顺序调用:
- 注解@PreDestroy修饰的方法
- 由DisposableBean回调接口定义的destroy()方法
- 自定义配置的destroy()方法
例子:
<?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:context="http://www.springframework.org/schema/context"
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">
<context:component-scan base-package="com.my.domain"/>
<beans>
<bean id="good" class="com.my.domain.Good" init-method="init" destroy-method="myDestoryMethod">
<property name="name" value="西红柿"/>
</bean>
</beans>
</beans>
package com.my.domain;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.text.MessageFormat;
/**
* @description: 商品
* @author: lige
* @create: 2018-11-05
**/
public class Good implements InitializingBean , DisposableBean {
private String name;
private float price;
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("afterPropertiesSet方法 调用");
}
@Override
public void destroy() throws Exception {
System.out.println("destroy方法 调用");
}
@PostConstruct
public void postConstruct(){
System.out.println("PostConstruct方法 调用");
}
@PreDestroy
public void preDestory(){
System.out.println("preDestory方法 调用");
}
public void init() {
System.out.println("init方法 调用");
}
public void myDestoryMethod(){
System.out.println("myDestory方法 调用");
}
public Good() {
System.out.println("constructor方法 调用");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
System.out.println("setName 方法调用");
}
public float getPrice() {
return price;
}
public void setPrice(float price) {
this.price = price;
}
@Override
public String toString() {
return MessageFormat.format("商品名为{0},商品价格为{1}", name, price);
}
}
@Test
public void test1(){
ApplicationContext context = new ClassPathXmlApplicationContext("spring-app.xml");
Good good = context.getBean("good",Good.class);
System.out.println(good);
((ClassPathXmlApplicationContext) context).close();
}
结果:
感知spring容器变化
Spring提供了LifeCycle和SmartLifeCycle接口来在spring容器启动和关闭的时候做一些操作。具体可参考下这篇文章:https://blog.csdn.net/boling_cavalry/article/details/82051356
Spring在非web应用中关闭IoC容器 (registerShutdownHook)
本小结转自:https://my.oschina.net/huangcongmin12/blog/357538
在基于web的ApplicationContext实现中,已有相应的实现来处理关闭web应用时恰当地关闭Spring IoC容器。
但,如果你正在一个非web应用的环境下使用Spring的IoC容器,如dubbo服务,你想让容器优雅的关闭,并调用singleton的bean相应destory回调方法,你需要在JVM里注册一个“关闭钩子”(shutdown hook)。这一点非常容易做到,并且将会确保你的Spring IoC容器被恰当关闭,以及所有由单例持有的资源都会被释放。
为了注册“关闭钩子”,你只需要简单地调用在org.springframework.context.support.AbstractApplicationContext实现中的registerShutdownHook()方法即可。
package com.hcm.dubbo.service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class DubboServiceStart {
private static final Logger LOG = LoggerFactory.getLogger(DubboServiceStart.class);
public static final String DUBBO_PROVIDER = "spring/dubbo-provider.xml";
public static void init() {
LOG.info("开始启动dubo服务,载入的配置服务提供文件为[" + DUBBO_PROVIDER + "]");
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[] { DUBBO_PROVIDER });
context.registerShutdownHook();
context.start();
LOG.info("dubbo启动服务完毕,请查看日志");
String[] names = context.getBeanDefinitionNames();
System.out.print("Beans:");
for (String string : names) {
System.out.print(string + ",");
}
System.out.println();
}
public static void main(String[] args) throws InterruptedException {
DubboServiceStart.init();
while (true) {
try {
Thread.currentThread();
Thread.sleep(3L);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
2.3.1.4、ApplicationContextAware和BeanNameAware
2.3.1.4.1、获取容器能力—ApplicationContextAware
当创建一个实现org.springframework.context.ApplicationContextAware接口的对象实例时,Spring将为该实例提供对ApplicationContext的引用。(即获取了容器实例)
public interface ApplicationContextAware {
void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}
因此,bean可以通过ApplicationContext接口以编程方式操作创建它们的ApplicationContext,或者通过将引用强制转换为此接口的已知子类(例如ConfigurableApplicationContext)来使用其他功能。一种用途是对其他bean进行编程检索。 有时这种能力很有用; 但是,通常你应该避免它,因为它将代码耦合到Spring并且不遵循Inversion of Control样式,将协作者作为属性提供给bean。ApplicationContext的其他方法提供对文件资源的访问,发布应用程序事件和访问MessageSource。这些附加功能在ApplicationContext的附加功能中描述
具体实例可以参看这篇文章:https://blog.csdn.net/qq_21955179/article/details/84877691
2.3.1.4.2、BeanNameAware
当创建实现org.springframework.beans.factory.BeanNameAware接口的类时,将为该类提供其在容器中定义的名称。
public interface BeanNameAware {
void setBeanName(String name) throws BeansException;
}
2.3.1.5、其他Aware接口
aware,翻译过来是知道的,已感知的,意识到的,所以这些接口从字面意思应该是能感知到Aware前面内容的含义。比如前面的BeanNameAware表示可以是bean能感知到自己在容器中的name。
作用?
感知IOC容器。
怎么使用呢?
我们只需创建类来实现相关接口,再声明为bean,就可以被spring容器主动回调
Spring提供了哪些Aware呢?
接口 | 描述 |
---|---|
ApplicationContextAware | 实现了这个接口的类都可以获取到一个 ApplicationContext 对象. 可以获取容器中的所有 Bean |
ApplicationEventPublisherAware | 在 bean 中可以得到应用上下文的事件发布器, 从而可以在Bean中发布应用上下文的事件. |
BeanClassLoaderAware | 获取 bean 的类加载器 |
BeanFactoryAware | 获取 bean 的工厂 |
BeanNameAware | 获取 bean 在容器中的名字 |
BootstrapContextAware | 获取 BootstrapContext |
LoadTimeWeaverAware | 加载Spring Bean时织入第三方模块, 如AspectJ |
MessageSourceAware | 主要用于获取国际化相关接口 |
NotificationPublisherAware | 用于获取通知发布者 |
ResourceLoaderAware | 初始化时注入ResourceLoader |
ServletConfigAware | web开发过程中获取ServletConfig |
ServletContextAware | web开发过程中获取ServletContext信息 |
再次注意,这些接口的使用将您的代码与Spring API联系起来,并且不遵循Inversion of Control方式。因此,建议将它们用于需要以编程方式访问容器的基础结构bean。
2.3.1.6、容器扩展点
通常,应用程序开发者,不需要继承ApplicationContext的实现类。相反,Spring IoC容器可以通过插入特殊的集成接口的实现进行拓展。新的一节中,描述了这些集成接口。
2.3.1.6.1、使用 BeanPostProcessor 来定制bean
BeanPostProcessor接口定义了一些回调函数,可以通过实现它们提供自己的(覆盖容器默认的)实例化逻辑、依赖关系解析逻辑等等。如果想在Spring容器完成实例化、配置和初始化bean后实现一些用户逻辑,可以插入一个或多个BeanPostProcessor实现。
可以配置多个BeanPostProcessor实例,并且可以通过设置order属性来控制BeanPostProcessor的执行顺序。只有BeanPostProcessor实现了Ordered接口才能设置这个属性;如果自己编写BeanPostProcessor应该考虑实现Ordered接口。
作用:
BeanPostProcessor操作bean或者对象的实例,因此Spring IoC容器实例化一个bean的实例然后BeanPostProcessor开始工作。
BeanPostProcessor,通常用于在初始化Bean前后加入自定义逻辑,例如对@Autowired,@Value,@Resource,@Scheduled,@Async等注解的解析。
BeanPostProcessor拥有两个方法:
//在Bean初始化之前回调
public Object postProcessBeforeInitialization(Object bean, String beanName);
//在Bean初始化之后回调
public Object postProcessAfterInitialization(Object bean, String beanName)
如何使用
我们只要声明一个实现BeanPostProcessor接口的类,并将该类对象交由Spring容器进行管理,Spring会自动调用回调方法。
例子:
spring配置文件
<beans>
<bean id="student" class="com.my.domain.Student"/>
<bean class="com.my.MyBeanPostProcessor"/>
</beans>
自定义BeanPostProsessor
package com.my;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
/**
* @description:
* @author: lige
* @create: 2018-12-09
**/
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("postProcessBeforeInitialization 方法调用");
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("postProcessAfterInitialization 方法调用");
return bean;
}
}
test
@Test
public void test1(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-app.xml");
Student stu= context.getBean("student",Student.class);
System.out.println(stu);
context.registerShutdownHook();
}
结果:
spring内置扩展实现
- ScheduledAnnotationBeanPostProcessor 用于解析@Scheduled,定时任务;
- AsyncAnnotationBeanPostProcessor 用于解析@Async,异步执行;
- AutowiredAnnotationBeanPostProcessor 用于解析@Autowired,@Value注解,实现依赖注入;
- CommonAnnotationBeanPostProcessor 用于解析@PostConstruct,@PreDestroy,@Resource注解;
2.3.1.6.2、使用BeanFactoryPostProcessor定制配置元数据
实现该接口,可以在spring的bean创建之前,修改bean的定义属性。也就是说,Spring允许BeanFactoryPostProcessor在容器实例化任何其它bean之前读取配置元数据,并可以根据需要进行修改,例如可以把bean的scope从singleton改为prototype,也可以把property的值给修改掉。可以同时配置多个BeanFactoryPostProcessor,并通过设置’order’属性来控制各个BeanFactoryPostProcessor的执行次序。
注意:BeanFactoryPostProcessor是在spring容器加载了bean的定义文件之后,在bean实例化之前执行的。
BeanFactoryPostProcessor只提供了一个方法:
void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
很明显,ConfigurableListableBeanFactory这个参数就是核心所在。ConfigurableListableBeanFactory类提供了获取bean定义的能力,获取了Bean敌营,我们就可以修改啦。
BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;
示例:
自定义BeanFactoryPostProcessor
package com.my;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
/**
* @description:
* @author: lige
* @create: 2018-12-09
**/
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
BeanDefinition beanDefinition =beanFactory.getBeanDefinition("student");
beanDefinition.setScope("prototype");
}
}
spring配置
<beans>
<bean id="student" class="com.my.domain.Student" scope="singleton"/>
<bean class="com.my.MyBeanFactoryPostProcessor"/>
</beans>
配置文件中,我将student实例设置为单例范围,MyBeanFactoryPostProcessor将其更改为原型范围
@Test
public void test1(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-app.xml");
Student stu1= context.getBean("student",Student.class);
Student stu2= context.getBean("student",Student.class);
System.out.println(stu1);
System.out.println(stu2);
context.registerShutdownHook();
}
结果,bean的范围变更为了原型范围:
spring内置的BeanFactoryPostProcessor常见实现类
- org.springframework.beans.factory.config.PropertyPlaceholderConfigurer 占位符内的内容会被替换为属性文件中的匹配的value
- org.springframework.beans.factory.config.PropertyOverrideConfigurer
- org.springframework.beans.factory.config.CustomEditorConfigurer:用来注册自定义的属性编辑器(来指定string到特定类型转换的逻辑)
2.3.1.6.3、使用FactoryBean自定义实例化逻辑
FactoryBean接口是Spring IOC容器的实例化逻辑的可插拔点。如果有复杂的bean初始化,相对于冗长的xml方式,期望通过java编程的方式来表达,就可以通过创建自定义的FactoryBean来实现并将FactoryBean插入到IOC容器中。
上面的解释可能有些抽象,简单地说,FactoryBean就是可以创建Bean对象的工厂Bean,当我们不想或不能使用Spring帮我们自动构建对象的时候,我们可以通过创建一个FactoryBean接口的实例,来自己实现对象的创建。在Spring中,通过FactoryBean来扩展的遍地都是:AOP,ORM,事务管理,JMX,Remoting,Freemarker,Velocity等等。
FactoryBean的方法
public interface FactoryBean<T> {
//返回工厂创建的bean对象实例,可以是单例的也可以是多例
T getObject() throws Exception;
// 返回创建对象的类型
Class<?> getObjectType();
// 创建的对象是否单例
boolean isSingleton();
}
实例:
配置文件
<beans>
<bean class="com.my.MyStudentFactoryBean" id="student"/>
</beans>
自定义FactoryBean
package com.my;
import com.my.domain.Student;
import org.springframework.beans.factory.FactoryBean;
/**
* @description:
* @author: lige
* @create: 2018-12-09
**/
public class MyStudentFactoryBean implements FactoryBean<Student> {
@Override
public Student getObject() throws Exception {
Student student = new Student();
student.setName("拉拉");
return student;
}
@Override
public Class<?> getObjectType() {
return Student.class;
}
@Override
public boolean isSingleton() {
return true;
}
}
测试代码:
@Test
public void test1(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-app.xml");
Student stu= context.getBean("student",Student.class);
System.out.println(stu);
System.out.println(stu.getName());
context.registerShutdownHook();
}
结果:
注意:
这里getBean(“student”,Student.class)获取的FactoryBean构造的实例,如果要拿到MyStudentFactoryBean实例的话,需要getBean("&student",MyStudentFactoryBean.class)
2.3.2、依赖概述
DI存在两个主要变体,基于构造函数的依赖注入和基于Setter的依赖注入。
2.3.2.1、基于构造函数的依赖注入
使用构造函数实例化的bean,进行基于构造函数的依赖注入
<beans>
<!--构造函数参数类型匹配 通过name属性注入-->
<bean id="good" class="com.my.domain.Good">
<constructor-arg name="name" value="苹果"/>
<constructor-arg name="price" value="999.999"/>
</bean>
<!--构造函数参数类型匹配 通过index属性注入-->
<bean id="order" class="com.my.domain.Order">
<constructor-arg index="0" value="777777"/>
<constructor-arg index="1" ref="good"/>
</bean>
</beans>
package com.my.domain;
import java.text.MessageFormat;
/**
* @description: 商品
* @author: lige
* @create: 2018-11-05
**/
public class Good {
private String name;
private float price;
public Good(){
}
public Good(String name ,float price){
this.name = name;
this.price =price;
}
public Good(String name ){
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public float getPrice() {
return price;
}
public void setPrice(float price) {
this.price = price;
}
@Override
public String toString() {
return MessageFormat.format("商品名为{0},商品价格为{1}",name,price);
}
}
package com.my.domain;
import java.text.MessageFormat;
import java.util.Date;
/**
* @description: 订单类
* @author: lige
* @create: 2018-12-06
**/
public class Order {
private Integer id;
private Good good;
private Date orderDate;
public Order(Integer id,Good good){
this.good = good;
this.id = id;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Good getGood() {
return good;
}
public void setGood(Good good) {
this.good = good;
}
public Date getOrderDate() {
return orderDate;
}
public void setOrderDate(Date orderDate) {
this.orderDate = orderDate;
}
@Override
public String toString(){
return MessageFormat.format("id为{0},good信息:{1}",id,good.toString());
}
}
使用静态工厂实例化的bean,进行基于构造函数的依赖注入
这种配置的时候,需要配置将静态工厂方法当做bean的构造方法来配置
<beans>
<bean id="good" class="com.my.factory.GoodFactory" factory-method="getGood">
<constructor-arg index="0" value="computer"/>
<constructor-arg index="1" value="19999.99"/>
</bean>
</beans>
package com.my.factory;
import com.my.domain.Good;
/**
* @description: 商品工厂
* @author: lige
* @create: 2018-12-06
**/
public class GoodFactory {
/**
* 获取good实例方法
* @return
*/
public static Good getGood(String name,float price){
Good good = new Good();
good.setName(name);
good.setPrice(price);
return good;
}
}
注意下,这个时候我的Good类是没有有参构造方法的,所以这个时候spring是将constructor-arg中的依赖关系了静态刚放方法getGood的参数上
package com.my.domain;
import java.text.MessageFormat;
/**
* @description: 商品
* @author: lige
* @create: 2018-11-05
**/
public class Good {
private String name;
private float price;
public Good(){
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public float getPrice() {
return price;
}
public void setPrice(float price) {
this.price = price;
}
@Override
public String toString() {
return MessageFormat.format("商品名为{0},商品价格为{1}",name,price);
}
}
2.3.2.2、基于Setter的依赖注入
<?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
http://www.springframework.org/schema/beans/spring-beans.xsd">
<beans>
<bean id="good" class="com.my.domain.Good">
<property name="price" value="999.222"/>
<property name="name" value="华为"/>
</bean>
</beans>
</beans>
2.3.2.3、依赖循环
使用constructor-arg注入依赖,而且bean的scope是singleton时,可能会出现依赖循环,具体过程是A实例化的时候需要注入依赖B,这个时候去实例化B,又发现B实例化的时候需要A,这就尴尬了(⊙﹏⊙)。这个时候可以将注入方式改成setter,Spring会根据三级缓存和实例的提前暴露可以解决这个问题,即A实例化->注入B->发现B没实例化->实例化B->注入A->注入A->B实例化完成->A实例化完成(这里只给出思路,有时间补充具体的例子)
2.3.2.4、使用p命名空间或者c命名空间简化依赖注入的配置
使用p命名空间简化通过setter注入依赖配置
注意下,需要提前声明p命名空间:xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance”
<?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:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<beans>
<bean id="good" class="com.my.domain.Good" p:name="虾条" p:price="3.33">
</bean>
</beans>
</beans>
使用c命名空间简化通过constructor-arg注入依赖配置
<?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:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<beans>
<bean id="good" class="com.my.domain.Good" p:name="虾条" p:price="3.33">
</bean>
<bean id="order" class="com.my.domain.Order" c:id="77677" c:good-ref="good">
</bean>
</beans>
</beans>
2.3.2.5、依赖注入配置中的一些细节
2.3.2.5.1、依赖注入中的基本数据类型转换
上面的例子中有这这么一些配置,bean的属性类型是integer或者float,配置的时候却直接配置成了字符串,这是因为Spring会对这些字符串做转换,转换成所需的相应的基本数据类型
2.3.2.5.2、内部bean
在Spring框架中,只要bean只用于一个特定的属性,就建议将它声明为内部bean。< bean/>内部的元件< property/>或< constructor-arg/>元件定义了一个所谓的内部bean。
<bean id="outer" class="com.example.Customer">
<!-- instead of using a reference to a target bean, simply define the target bean inline -->
<property name="target">
<bean class="com.example.Person"> <!-- this is the inner bean -->
<property name="name" value="Fiona Apple"/>
<property name="age" value="25"/>
</bean>
</property>
</bean>
假设上面的例子中,Person类只在Cusomer类的target属性中使用,这个时候注入target属性的时候,可以使用内部bean的方式,这样就不会发生单独将Person声明为一个bean,被除了Customer对象之外的其他对象所依赖。
内部bean定义不需要定义的id或名称; 如果指定,则容器不使用这样的值作为标识符。容器还在创建时忽略scope:内部bean 始终是匿名的,并且始终使用外部bean创建它们。
作为一个极端情况,可以从自定义范围接收销毁回调,例如,对于包含在单例bean中的请求范围的内部bean:内部bean实例的创建将绑定到其包含的bean,但是销毁回调允许它参与请求范围的生命周期。这不是常见的情况; 内部bean通常只是共享其包含bean的范围。
2.3.2.5.3、集合
<bean id="moreComplexObject" class="example.ComplexObject">
<!-- results in a setAdminEmails(java.util.Properties) call -->
<property name="adminEmails">
<props>
<prop key="administrator">administrator@example.org</prop>
<prop key="support">support@example.org</prop>
<prop key="development">development@example.org</prop>
</props>
</property>
<!-- results in a setSomeList(java.util.List) call -->
<property name="someList">
<list>
<value>a list element followed by a reference</value>
<ref bean="myDataSource" />
</list>
</property>
<!-- results in a setSomeMap(java.util.Map) call -->
<property name="someMap">
<map>
<entry key="an entry" value="just some string"/>
<entry key ="a ref" value-ref="myDataSource"/>
</map>
</property>
<!-- results in a setSomeSet(java.util.Set) call -->
<property name="someSet">
<set>
<value>just some string</value>
<ref bean="myDataSource" />
</set>
</property>
</bean>
map的key和value或者是set的值,也可以是下面的任何一种:
bean | ref | idref | list | set | map | props | value | null
2.3.2.5.4、merge、parent、abstract
Spring中可以将bean的公共属性提出来共用,就像java中的继承一样。主要用到abstract和parent两个关键字。
- parent:生命一个父bean
- merge:将同一个属性的父bean的集合与子bean的集合合并,而不是覆盖
- abstract:abstract属性为true的bean 不会被实例化
例子:
<beans>
<bean id="parent" abstract="true" class="example.ComplexObject">
<property name="adminEmails">
<props>
<prop key="administrator">administrator@example.com</prop>
<prop key="support">support@example.com</prop>
</props>
</property>
</bean>
<bean id="child" parent="parent">
<property name="adminEmails">
<!-- the merge is specified on the child collection definition -->
<props merge="true">
<prop key="sales">sales@example.com</prop>
<prop key="support">support@example.co.uk</prop>
</props>
</property>
</bean>
<beans>
说明几点:
- 这是springbean的继承关系,并不是java的继承关系,即子bean对应的类没有extends父bean对应的类
- merge属性用在集合标签上
- parent和abstract属性是为了将bean的公共属性提出来共用
- abstract=true 并不代表该bean对应的类是抽象类
- 还有一句,这玩意有点难用(ノ`Д)ノ,还是建议直接在java类的层面解决公共属性复用的问题
2.3.2.5.5、空值和空字符串值
Spring将属性等的空参数视为空Strings。以下基于XML的配置元数据片段将email属性设置为空 String值(“”)。
<bean class="ExampleBean">
<property name="email" value=""/>
</bean>
上面的示例等效于以下Java代码:
exampleBean.setEmail("");
该< null/>元素处理null的值。例如:
<bean class="ExampleBean">
<property name="email">
<null/>
</property>
</bean>
以上配置等同于以下Java代码:
exampleBean.setEmail(null);
2.3.2.6、depends-on 表明依赖关系
depends-on是bean标签的属性之一,表示一个bean对其他bean的依赖关系。乍一想,不是有ref吗?其实还是有区别的,< ref/>标签是一个bean对其他bean的引用,而depends-on属性只是表明依赖关系(不一定会引用),这个依赖关系决定了被依赖的bean必定会在依赖bean之前被实例化,反过来,容器关闭时,依赖bean会在被依赖的bean之前被销毁。
<bean id="beanOne" class="ExampleBean" depends-on="manager"/>
<bean id="manager" class="ManagerBean" />
要表示对多个bean的依赖关系,请提供bean名称列表作为depends-on属性的值,使用逗号,空格和分号作为有效分隔符:
<bean id="beanOne" class="ExampleBean" depends-on="manager,accountDao">
<property name="manager" ref="manager" />
</bean>
<bean id="manager" class="ManagerBean" />
<bean id="accountDao" class="x.y.jdbc.JdbcAccountDao" />
depends-on可以确保指定的bean在自己之前被初始化,这个还是比较有用的需要注意下
2.3.2.7、bean的懒惰初始化
默认情况下,ApplicationContext实现会创建和配置所有 单例 bean作为初始化过程的一部分。通常,这种预先实例化是可取的,因为配置或周围环境中的错误是立即发现的,而不是几小时甚至几天后。如果不希望出现这种情况,可以通过将bean定义标记为延迟初始化来阻止单例bean的预实例化。延迟初始化的bean告诉IoC容器在第一次请求时创建bean实例,而不是在启动时。
在XML中,此行为由 元素lazy-init上的属性控制; 例如:
<bean id="lazy" class="com.foo.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.foo.AnotherBean"/>
当前面的配置被a使用时ApplicationContext,name为lazy的bean在ApplicationContext启动时不会预先实例化,而name为lazybean的bean会被预先实例化。
但是,当延迟初始化的bean是未进行延迟初始化的单例bean的依赖项时 ,ApplicationContext会在启动时创建延迟初始化的bean,因为它必须满足单例的依赖关系。惰性初始化的bean被注入到其他地方的单例bean中,而不是懒惰初始化的。
2.3.2.8、自动装配
Spring容器可以自动建立bean和协作bean之间的关系。可以通过配置让Spring自动为bean解析相关依赖(协作者)。自动装配具有以下优点:
- 自动装配可以显着减少指定属性或构造函数参数的需要。(想一下如果你的bean如果有10个或者20个或者30个依赖bean,一个配置文档下来得有多长。。)
- 自动装配可以随着对象的发展更新配置。例如,如果需要向类添加依赖项,则可以自动满足该依赖项,而无需修改配置。
使用基于XML的配置元数据时,可以使用元素的autowire属性为bean定义指定autowire模式< bean/>。自动装配功能有四种模式。
模式 | 解释 |
---|---|
no | (默认)无自动装配。必须通过ref元素定义Bean引用。不建议对较大的部署更改默认设置,因为明确指定协作者可以提供更好的控制和清晰度。在某种程度上,它记录了系统的结构。 |
byName | 按属性名称自动装配。Spring查找与需要自动装配的属性同名的bean。例如,如果bean定义按名称设置为autowire,并且它包含master属性(即,它具有 setMaster(…)方法),则Spring会查找名为的bean定义master,并使用它来设置属性。 |
byType | 如果容器中只存在一个属性类型的bean,则允许自动装配属性。如果存在多个,则抛出异常,这表示您不能对该bean 使用byType自动装配。如果没有匹配的bean,则没有任何反应; 该属性为设置。 |
constructor | 类似于byType,但适用于构造函数参数。如果容器中没有复合构造函数参数类型的一个bean,则会引发致命错误。 |
对象的自动装配
实体类
package com.my.domain;
import com.sun.org.apache.bcel.internal.generic.FADD;
/**
* @description:
* @author: lige
* @create: 2018-12-07
**/
public class Father {
private String name;
private Child child;
public Father(){
}
public Father(Child child){
this.child = child;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Child getChild() {
return child;
}
public void setChild(Child child) {
this.child = child;
}
}
package com.my.domain;
/**
* @description:
* @author: lige
* @create: 2018-12-07
**/
public class Child {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
byName
<beans>
<bean id="father" class="com.my.domain.Father" autowire="byName">
</bean>
<bean id="child" class="com.my.domain.Child">
<property name="name" value="wangwa"/>
</bean>
</beans>
byType
<beans>
<bean id="father" class="com.my.domain.Father" autowire="byType">
</bean>
<bean id="child" class="com.my.domain.Child">
<property name="name" value="wangwa"/>
</bean>
</beans>
constuctor
<beans>
<bean id="father" class="com.my.domain.Father" autowire="constructor">
</bean>
<bean id="child" class="com.my.domain.Child">
<property name="name" value="wangwa"/>
</bean>
</beans>
集合的自动装配
在使用byType或者constructor方式进行自动装配的时候,Spring会将容器中该type对应的所有的对象取出来,放进集合中。例如:下面的例子中,School中的students进行自动装配,Spring会把容器中注册的student1、student2、student3取出,注入到List< Student> students中。
<beans>
<bean id="school" class="com.my.domain.School" autowire="byType">
</bean>
<bean id="student1" name="student1" class="com.my.domain.Student">
<property name="name" value="xiaoming"/>
</bean>
<bean id="student2" name="student2" class="com.my.domain.Student">
<property name="name" value="xiaogang"/>
</bean>
<bean id="student3" name="student3" class="com.my.domain.Student">
<property name="name" value="xiaohong"/>
</bean>
</beans>
package com.my.domain;
import java.util.List;
/**
* @description:
* @author: lige
* @create: 2018-12-07
**/
public class School {
private List<Student> students;
public List<Student> getStudents() {
return students;
}
public void setStudents(List<Student> students) {
this.students = students;
}
}
自动装配的缺点
考虑自动装配的局限和不足之处:
- 显示依赖(如果我们明确通过property或者constructor-a配置的依赖将会覆盖自动装配,对于简单的所谓的属性例如Strings或Classes ,这种,我们是不能使用autowire的,这个是设计的时候决定的。)
- 自动装配相比较给出明确的配置来说,不够精确,有可能会发生模糊匹配而导致错误。
- 多个bean定义可以通过setter方法或构造函数的参数指定bean的类型匹配。然而,对于期望一个单一值的依赖关系,如果没有唯一的bean定义可用,则抛出一个异常。
在后一种情况下,你有几个选项:
- 使用显式依赖而不是自动装配
- 当一个bean不需要被自动装配的时候,我们要在定义这个bean的时候,将autowire-candidate 属性设置为false。
- 假设A1集成接口A,A2集成接口A,当我们在使用A1(A1的类型是A)的时候要为其注入A1,而不是A2。也就是在使用多个子类注入到父类的时候,要将某个子类放置到主要的位置,那么我们可以在bean标签中使用primary=true来处理。
当一个bean不需要被自动装配的时候,可以在bean标签中,将autowire-candidate 属性设置为false即可。
注意:如果bean A 的autowire-candidate设置为true的时候,不是说A不能进行自动装配,而是B无法通过自动装配的方式装配bean A
2.3.2.9、方法注入(做了解)
在大多数应用程序场景中,容器中的大多数bean都是 单例。当单例bean需要与另一个单例bean协作,或者非单例bean需要与另一个非单例bean协作时,通常通过将一个bean定义为另一个bean的属性来处理依赖关系。当bean生命周期不同时会出现问题。假设单例bean A需要使用非单例(原型)bean B,可能是在A上的每个方法调用上。容器只创建一次单例bean A,因此只有一次机会来设置属性。每次需要时,容器都不能为bean A提供bean B的新实例。
方法注入有两种方式:
- 实现接口ApplicationContextAware,使bean 了解容器ApplicationContextAware
- 使用lookup配置
2.3.3、基于XML
2.3.3.1、使用多个xml
可以使用应用程序上下文构造函数从所有这些XML片段加载bean定义。此构造函数采用多个Resource位置,如上面Context初始化代码中所示。或者,使用一个或多个元素来从另一个或多个文件加载bean定义。
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<import resource="classpath*:spring-app.xml"/>
<beans>
<bean id="order" class="com.my.domain.Order">
<property name="good" ref="good"/>
<property name="id" value="3333"/>
</bean>
</beans>
</beans>
上面的例子中,引入了spring-app.xml配置文件,并配置了order对good的依赖关系
2.3.4、基于注解
2.3.4.1、注解基础—注解处理器
在进行基于注解的配置学习时,我们需要了解一点,@aotowired、@Resource等注解运行的基础是BeanPostProcessor,如果我们想要使用注解,就必须先注册响应的BeanPostProcessor注解处理器。
为了避免每使用一个注解,就必须在xml中配置一个注解处理器,Spring提供了一些简便的配置。
context:annotation-config
<context:annotation-config/>
该隐式注册的后处理器包括 AutowiredAnnotationBeanPostProcessor, CommonAnnotationBeanPostProcessor, PersistenceAnnotationBeanPostProcessor,以及 RequiredAnnotationBeanPostProcessor。
context:component-scan
<context:component-scan base-package="xxx.xxx.xxx"/>
< context:component-scan/> 的作用是让Bean定义注解工作起来,也就是上述传统声明方式。 它的base-package属性指定了需要扫描的类包,类包及其递归子包中所有的类都会被处理。
< context:component-scan/>首先有和context:annotation-config一样的作用,除此之外,context:component-scan还支持@Component、@Service等一些Bean注册所需的注解。
推荐使用context:component-scan方式
2.3.4.2、装配型注解
2.3.4.2.1、@Required
@Required注解适用于bean属性setter方法,并表示受影响的bean属性必须在XML配置文件在配置时进行填充。否则,容器会抛出一个BeanInitializationException异常。
@Required注释适用于bean属性setter方法,例如下面这样:
spring配置
<?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:context="http://www.springframework.org/schema/context"
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">
<context:component-scan base-package="com.my.domain"/>
<beans>
<bean id="car" class="com.my.domain.Car">
<property name="name" value="BENCE"/>
<!--<property name="id" value="11233"/>-->
</bean>
</beans>
</beans>
Car.java
package com.my.domain;
import org.springframework.beans.factory.annotation.Required;
/**
* @description:
* @author: lige
* @create: 2018-12-09
**/
public class Car {
private String id;
private String name;
public String getId() {
return id;
}
@Required
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
TEST
@Test
public void test1(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-app.xml");
Car car = context.getBean("car",Car.class);
System.out.println(car);
context.registerShutdownHook();
}
result:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'car' defined in class path resource [spring-app.xml]: Initialization of bean failed; nested exception is org.springframework.beans.factory.BeanInitializationException: Property 'id' is required for bean 'car'
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:591)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:502)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:312)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:228)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:310)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:756)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:868)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:549)
at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:144)
at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:85)
at com.my.AppTest.test1(AppTest.java:30)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
at org.junit.runner.JUnitCore.run(JUnitCore.java:160)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Caused by: org.springframework.beans.factory.BeanInitializationException: Property 'id' is required for bean 'car'
at org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor.postProcessPropertyValues(RequiredAnnotationBeanPostProcessor.java:156)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1340)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:582)
... 33 more
如果car的id属性没有注入的话,容器将报错。
2.3.4.2.2、@Autowired 和@Inject
@Autowired 用来注入依赖,JSR330 的@Inject 注解可以用于代替 Spring 的@Autowired。
@Autowired 有三种注入方式:
- 通过构造器注入
- 通过 setter 方法注入
- 通过 field 反射注入
例子:
Phone.java
package com.my.domain;
import org.springframework.beans.factory.annotation.Autowired;
/**
* @description:
* @author: lige
* @create: 2018-12-09
**/
public class Phone {
//@Autowired
private Headset headset;
private String brandName;
public Phone(){
}
@Autowired
public Phone(Headset headset){
this.headset = headset;
}
public Headset getHeadset() {
return headset;
}
//@Autowired
public void setHeadset(Headset headset) {
this.headset = headset;
}
public String getBrandName() {
return brandName;
}
public void setBrandName(String brandName) {
this.brandName = brandName;
}
}
Headset.java
package com.my.domain;
/**
* @description: 耳机
* @author: lige
* @create: 2018-12-09
**/
public class Headset {
}
注入集合
在集合上使用@Autowired,会复合的type类型的bean,注入
例如:
//会将容器中所有Car类型的bean全部注入
@Autowired
private List<Car> cars;
类似的Map、Set也沿用这种方式
注入Spring容器的相关接口
还可以将@Autowired用于众所周知的可解析依赖项的接口:BeanFactory,ApplicationContext,Environment,ResourceLoader,ApplicationEventPublisher和MessageSource。 这些接口及其扩展接口(如ConfigurableApplicationContext或ResourcePatternResolver)将自动解析,无需特殊设置。
public class MovieRecommender {
@Autowired
private ApplicationContext context;
public MovieRecommender() {
}
// ...
}
2.3.4.2.3、使用限定符来微调基于注解的自动装配
如果容器中有同一个type有多个bean存在,可以使用@Qualifier指定注入的bean的name
@Autowired
@Qualifier("car1")
private Car car;
2.3.4.2.4、@Resource
Spring还支持在字段或bean属性setter方法上使用JSR-250 @Resource注解进行注入,@Resource可以标注在字段或属性的setter方法上
@Resource采用name属性,默认情况下Spring将该值解释为要注入的bean名称。 换句话说,它遵循按名称语义,如本例所示:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Resource(name="myMovieFinder")
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
如果未明确指定名称,则默认名称是从字段名称或setter方法派生的。如果是字段,则采用字段名称(movieFinder1); 在setter方法的情况下,它采用bean属性名称(movieFinder2)。所以下面的例子将把名为“movieFinder”的bean注入其setter方法:
public class SimpleMovieLister {
private MovieFinder movieFinder1;
@Resource
public void setMovieFinder(MovieFinder movieFinder2) {
this.movieFinder1 = movieFinder2;
}
}
如果@Resource没有限定bean名称
- 若注解标注在字段上且未指定name属性,则默认取字段名作为bean名称寻找依赖对象
- 若注解标注在setter上且未指定name属性,则默认取属性名作为bean名称寻找依赖对象
- 如果没有指定name属性,并且按照默认的名称仍找不到依赖对象时,它就会按类型匹配
但只要指定了name属性,就只能按名称装配了
@Autowired or @Resource ?
最好选择@Resource,@Resource是java6就提供了的注解,使用@Resource可以减少对spring的耦合。
2.3.4.2.5、@PostConstruct和@PreDestroy
CommonAnnotationBeanPostProcessor不仅识别@Resource注释,还识别JSR-250生命周期注释。 在Spring 2.5中引入,对这些注释的支持提供了初始化回调和销毁回调中描述的另一种替代方法。
/**
* @description: 商品
* @author: lige
* @create: 2018-11-05
**/
@Component
public class Good implements InitializingBean , DisposableBean , BeanNameAware {
private String name;
private float price;
private String beanName;
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("afterPropertiesSet方法 调用");
}
@Override
public void destroy() throws Exception {
System.out.println("destroy方法 调用");
}
@PostConstruct
public void postConstruct(){
System.out.println("PostConstruct方法 调用");
}
@PreDestroy
public void preDestory(){
System.out.println("preDestory方法 调用");
}
public void init() {
System.out.println("init方法 调用");
}
public void myDestoryMethod(){
System.out.println("myDestory方法 调用");
}
public Good() {
System.out.println("constructor方法 调用");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
System.out.println("setName 方法调用");
}
public float getPrice() {
return price;
}
public void setPrice(float price) {
this.price = price;
}
@Override
public String toString() {
return MessageFormat.format("商品名为{0},商品价格为{1}", name, price);
}
@Override
public void setBeanName(String s) {
this.beanName = s;
}
public String getBeanName() {
return beanName;
}
}
2.3.4.3、组件型注解
2.3.4.3.1、@Component和进一步的构造型注释(@Repository、@Service、@Controller)
@Component可以注册一个bean,name默认为类名首字母小写的驼峰格式。
@Component
public class Car{
}
在Spring2.5版本中,引入了更多的Spring类注解:@Component,@Service,@Controller。@Component是一个通用的Spring容器管理的单例bean组件。而@Repository, @Service, @Controller就是针对不同的使用场景所采取的特定功能化的注解组件。
- @Service 在业务逻辑层使用(service层)
- @Repository 在数据访问层使用(dao层)
- @Controller 在展现层使用,控制器的声明(C)
@Service, @Controller , @Repository = {@Component + 一些特定的功能}。这个就意味着这些注解在部分功能上是一样的。
@Scope
@Scope可以设置bean的范围
@Component
@Scope("prototype")
public class Car {
private String id;
private String name;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
2.3.4.4、@PropertySource
@PropertySource 注解提供便利和声明的机制添加PropertySource 到Spring的Environment。
给定包含键/值对的文件“app.properties” (文件内容为键值对,例如testbean.name=myTestBean),以下@Configuration类@PropertySource以这样的方式使用,即调用testBean.getName()将返回“myTestBean”。
@Configuration
@PropertySource("classpath:/com/myco/app.properties")
public class AppConfig {
@Autowired
Environment env;
@Bean
public TestBean testBean() {
TestBean testBean = new TestBean();
testBean.setName(env.getProperty("testbean.name"));
return testBean;
}
}
2.3.5、基于java的配置
待完善
2.3.6、环境抽象
Environment是一个集成到容器之中的特殊抽象,它针对应用的环境建立了两个关键的概念:profile和properties.
profile是命名好的,其中包含了多个Bean的定义的一个逻辑集合,只有当指定的profile被激活的时候,其中的Bean才会激活。无论是通过XML定义的还是通过注解解析的Bean都可以配置到profile之中。而Environment对象的角色就是跟profile相关联,然后决定来激活哪一个profile,还有哪一个profile为默认的profile。
properties在几乎所有的应用当中都有着重要的作用,当然也可能导致多个数据源:property文件,JVM系统property,系统环境变量,JNDI,servlet上下文参数,ad-hoc属性对象,Map等。Environment对象和property相关联,然后来给开发者一个方便的服务接口来配置这些数据源,并正确解析。
2.3.6.1、配置和激活profile
配置profile
在XML中相对应配置是< beans/>中的profile属性
<beans profile="production"
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="...">
<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
</beans>
激活profile
有多种方式激活配置,但是最直接的方式是编程式的方式使用ApplicationContext API。
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("dev");
ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class);
ctx.refresh();
除了上面的方式还可以通过spring.profiles.active参数的设置:
- 作为SpringMVC中的DispatcherServlet的初始化参数
- 作为Web 应用上下文中的初始化参数
- 作为JNDI的入口
- 作为环境变量
- 作为虚拟机的系统参数
- 使用@AtivceProfile来进行激活
举例(web.xml方式)
<init-param>
<param-name>spring.profiles.active</param-name>
<param-value>production</param-value>
</init-param>
配置默认profile
使用spring.profiles.default参数可以设置默认的profile,如果设置了spring.profiles.active属性的话,那么它的值就会用来确定哪个profile是激活的。但如果没有设置spring.profiles.active属性的话,那Spring将会查找spring.profiles.default的值。如果spring.profiles.active和spring.profiles.default均没有设置的话,那就没有激活的profile,因此只会创建那些没有定义在profile中的bean。
具体配置方式同spring.profiles.active。
2.3.6.2、PropertySource抽象
对于各种基于"名称/值"对(key/value pair)的属性源,Spring将其抽象成了抽象泛型类PropertySource。底层的属性源T可以是容纳属性信息的任意类型,比如java.util.Properties,java.util.Map,ServletContext,ServletConfig对象,或者是命令行参数CommandLineArgs对象。类PropertySource的方法getSource()用于获取底层的属性源对象T。顶层的属性源对象经过PropertySource封装,从而具有统一的访问方式。
Spring的Environment抽象提供了对可配置的属性源层次结构的搜索操作
ApplicationContext ctx = new GenericApplicationContext();
Environment env = ctx.getEnvironment();
boolean containsFoo = env.containsProperty("foo");
System.out.println("Does my environment contain the 'foo' property? " + containsFoo);
注意:执行的搜索是分层的。默认情况下,系统属性优先于环境变量,因此如果foo在调用期间恰好在两个位置都设置了属性env.getProperty(“foo”),则系统属性值将“获胜”并优先于环境变量返回。请注意,属性值不会被合并,而是被前面的条目完全覆盖。
对于公共StandardServletEnvironment层次结构,完整层次结构如下所示,最高优先级条目位于顶部:
- ServletConfig参数(如果适用,例如在DispatcherServlet上下文的情况下)
- ServletContext参数(web.xml context-param条目)
- JNDI环境变量(“java:comp / env /”条目)
- JVM系统属性(“-D”命令行参数)
- JVM系统环境(操作系统环境变量)
占位符
PropertyPlaceholderConfigurer是个bean工厂后置处理器的实现,也就是 BeanFactoryPostProcessor接口的一个实现。PropertyPlaceholderConfigurer可以将上下文(配置文 件)中的属性值放在另一个单独的标准java Properties文件中去。在XML文件中用${key}替换指定的properties文件中的值。这样的话,只需要对properties文件进 行修改,而不用对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"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="properties/db.properties"></property>
</bean>
<bean id="dataSource" class="org.xrq.spring.action.properties.DataSource">
<property name="driveClass" value="${driveClass}" />
<property name="url" value="${url}" />
<property name="userName" value="${userName}" />
<property name="password" value="${password}" />
</bean>
</beans>
或者使用简化配置:
<context:property-placeholder location="properties/db.properties"/>
PropertyPlaceholderConfigurer如果在指定的Properties文件中找不到你想使用的属性,它还会在Java的System类属性中查找。
我们可以通过System.setProperty(key, value)或者java中通过-Dnamevalue来给Spring配置文件传递参数。