1 Spring框架的由来
Spring框架的本质:提供各种服务,以帮助我们简化基于POJO的Java应用程序开发。
各种服务实现被划分到了多个相互独立却又相互依赖的模块当中:
Core核心模块:IoC容器、Framework工具类。
AOP模块:Spring AOP。
DAO模块:Spring JDBC、事务管理。
ORM集成模块。
Java EE服务集成模块。
Web模块:Spring MVC。
2 IoC的基本概念
IoC:Inversion of Control,控制反转。
DI:Dependency Injection,依赖注入。
IoC的理念就是,让别人为你服务。原来是需要什么东西自己去拿,现在是需要什么东西就让别人送过来。
用一句话来概括IoC可以带给我们什么,那就是,IoC是一种可以帮助我们解耦各业务对象间依赖关系的对象绑定方式。
三种依赖注入方式:构造方法注入、setter方法注入、接口注入(已淘汰,原因是侵入性较强)。
3 掌管大局的IoC Service Provider
IoC Service Provider:控制反转服务供应者。
IoC Service Provider是一个抽象出来的概念,它可以指代任何将IoC场景中的业务对象绑定到一起的实现方式,Spring的IoC容器就是一个提供依赖注入服务的IoC Service Provider。
IoC Service Provider管理对象间依赖关系的方式:
1) 直接编码方式。
IoContainer container = ...;
container.register(FXNewsProvider.class, new FXNewsProvider());
container.register(IFXNewsListener.class, new DowJonesNewsListener());
...
FXNewsProvider newsProvider = (FXNewsProvider) container.get(FXNewsProvider.class);
newProvider.getAndPersistNews();
2) 配置文件方式。
配置:
<bean id="newsProvider" class="..FXNewsProvider">
<property name="newsListener">
<ref bean="djNewsListener"/>
</property>
<property name="newPersistener">
<ref bean="diNewsPersister"/>
</property>
</bean>
<bean id="djNewsListener"
class="..impl.DowJonesNewsListener">
</bean>
<bean id="diNewsPersister"
class="..impl.DowJonesNewsPersister">
</bean>
读取配置:
...
container.readConfigurationFiles(...);
FXNewsProvider newsProvider = (FXNewsProvider) container.getBean("newsProvider");
newsProvider.getAndPersistNews();
4 Spring的IoC容器之BeanFactory
4.1 Spring的IoC容器
Spring提供了两种容器类型:BeanFactory和ApplicationContext。
1) BeanFactory:基础类型IoC容器,默认采用延迟初始化策略(lazy-load),只有当客户端对象需要访问容器中的某个受管对象时,才对该受管对象进行初始化以及依赖注入操作。适合资源有限、功能要求不是很严格的场景。
2) ApplicationContext:在BeanFactory的基础上构建,除了拥有BeanFactory的所有支持,还提供事件发布、国际化信息支持等高级特性。ApplicationContext管理的对象,在该类型容器启动之后,默认全部初始化并绑定完成。适合系统资源充足,并且要求更多功能的场景。
尚硅谷视频教程中提到:
BeanFactory更多是作为Spring内部使用的接口,我们开发人员一般无脑选ApplicationContext就可以了。
4.2 BeanFactory依赖注入
BeanFactory:
public interface BeanFactory {
String FACTORY_BEAN_PREFIX = "&";
Object getBean(String name) throws BeansException;
Object getBean(String name, Class requiredType) throws BeansException;
Object getBean(String name, Object[] args) throws BeansException;
boolean containsBean(String name);
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String name, Class targetType) throws NoSuchBeanDefinitionException;
Class getType(String name) throws NoSuchBeanDefinitionException;
String[] getAliases(String name);
}
直接编码方式
通过直接编码方式使用BeanFactory实现FX新闻相关类的注册及绑定:
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
public class Test {
public static void main(String[] args) {
DefaultListableBeanFactory beanRegistry = new DefaultListableBeanFactory();
BeanFactory container = (BeanFactory) bindViaCode(beanRegistry);
FXNewsProvider newsProvider = (FXNewsProvider) container.getBean("djNewsProvider");
newsProvider.getAndPersistNews();
}
public static BeanFactory bindViaCode(BeanDefinitionRegistry registry) {
AbstractBeanDefinition newsProvider = new RootBeanDefinition(FXNewsProvider.class);
AbstractBeanDefinition newsListener = new RootBeanDefinition(DowJonesNewsListener.class);
AbstractBeanDefinition newsPersister = new RootBeanDefinition(DowJonesNewsPersister.class);
// 将bean定义注册到容器中
registry.registerBeanDefinition("djNewsProvider", newsProvider);
registry.registerBeanDefinition("djListener", newsListener);
registry.registerBeanDefinition("djPersister", newsPersister);
// 指定依赖关系
// 1.构造方法注入
// ConstructorArgumentValues argValues = new ConstructorArgumentValues();
// argValues.addIndexedArgumentValue(0, newsListener);
// argValues.addIndexedArgumentValue(1, newsPersister);
// newsProvider.setConstructorArgumentValues(argValues);
// 2.setter方法注入
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.addPropertyValue(new PropertyValue("newsListener", newsListener));
propertyValues.addPropertyValue(new PropertyValue("newsPersister", newsPersister));
newsPersister.setPropertyValues(propertyValues);
// 绑定完成
return (BeanFactory) registry;
}
}
class FXNewsProvider {
private IFXNewsListener newsListener;
private IFXNewsPersister newsPersister;
public FXNewsProvider(DowJonesNewsListener newsListener, DowJonesNewsPersister newsPersister) {
this.newsListener = newsListener;
this.newsPersister = newsPersister;
}
public void getAndPersistNews() {
System.out.println("hello world");
}
}
interface IFXNewsListener {
}
interface IFXNewsPersister {
}
class DowJonesNewsListener implements IFXNewsListener {
}
class DowJonesNewsPersister implements IFXNewsPersister {
}
BeanFactory只是一个接口,我们最终需要一个该接口的实现来进行实际的Bean的管理,DefaultListableBeanFactory就是这么一个比较通用的BeanFactory实现类。
DefaultListableBeanFactory除了间接地实现了BeanFactory接口,还实现了BeanDefinitionRegistry接口,该接口才是在BeanFactory的实现中担当Bean注册管理的角色。
配置文件方式
Spring的IoC容器支持两种配置文件格式:Properties文件格式和XML文件格式。
采用配置文件时,Spring的Ioc容器统一的处理方式是:根据不同的外部配置文件格式,给出相应的BeanDefinitionReader实现类,由BeanDefinitionReader的相应实现类负责将相应的配置文件内容读取并映射到BeanDefinition,然后将映射后的BeanDefinition注册到一个BeanDefinitionRegistry。
以最常用的XML配置格式为例:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="djNewsProvider" class="ioc.FXNewsProvider">
<constructor-arg index="0">
<ref bean="djNewsListener"/>
</constructor-arg>
<constructor-arg index="1">
<ref bean="djNewsPersister"/>
</constructor-arg>
</bean>
<bean id="djNewsListener" class="ioc.DowJonesNewsListener"/>
<bean id="djNewsPersister" class="ioc.DowJonesNewsPersister"/>
</beans>
加载XML配置文件的BeanFactory:
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;
public class Test {
public static void main(String[] args) {
DefaultListableBeanFactory beanRegistry = new DefaultListableBeanFactory();
BeanFactory container = (BeanFactory) bindViaCode(beanRegistry);
FXNewsProvider newsProvider = (FXNewsProvider) container.getBean("djNewsProvider");
newsProvider.getAndPersistNews();
}
public static BeanFactory bindViaCode(BeanDefinitionRegistry registry) {
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(registry);
reader.loadBeanDefinitions("classpath:./testXML.xml");
return (BeanFactory) registry;
// 或者直接
// return new XmlBeanFactory(new ClassPathResource("./testXML.xml"));
}
}
注解方式
主要是@Autowired 和@Component 两个注解的使用。
package ioc;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.stereotype.Component;
public class Test {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("./testXML.xml");
FXNewsProvider newsProvider = (FXNewsProvider) ctx.getBean("FXNewsProvider");
newsProvider.getAndPersistNews();
}
}
@Component
class FXNewsProvider {
@Autowired
private IFXNewsListener newsListener;
@Autowired
private IFXNewsPersister newsPersister;
public FXNewsProvider(DowJonesNewsListener newsListener, DowJonesNewsPersister newsPersister) {
this.newsListener = newsListener;
this.newsPersister = newsPersister;
}
public void getAndPersistNews() {
System.out.println("hello world");
}
}
interface IFXNewsListener {
}
interface IFXNewsPersister {
}
@Component
class DowJonesNewsListener implements IFXNewsListener {
}
@Component
class DowJonesNewsPersister implements IFXNewsPersister {
}
配置文件:
<?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"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<context:component-scan base-package="ioc"/>
</beans>
<context:component-scan/>会到指定的包(package)下面扫描标注有@Component 的类,如果找到,则将它们添加到容器进行管理,并根据它们所标注的@Autowired 为这些类注入符合条件的依赖对象。
4.3 BeanFacory的XML之旅
<bean>标签属性:
id、name、class、depends-on、autowire、dependency-check、lazy-init、parent、abstract、scope、init-method、destroy-method等。
依赖注入简写形式
构造方法注入:
<bean id="djNewsProvider" class="..FXNewsProvider">
<constructor-arg>
<ref bean="djNewsListener"/>
</constructor-arg>
<constructor-arg>
<ref bean="djNewsPersister"/>
</constructor-arg>
</bean>
<!-- 简写形式 -->
<bean id="djNewsProvider" class="..FXNewsProvider">
<constructor-arg ref="djNewsListener"/>
<constructor-arg ref="djNewsPersister"/>
</bean>
setter方法注入:
<bean id="djNewsProvider" class="..FXNewsProvider">
<property name="newsListener">
<ref bean="djNewsListener"/>
</property>
<property name="newsPersistener">
<ref bean="djNewsPersister"/>
</property>
</bean>
<!-- 简写形式 -->
<bean id="djNewsProvider" class="..FXNewsProvider">
<property name="newsListener" ref="djNewsListener"/>
<property name="newsPersistener" ref="djNewsPersister"/>
</bean>
自动绑定
见6.1节 自动绑定。
scope:作用域
配置中的bean定义可以看作是一个模板,容器会根据这个模板来构造对象,但是要构造多少对象实例、该让构造的对象实例存活多久,则由bean定义的scope(作用域)来决定。
Spring容器最初提供了两种bean的scope类型:singleton和prototype。后来又引入了另外三种scope类型:request、session和global session类型,不过这三种类型只能在支持Web应用的ApplicationContext中使用。
1) singleton:在Spring的IoC容器中只存在一个实例,该实例从被请求而初始化之后,将一直存活到容器销毁或退出。singleton是默认的scope类型。
2) prototype:每次都重新生成一个新的对象实例给请求方,并且对象实例返回给请求方之后,容器就不再拥有当前返回对象的引用。
3) request:Spring容器会为每个HTTP请求创建一个全新的Request-Processor对象供当前请求使用,当请求结束后,该对象实例的生命周期即告结束。
4) session:Spring容器会为每个独立的session创建全新的UserPreferences对象实例。
5) global-session:只有应用在基于portlet的Web应用程序中才有意义,它映射到portlet的global范围的session。否则等价于session类型的scope。
工厂模式
有时,我们需要依赖第三方库,需要实例化并使用第三方库中的类,这时,接口与实现类的耦合性可以使用工厂方法模式来避免。
提供一个工厂类来实例化具体的接口实现类,这样,主体对象只需要依赖工厂类,具体使用的实现类有变更的话,只是变更工厂类,而主体对象不需要做任何变动。
IoC容器的工作原理
IoC容器的作用:以某种方式加载配置信息(通常是XML),然后根据这些信息绑定整个系统的对象,最终组装成一个可用的基于轻量级容器的应用系统。
实现以上功能的流程分为两个阶段:容器启动阶段和bean实例化阶段。
1) 容器启动阶段。
依赖某些工具类(BeanDefinitionReader)对加载的Configuration Metadata进行解析和分析。
将分析后的信息编组为BeanDefinition。
把BeanDefiniton注册到BeanDefinitionRegistry。
2) bean实例化阶段。
检查请求方请求的对象是否已经初始化。如果没有,实例化被请求对象,并为其注入依赖。
对象装配完毕后,容器将其返回请求方使用。
尚硅谷视频教程版本的IoC容器工作原理:
XML解析+工厂模式+反射。
读取XML文件,获取bean定义,使用反射技术获取对应的类信息、创建instance,使用工厂类将instance返回请求方。
bean的生命周期
当(请求方或者容器自己)通过BeanFactory的getBean方法请求某个对象实例时,可能会触发bean实例化阶段的活动。当getBean方法发现该bean定义需要被初始化时,会通过createBean方法来进行具体的对象实例化。
1) 实例化bean对象。
容器在内部实现的时候,采用策略模式(Strategy Pattern)来决定以何种方式初始化bean实例。通常可以通过反射或者CGLIB动态字节码生成来初始化相应的bean或动态生成其子类,默认采用后者。
构造完成的对象实例并不会被直接返回,而是以BeanWrapper对构造完成的对象实例进行包裹(Wrapper Pattern),返回相应的BeanWrapper实例。BeanWrapper继承了PropertyAccessor接口,可以以统一的方式对对象属性进行访问。如果没有BeanWrapper对bean实例进行包装,也可以通过Java反射API操作对象实例,这个过程会比较繁琐。
2) 设置对象属性。
Object provider = Class.forName("package.name.FXNewsProvider").newInstance();
Object listener = Class.forName("package.name.DowJonesNewsListener").newInstance();
Object persistener = Class.forName("package.name.DowJonesNewsPersistener").newInstance();
BeanWrapper newsProvider = new BeanWrapperImpl(provider);
newsProvider.setPropertyValue("newsListener", listener);
newsProvider.setPropertyValue("newsPersistener", persistener);
3) 检查Aware相关接口并设置相关依赖。
检查当前对象实例是否实现了一系列以Aware命名结尾的接口,如果是,则将这些Aware接口定义中规定的依赖注入给当前对象实例。
4) BeanPostProcessor前置处理。
执行BeanPostProcessor.postProcessBeforeInitialization方法。可以自定义实现。
5) 检查是否是InitializingBean以决定是否调用afterPropertiesSet方法。
指定自定义的对象初始化操作。
6) 检查是否配置有自定义的init-method。
指定自定义的对象初始化操作。
可以认为在InitializingBean和init-method中任选其一就可以完成初始化工作。相对来说采用init-method的方式更灵活,并且没有那么强的侵入性。
7) BeanPostProcessor后置处理。
执行BeanPostProcessor.postProcessAfterInitialization方法。可以自定义实现。
8) 注册必要的Destruction相关回调接口。
9) 使用bean。
10) 检查是否实现了DisposableBean接口。
指定自定义的对象销毁操作。
11) 检查是否配置有自定义的destory-method。
指定自定义的对象销毁操作。
尚硅谷视频教程的简化版本:
1) 通过构造器创建bean实例。
2) 为bean的属性设值、设置对其他bean的引用。
3) 把bean实例传递给bean后置处理器的postProcessBeforeInitialization方法。
4) 调用bean的初始化方法。
5) 把bean实例传递给bean后置处理器的postProcessAfterInitialization方法。
6) 使用bean。
7) 关闭容器时,调用bean的销毁方法。
import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Test { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("testXML.xml"); Orders orders = context.getBean("orders", Orders.class); System.out.println("第四步 使用bean对象实例"); System.out.println(orders); ((ClassPathXmlApplicationContext) context).close(); } } class Orders { private String oname; public Orders() { System.out.println("第一步 执行无参构造器创建bean实例"); } public void setOname(String oname) { System.out.println("第二步 调用set方法设置属性值"); this.oname = oname; } public void initMethod() { System.out.println("第三步 执行初始化方法"); } public void destroyMethod() { System.out.println("第五步 执行销毁方法"); } } class MyBeanPost implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println("执行初始化方法之前执行的方法"); return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println("执行初始化方法之后执行的方法"); return 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-2.5.xsd"> <bean id="orders" class="...Orders" init-method="initMethod" destroy-method="destroyMethod"> <property name="oname" value="手机"></property> </bean> <!-- 限ApplicationContext容器, 注册到BeanFactory容器的方法请翻书 --> <bean id="myBeanPost" class="...MyBeanPost"></bean> </beans>
5 Spring IoC容器 ApplicationContext
ApplicationContext除了拥有BeanFactory支持的所有功能外,还提供了:
统一资源加载策略。
国际化信息支持。
容器内事件发布功能。
5.1 统一资源加载策略
为什么需要统一资源加载策略?
理想情况下,资源查找完成后,返回给客户端的应该是一个统一的资源抽象接口,客户端要对资源进行什么样的处理,应该由资源抽象接口来界定,而不应该成为资源的定位者和查找者要关心的事情。在此前提下,Spring提出了一套基于org.springframework.core.io.Resource和org.springframework.core.io.ResourceLoader接口的资源抽象和加载策略。
Resource
Resource:翻译为资源。资源这个词的范围比较广义,资源可以以任何形式存在,如以二进制对象形式存在、以字节流形式存在、以文件形式存在等;而且,资源也可以存在于任何场所,如存在于文件系统、存在于Java应用的classpath中,甚至存在于URL可以定位的地方。
org.springframework.core.io.Resource接口:
public interface Resource extends InputStreamSource {
boolean exists();
boolean isOpen();
URL getURL() throws IOException;
File getFile() throws IOException;
Resource createRelative(String relativePath) throws IOException;
String getFilename();
String getDescription();
}
Spring提供的Resource接口的实现类有:
ByteArrayResource、ClassPathResource、FileSystemResource、UrlResource、InputStreamResource。
ResourceLoader
ResourceLoader:资源加载器。负责资源的查找和定位。
org.springframework.core.io.ResourceLoader接口:
public interface ResourceLoader {
String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;
Resource getResource(String location);
ClassLoader getClassLoader();
}
Spring提供的ResourceLoader接口的实现类有:
DefaultResourceLoader、FileSystemResourceLoader。
DefaultResourceLoader定位资源的逻辑:
1) 首先检查资源路径是否以classpath: 前缀打头,如果是,尝试构造ClassPathResource类型的资源并返回。
2) 否则,尝试通过URL来定位资源,构造UrlResource类型的资源并返回。
3) 如果无法根据URL定位资源,则委派getResourceByPath方法来定位,该方法的默认实现逻辑是,构造ClassPathResource类型的资源并返回。
FileSystemResourceLoader定位资源的逻辑:
FileSystemResourceLoader继承自DefaultResourceLoader,重写了getResourceByPath方法,使之从文件系统加载资源并以FileSystemResource类型返回。
代码示例:
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.FileSystemResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.UrlResource;
public class Test {
public static void main(String[] args) {
// 使用DefaultResourceLoader
ResourceLoader resourceLoader = new DefaultResourceLoader();
Resource classpathResource = resourceLoader.getResource("classpath:test.txt");
assertTrue(classpathResource instanceof ClassPathResource);
assertTrue(classpathResource.exists());
Resource urlResource1 = resourceLoader.getResource("file:D:/test.txt");
assertTrue(urlResource1 instanceof UrlResource);
assertTrue(urlResource1.exists());
Resource urlResource2 = resourceLoader.getResource("http://www.spring21.cn");
assertTrue(urlResource2 instanceof UrlResource);
assertFalse(urlResource2.exists());
Resource fakeFileResource = resourceLoader.getResource("D:test.txt");
assertTrue(fakeFileResource instanceof ClassPathResource);
assertFalse(fakeFileResource.exists());
// 使用FileSystemResourceLoader
ResourceLoader fileResourceLoader = new FileSystemResourceLoader();
Resource fileResource = fileResourceLoader.getResource("D:/test.txt");
assertTrue(fileResource instanceof FileSystemResource);
assertTrue(fileResource.exists());
}
}
ResourcePatternResolver
ResourcePatternResolver——批量查找的ResourceLoader。
public interface ResourcePatternResolver extends ResourceLoader {
String CLASSPATH_ALL_URL_PREFIX = "classpath*:";
Resource[] getResources(String locationPattern) throws IOException;
}
ResourcePatternResolver最常用的一个实现是PathMatchingResourcePatternResource。
Application对统一资源加载策略的支持
Application直接或间接实现了ResourceLoader或ResourcePatternResolver接口,所以可以加载任何Spring支持的Resource类型。
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.UrlResource;
public class Test {
public static void main(String[] args) {
ResourceLoader resourceLoader = new ClassPathXmlApplicationContext("配置文件路径");
// 或者 ResourceLoader resourceLoader = new FileSystemXmlApplicationContext("配置文件路径");
Resource fileResource = resourceLoader.getResource("D:/test.txt");
assertTrue(fileResource instanceof ClassPathResource);
assertFalse(fileResource.exists());
Resource urlResource = resourceLoader.getResource("http://www.spring21.cn");
assertTrue(urlResource instanceof UrlResource);
}
}
ClassPathXmlApplicationContext和FileSystemXmlApplicationContext的区别在于,当资源无前缀时,ClassPathXmlApplicationContext默认从classpath加载资源,FileSystemXmlApplicationContext默认从文件系统加载资源。
ResourceLoader类型注入和Resource类型注入书上都有讲解,如果用到的话请翻书。
5.2 国际化信息支持
ApplicationContext实现了Spring提供的国际化信息的访问接口org.springframework.context.MessageSource。
5.3 容器内部事件发布
自定义事件发布
Java SE提供了实现自定义事件发布功能的基础类:java.util.EventObject类和java.util.EventListener接口。所有的自定义事件类型可以通过扩展EventObject来实现,事件监听器可以通过扩展EventListener来实现。
1) 自定义事件类型。
2) 实现针对自定义事件类的事件监听器接口。
3) 组合事件类和监听器,发布事件。
import java.util.ArrayList;
import java.util.EventListener;
import java.util.EventObject;
import java.util.List;
public class Test {
public static void main(String[] args) {
MethodExecutionEventPublisher eventPublisher = new MethodExecutionEventPublisher();
eventPublisher.addMethodExecutionEventListener(new SimpleMethodExecutionEventListener());
eventPublisher.methodToMonitor();
}
}
/**
* 自定义事件类型
*/
class MethodExecutionEvent extends EventObject {
private static final long serialVersionUID = -71960369269303337L;
private String methodName;
public MethodExecutionEvent(Object source) {
super(source);
}
public MethodExecutionEvent(Object source, String methodName) {
super(source);
this.methodName = methodName;
}
public String getMethodName() {
return methodName;
}
public void setMethodName(String methodName) {
this.methodName = methodName;
}
}
/**
* 自定义事件监听器接口
*/
interface MethodExecutionEventListener extends EventListener {
void onMethodBegin(MethodExecutionEvent evt);
void onMethodEnd(MethodExecutionEvent evt);
}
/**
* 自定义事件监听器实现类
*/
class SimpleMethodExecutionEventListener implements MethodExecutionEventListener {
@Override
public void onMethodBegin(MethodExecutionEvent evt) {
String methodName = evt.getMethodName();
System.out.println("start to execute the method[" + methodName + "].");
}
@Override
public void onMethodEnd(MethodExecutionEvent evt) {
String methodName = evt.getMethodName();
System.out.println("finish executing the method[" + methodName + "].");
}
}
/**
* 方法执行状态枚举
*/
enum MethodExecutionStatusEnum {
BEGIN, END
}
/**
* 自定义事件发布器
*/
class MethodExecutionEventPublisher {
private List<MethodExecutionEventListener> listenerList = new ArrayList<>();
/**
* 监控方法
*/
public void methodToMonitor() {
MethodExecutionEvent event2Publish = new MethodExecutionEvent(this, "methodToMonitor");
publishEvent(event2Publish, MethodExecutionStatusEnum.BEGIN);
System.out.println("work");
publishEvent(event2Publish, MethodExecutionStatusEnum.END);
}
protected void publishEvent(MethodExecutionEvent methodExecutionEvent, MethodExecutionStatusEnum status) {
// 安全复制
List<MethodExecutionEventListener> copyListenerList = new ArrayList<>(listenerList);
for (MethodExecutionEventListener listener : copyListenerList) {
if (MethodExecutionStatusEnum.BEGIN.equals(status)) {
listener.onMethodBegin(methodExecutionEvent);
} else {
listener.onMethodEnd(methodExecutionEvent);
}
}
}
public void addMethodExecutionEventListener(MethodExecutionEventListener listener) {
this.listenerList.add(listener);
}
// 如果不提供remove方法, 注册的监听器实例会一直被引用, 导致内存泄漏
public void removeListener(MethodExecutionEventListener listener) {
if (this.listenerList.contains(listener)) {
this.listenerList.remove(listener);
}
}
public void removeAllListener() {
this.listenerList.clear();
}
}
ApplicationContext对事件发布的支持
ApplicationContext容器内部允许以org.springframework.context.ApplicationEvent的形式发布事件,容器内注册的org.springframework.context.ApplicationListener类型的bean定义会被ApplicationContext容器自动识别,它们负责监听容器内发布的所有ApplicationEvent类型的事件。也就是说,一旦容器内发布ApplicationEvent及其子类型的事件,注册到容器的ApplicationListener就会对这些事件进行处理。
ApplicationContext继承了ApplicationEventPublisher接口,担当了事件发布者的角色。
ApplicationContext把实现事件发布和事件监听器注册的工作转包给了org.springframework.context.event.ApplicationEventMulticaster接口。
容器启动伊始,就会检查容器内是否存在名称为applicationEventMulticaster的ApplicationEventMulticaster实例,有的话就使用提供的实现,没有的话就初始化一个SimpleApplicationEventMulticaster,完成事件发布功能。
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.context.ApplicationListener;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
ApplicationContext container = new ClassPathXmlApplicationContext("classpath:./testXML.xml");
MethodExecutionEventPublisher eventPublisher = (MethodExecutionEventPublisher) container.getBean("evtPublisher");
eventPublisher.methodToMonitor();
}
}
/**
* 自定义ApplicationEvent类型的事件
*/
class MethodExecutionEvent extends ApplicationEvent {
private static final long serialVersionUID = -71960369269303337L;
private String methodName;
private MethodExecutionStatusEnum methodExecutionStatus;
public MethodExecutionEvent(Object source) {
super(source);
}
public MethodExecutionEvent(Object source, String methodName, MethodExecutionStatusEnum methodExecutionStatus) {
super(source);
this.methodName = methodName;
this.methodExecutionStatus = methodExecutionStatus;
}
public String getMethodName() {
return methodName;
}
public void setMethodName(String methodName) {
this.methodName = methodName;
}
public MethodExecutionStatusEnum getMethodExecutionStatus() {
return methodExecutionStatus;
}
public void setMethodExecutionStatus(MethodExecutionStatusEnum methodExecutionStatus) {
this.methodExecutionStatus = methodExecutionStatus;
}
}
/**
* 方法执行状态枚举
*/
enum MethodExecutionStatusEnum {
BEGIN, END
}
/**
* 自定义ApplicationListener类型的监听器
*/
class MethodExecutionEventListener implements ApplicationListener {
@Override
public void onApplicationEvent(ApplicationEvent evt) {
if (evt instanceof MethodExecutionEvent) {
String methodName = ((MethodExecutionEvent) evt).getMethodName();
MethodExecutionStatusEnum status = ((MethodExecutionEvent) evt).getMethodExecutionStatus();
if (MethodExecutionStatusEnum.BEGIN.equals(status)) {
System.out.println("start to execute the method[" + methodName + "].");
} else {
System.out.println("finish executing the method[" + methodName + "].");
}
}
}
}
/**
* 自定义事件发布器
*/
class MethodExecutionEventPublisher implements ApplicationEventPublisherAware {
private ApplicationEventPublisher eventPublisher;
public void methodToMonitor() {
MethodExecutionEvent beginEvt = new MethodExecutionEvent(this, "methodToMonitor", MethodExecutionStatusEnum.BEGIN);
this.eventPublisher.publishEvent(beginEvt);
System.out.println("work");
MethodExecutionEvent endEvt = new MethodExecutionEvent(this, "methodToMonitor", MethodExecutionStatusEnum.END);
this.eventPublisher.publishEvent(endEvt);
}
// 由于实现了ApplicationEventPublisherAware接口, 自动调用该方法
public void setApplicationEventPublisher(ApplicationEventPublisher appCtx) {
this.eventPublisher = appCtx;
}
}
classpath:./testXML.xml配置:
<bean id="methodExecListener" class="...MethodExecutionEventListener"/>
<bean id="evtPublisher" class="...MethodExecutionEventPublisher"/>
6 Spring IoC容器之扩展篇
6.1 自动绑定
手动绑定
使用<constructor-arg>标签或<property>标签为对象实例的属性注入依赖。
package ioc;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
// ApplicationContext批量加载资源
ApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"test.xml"});
Student student = context.getBean("student", Student.class);
System.out.println(student.getBook());
}
}
class Student {
private Book book;
public Book getBook() {
return book;
}
public void setBook(Book book) {
this.book = book;
}
}
class Book {
}
classpath:test.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-2.5.xsd">
<bean id="student" class="ioc.Student">
<property name="book" ref="book"></property>
</bean>
<bean id="book" class="ioc.Book"></bean>
</beans>
自动绑定
根据bean定义的某些特点将相互依赖的某些bean直接自动绑定。
Spring提供了5种自动绑定模式:no、byName、byType、constructor、autodetect。默认为no(只能手动绑定)。
通过<bean>标签的autowire属性,可以指定采用何种类型的自动绑定模式。
<?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-2.5.xsd">
<bean id="student" class="ioc.Student" autowire="byName"></bean>
<bean id="book" class="ioc.Book"></bean>
</beans>
6.2 基于注解的自动绑定
@Autowired
1) @Autowired是按照类型匹配进行依赖注入的,类似于byType类型的自动绑定,它比byType更加灵活,可以标注在属性、方法、构造方法上。
2) IoC容器需要某种方式来了解哪些对象标注了@Autowired,Spring提供了一个BeanPostProcessor实现类AutowiredAnnotationBeanPostProcessor,在实例化bean定义的过程中,检查当前对象是否有@Autowired标注的依赖需要注入。只需要在配置文件中追加:
<bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"/>
@Qualifier
如果用byType方式同时找到多个同类型的对象实例,可以使用注解@Qualifier对依赖注入的条件做进一步限定。
@Qualifier是byName自动绑定的注释版,可以单独使用,也可以和@Autowired一起使用。
@Qualifier也需要配置AutowiredAnnotationBeanPostProcessor。
@Resource
@Resource遵循byName自动绑定形式的行为准则。
Spring提供了一个BeanPostProcessor实现类CommonAnnotationBeanPostProcessor,在实例化bean定义的过程中,检查当前对象是否有@Resource标注的依赖需要注入。只需要在配置文件中追加:
<bean class="org.springframework.beans.factory.annotation.CommonAnnotationBeanPostProcessor"/>
6.3 消灭XML中的bean定义
<context:annotation-config>
我们可以在基于XSD的配置文件中使用一个<context:annotation-config/>搞定所有的BeanPostProcessor配置。
实际上<context:annotation-config>的功能会被下面要介绍的<context:component-scan>的功能覆盖,所以一般也用不到。
<context:component-scan>
Spring提供了classpath-scanning功能,可以从某一顶层包(base package)开始扫描,当扫描到某个类标注了相应的注解(下面会介绍)之后,就会提取该类的相关信息,构建对应的BeanDefinition,然后把构建完的BeanDefinition注册到容器。
我们可以在基于XSD的配置文件中使用<context:component-scan base-package="xx.xx"/>开启classpath-scanning功能。
<context:component-scan>默认扫描的注解类型是@Component。不过,在@Component语义基础上细化后的@Repository、@Service、@Controller也同样可以被<context:component-scan>扫描到。事实上四个注解在功能上几乎没有区别,只是在语义上会有相对比较好的选择。
<context:component-scan>在扫描相关类定义并将它们添加到容器时,会使用一种默认的命名规则来生成beanName。默认规则是【将类名首字母小写】。
7 一起来看AOP
待补充。