《Spring揭秘》读书笔记 1:IoC和AOP

news2025/1/15 20:40:45

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

待补充。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/153258.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何抓住风口,利用互联网赚钱?(內含三大商业模式推荐)建议收藏

大家好&#xff0c;我是你们熟悉而又陌生的好朋友梦龙&#xff0c;一个创业期的年轻人 今天跟你做个分享&#xff0c;众所周知互联网是一块非常大的蛋糕&#xff0c;几位互联网巨头也做不到完全吃透&#xff0c;同时也是一个门槛较低的创业之路&#xff0c;非常的适合年轻人&a…

8、Javaweb_ServlethttpRequst

Servlet&#xff1a; 1. 概念 2. 步骤 3. 执行原理 4. 生命周期 5. Servlet3.0 注解配置 6. Servlet的体系结构 Servlet -- 接口 | GenericServlet -- 抽象类 | HttpServlet -- 抽象类 * GenericServlet&#xff1a;将Servlet接口中其他的方…

C语言-指针进阶-常见笔试面试题详解(9.4)

目录 思维导图&#xff1a; 指针和数组笔试题 指针笔试题 写在最后&#xff1a; 思维导图&#xff1a; 指针和数组笔试题 只有多刷题&#xff0c;才能巩固提高所学的知识。 例1&#xff1a; #include <stdio.h>int main() {//一维数组int a[] { 1,2,3,4 };//求出…

「精研科技」× 企企通,全球MIM龙头借助采购供应商数字化向多领域突破

近日&#xff0c;金属粉末注射成型&#xff08;MIM&#xff09;龙头企业江苏精研科技股份有限公司&#xff08;以下简称“精研科技”&#xff09;与企企通达成合作。双方将共同构建完整的采购管理和供应商协同平台&#xff0c;加强供应商管理&#xff0c;提高采购效率&#xff…

Netty源码性能分析 - ThreadLocal PK FastThreadLocal

序 既然jdk已经有ThreadLocal&#xff0c;为何netty还要自己造个FastThreadLocal&#xff1f;FastThreadLocal快在哪里&#xff1f;这需要从jdk ThreadLocal的本身说起。在java线程中&#xff0c;每个线程都有一个ThreadLocalMap实例变量&#xff08;如果不使用ThreadLocal&…

使用Alexnet实现CIFAR100数据集的训练

如果对你有用的话&#xff0c;希望能够点赞支持一下&#xff0c;这样我就能有更多的动力更新更多的学习笔记了。&#x1f604;&#x1f604; 使用Alexnet进行CIFAR-10数据集进行测试&#xff0c;这里使用的是将CIFAR-10数据集的分辨率扩大到224X224&#xff0c;因为在测试…

【ROS】—— ROS常用组件_TF坐标变换_多态坐标变换与TF坐标变换实操(十一)

文章目录前言1. 多态坐标变换1.1 发布方1.2 订阅方(C)1.3 订阅方(python)2. 坐标系关系查看3. TF坐标变换实操(C)3.1准备3.2 生成新的乌龟3.3 增加键盘控制3.4 发布方(发布两只乌龟的坐标信息)3.5 订阅方(解析坐标信息并生成速度信息)前言 &#x1f4e2;本系列将依托赵虚左老师…

将git仓库瘦身

一个小工具的仓库居然有7个g了&#xff0c;每次clone都要等好久&#xff0c;在网上找的方法&#xff0c;实际了几个小时才成功瘦身&#xff0c;做一次记录 一、排查是哪些历史文件占用了内存&#xff0c;下面是查询最大的5个文件 git rev-list --objects --all | grep "$(…

使用nvm管理node版本,实现高版本与低版本node之间的转换

第一步&#xff1a;先清空本地安装的node.js版本 1.按健winR弹出窗口&#xff0c;键盘输入cmd,然后敲回车&#xff08;或者鼠标直接点击电脑桌面最左下角的win窗口图标弹出&#xff0c;输入cmd再点击回车键&#xff09; 然后进入命令控制行窗口&#xff0c;并输入where node查…

Linux环境配置

