《Spring揭秘》读书笔记 1:IoC容器

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。

工厂模式

有时,我们需要依赖第三方库,需要实例化并使用第三方库中的类,这时,接口与实现类的耦合性可以使用工厂方法模式(Factory Method Pattern)来避免。

提供一个工厂类来实例化具体的接口实现类,这样,主体对象只需要依赖工厂类,具体使用的实现类有变更的话,只是变更工厂类,而主体对象不需要做任何变动。

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对统一资源加载策略的支持

ApplicationContext直接或间接实现了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。默认规则是【将类名首字母小写】。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值