一、安装ubuntu16的步骤 1、新建Ubuntu 右击文件---------新建虚拟机 典型----------------下一步 稍后安装操作系统-------------下一步 Linux&#xff08;L&#xff09;------------------Ubuntu64位-----------------下一步 虚拟机名称&#xff08;随意起&#xff09;----…

OSS简单介绍

OSS 阿里云对象存储OSS&#xff08;Object Storage Service&#xff09;是一款海量、安全、低成本、高可靠的云存储服务&#xff0c;可提供99.9999999999%&#xff08;12个9&#xff09;的数据持久性&#xff0c;99.995%的数据可用性。多种存储类型供选择&#xff0c;全面优化…

197: vue+openlayers 预加载preload瓦片地图,减少过渡期间的空白区域

第197个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+openlayers项目中演示瓦片预加载和没有预加载的不同状态。 没有采用预加载 当我们平移和缩放地图时,经常会遇到过渡期间的空白区域(因为内容正在加载),过一会儿,切片图像才出现了。 采用预加载,将预载的值设置为…

深入ReentrantLock锁

1. 前言 今天我们来探讨下另一个核心锁ReentrantLock. 从具体的实现到JVM层面是如何实现的。 我们都会一一进行讨论的&#xff0c;好了&#xff0c;废话不多说了&#xff0c;我们就开始吧 2. ReentrantLock 以及synchronized 核心区别&#xff1a; ReentrantLock 是一个抽象的…

MVC框架知识详解

✅作者简介&#xff1a;热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏&#xff1a;Java案例分…

机器学习课程学习随笔

文章目录本文来源机器学习简介机器学习流程机器学习可以完成如下功能&#xff1a;机器学习应用场景金融领域零售领域机器学习分类机器学习实现基于python等代码自己实现本文来源 本博客 是通过学习亚马逊的官方机器学习课程的随笔。 课程参考链接https://edu.csdn.net/course/…

爬虫与反爬虫 - 道高一尺魔高一丈 - 2013最新 - JS逆向 - Python Scrapy实现 - 爬取某天气网站历史数据

目录 背景介绍 网站分析 第1步&#xff1a;找到网页源代码 第2步&#xff1a;分析网页源代码 Python 实现 成果展示 后续 Todo 背景介绍 今天这篇文章&#xff0c;3个目的&#xff0c;1个是自己记录&#xff0c;1个是给大家分享&#xff0c;还有1个是向这个被爬网站的前…

synchronized锁膨胀(附代码分析)

synchronized锁膨胀 1. 基本概念 Java对象头 Java对象的对象头信息中的 Mark Word 主要是用来记录对象的锁信息的。 现在看一下 Mark Word 的对象头信息。如下&#xff1a; 其实可以根据 mark word 的后3位就可以判断出当前的锁是属于哪一种锁。注意&#xff1a;表格中的…

shell脚本练习2023年下岗版

shell脚本练习 1.判断指定进程的运行情况 #!/bin/bash NAMEhttpd #这里输入进程的名称 NUM$(ps -ef |grep $NAME |grep -vc grep) if [ $NUM -eq 1 ]; thenecho "$NAME running." elseecho "$NAME is not running!" fi2.判断用户是否存在 #!/bin/bash r…

【RabbitMQ】安装、启动、配置、测试一条龙

一、基本环境安装配置 1.英文RabbitMQ是基于erlang开发的所以需要erlang环境,点击以下链接下载安装 Downloads - Erlang/OTP 2.官网下载RabbitMQ安装包并安装 Installing on Windows — RabbitMQ 3.配置erlang本地环境变量(和JAVAHOME类似) 4.cmd查看erlang版本 5.点击以下…

自己看的操作系统

计算机网络冯诺依曼体系进程线程内核和虚拟内存os管理线程冯诺依曼体系 计算机五大组成&#xff1a;输入设备、输出设备、控制器、运算器、存储器 进程线程 这些应用都是进程 进程相当于一个菜谱&#xff0c;读取到内存中去使用。 电脑一时间能运行很多进程。 进程中为什么要…