• 深入了解Spring


    1.Bean后处理器

    Spring容器提供了一个接口InitializingBean,实现这个接口的bean只要重写afterPropertiesSet()或者在XML中添加init-method属性,就可以在Bean初始化前后执行特定行为。

    InitializingBean是针对单个Bean起作用的,Spring还提供了另外一个接口叫BeanPostProcessor,这个接口是针对容器中所有Bean起作用的。

    只要定义个普通的Bean实现这个接口,并实现postProcessBeforeInitialization()和postProcessAfterInitialization()两个方法,容器中所有的Bean都会受影响。

    下面是一个实现了BeanPostProcessor的普通bean类,

     1 package spi;
     2 
     3 import org.springframework.beans.BeansException;
     4 import org.springframework.beans.factory.config.BeanPostProcessor;
     5 
     6 public class MyBeanPostProcessor implements BeanPostProcessor {
     7 
     8     @Override
     9     public Object postProcessBeforeInitialization(Object bean, String beanName)
    10             throws BeansException {
    11         System.out.println("Bean后处理器在初始化之前对 "+beanName+" 进行增强处理...");
    12         return bean;
    13     }
    14 
    15     @Override
    16     public Object postProcessAfterInitialization(Object bean, String beanName)
    17             throws BeansException {
    18         System.out.println("Bean后处理器在初始化之后对 "+beanName+" 进行增强处理...");
    19         if (bean instanceof Chinese) {
    20             System.out.println("Bean后处理器将Chinese.beanName修改为'Java编程'.");
    21             Chinese c = (Chinese)bean;
    22             c.setBeanName("Java编程");
    23         }
    24         return bean;
    25     }
    26     
    27 }

    特意让这个bean修改chinese这个bean的属性,下面是chinese Bean的代码,

     1 package spi;
     2 
     3 import org.springframework.beans.factory.BeanNameAware;
     4 import org.springframework.beans.factory.InitializingBean;
     5 
     6 public class Chinese implements Person, InitializingBean {
     7     private Son son;
     8     private String beanName;
     9 
    10     public void setBeanName(String beanName) {
    11         System.out.println(beanName+":Spring正在执行setBeanName()方法注入依赖关系...");
    12         this.beanName = beanName;
    13         
    14     }
    15     public void info(){
    16         System.out.println("我在XML中的id名称为:"+beanName);
    17     }
    18     public int age;
    19     private Axe axe;
    20     public int getAge() {
    21         return age;
    22     }
    23     public void setAge(int age) {
    24         this.age = age;
    25     }
    26     public Axe getAxe() {
    27         return axe;
    28     }
    29     public void setAxe(Axe axe) {
    30         this.axe = axe;
    31     }
    32     public Chinese() {
    33         System.out.println("Spring正在执行默认构造函数构造Chinese实例...");
    34     }
    35     public Chinese(Axe axe) {
    36         this.axe = axe;
    37     }
    38     public void useAxe() {
    39         System.out.println("我打算去砍点柴火");
    40         System.out.println("修改beanName="+beanName+","+axe.chop());
    41     }
    42     public void close() {
    43         System.out.println("正在执行销毁前的方法 close ...");
    44     }
    45     public Son getSon() {
    46         return son;
    47     }
    48     public void setSon(Son son) {
    49         this.son = son;
    50     }
    51     public void init() {
    52         System.out.println("正在执行初始化方法init...");
    53     }
    54     @Override
    55     public void afterPropertiesSet() throws Exception {
    56         // TODO Auto-generated method stub
    57         System.out.println("正在执行初始化方法afterPropertiesSet...");
    58     }
    59 
    60 }

    Chinese Bean实现了InitializingBean接口,添加了init()(需要XML配置)方法和afterPropertiesSet()方法,那么它在初始化前后会被插入特定行为,行为由这两个方法决定。 同时,由于容器中存在实现了BeanPostProcessor的Bean,那么所有Bean包括chinese,都会在初始化前后批量被插入特定行为。

    XML配置如下,

    1     <bean id="chinese" class="spi.Chinese" 
    2     init-method="init" p:axe-ref="steelAxe" p:beanName="依赖注入的值" />
    3     <bean class="spi.MyBeanPostProcessor" />

    测试代码,

    1     public static void test8() {
    2         ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
    3         Person p = ctx.getBean("chinese", Person.class);
    4         p.useAxe();
    5     }

    执行结果,

     1 Bean后处理器在初始化之前对 messageSource 进行增强处理...
     2 Bean后处理器在初始化之后对 messageSource 进行增强处理...
     3 Bean后处理器在初始化之前对 stoneAxe 进行增强处理...
     4 Bean后处理器在初始化之后对 stoneAxe 进行增强处理...
     5 Bean后处理器在初始化之前对 steelAxe 进行增强处理...
     6 Bean后处理器在初始化之后对 steelAxe 进行增强处理...
     7 Bean后处理器在初始化之前对 spi.EmailNotifier#0 进行增强处理...
     8 Bean后处理器在初始化之后对 spi.EmailNotifier#0 进行增强处理...
     9 Bean后处理器在初始化之前对 getContextViaBean 进行增强处理...
    10 Bean后处理器在初始化之后对 getContextViaBean 进行增强处理...
    11 Bean后处理器在初始化之前对 getField 进行增强处理...
    12 Bean后处理器在初始化之后对 getField 进行增强处理...
    13 Spring正在执行默认构造函数构造Chinese实例...
    14 依赖注入的值:Spring正在执行setBeanName()方法注入依赖关系...
    15 Bean后处理器在初始化之前对 chinese 进行增强处理...
    16 正在执行初始化方法afterPropertiesSet...
    17 正在执行初始化方法init...
    18 Bean后处理器在初始化之后对 chinese 进行增强处理...
    19 Bean后处理器将Chinese.beanName修改为'Java编程'.
    20 Java编程:Spring正在执行setBeanName()方法注入依赖关系...
    21 我打算去砍点柴火
    22 修改beanName=Java编程,钢斧砍柴好快

     从执行结果可以看到,受BeanPostProcessor影响,所有Bean都被插入了两条特定行为,chinese Bean由于额外实现了InitializingBean接口而多了两条额外行为。

    另外可以看到虽然在配置文件中chinese的beanName被注入的是“依赖注入的值”,但是初始化之后它又被修改成了“Java编程”,所以setBeanName也被调用了两次。

    从上面的例子可以看出,BeanPostProcessor接口的作用就是对容器bean进行批量处理,实际中Spring的这种后处理很有用,通常用来实现代理器,

    例如BeanNameAutoProxyCreator是根据Bean实例的name属性,创建Bean实例的代理。

    DefaultAdvisorProxyCreator是根据提供的Advisor对容器所有Bean实例创建代理。

    2.容器后处理器

    类似的,容器也有后处理器,专门用来扩展容器的功能。容器后处理器需要实现BeanFactoryPostProcesser接口。

    Spring为我们实现了几个容器后处理器,常用的有PropertyPlaceholderConfigurer, PropertyOverrideConfigurer等。

    PropertyPlaceholderConfigurer可以用property文件来替换Spring配置文件中的占位符变量,典型用法如,

     1     <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
     2         <property name="locations">
     3             <list>
     4                 <value>dbconn.properties</value>
     5                 <value>dbconn.properties2</value>
     6                 <value>dbconn.properties3</value>
     7             </list>
     8         </property>
     9     </bean>
    10     
    11     <bean id="dataSource" class="conn.machange.v2.c3p0.ComboPooledDataSource" destroy-method="close"
    12         p:driverClass="${jdbc.driverClassName}"
    13         p:jdbcUrl="${jdbc.url}"
    14         p:user="${jdbc.username}"
    15         p:password="${jdbc.password}" />

    上面定义的dataSource Bean,并没有直接将值注入driverClass, jdbcUrl等属性中,而是使用${jdbc.driverClassName},${jdbc.url},

    因为有了容器后处理器PropertyPlaceholderConfigurer的存在,Spring会查找对应的外部properties文件并用里面设置的变量替换Spring中的变量,外部properties文件内容如下,

    dbconn.properties

    jdbc.driverClassName=com.mysql.jdbc.Driver
    jdbc.url=jdbc:mysql://localhost:3306/test
    jdbc.username=root
    jdbc.pawword=123456

    另外还有PropertyOverrideConfigurer容器后处理器,这个跟前面有点类似都是用来将Spring配置提取到外部properties文件中,但是功能更强大。

    PropertyOverrideConfigurer属性文件指定的信息可以直接覆盖Spring配置的元数据,还是上面的例子,

     1     <bean class="org.springframework.beans.factory.config.PropertyOverrideConfigurer">
     2         <property name="locations">
     3             <list>
     4                 <value>dbconn.properties</value>
     5                 <value>dbconn.properties2</value>
     6                 <value>dbconn.properties3</value>
     7             </list>
     8         </property>
     9     </bean>
    10     
    11     <bean id="dataSource" class="conn.machange.v2.c3p0.ComboPooledDataSource" 
    12     destroy-method="close" />

    可以看到dataSource这个bean没有最注入任何属依赖属性,但是在下面的外部properties文件中,却配置了dataSource Bean的属性,注意这里的属性名称必须是Bean确实拥有的才行。

    dbconn.properties
    dataSource.driverClassName=com.mysql.jdbc.Driver
    dataSource.url=jdbc:mysql://localhost:3306/test
    dataSource.username=root
    dataSource.pawword=123456

    3.Spring的Annotation

    使用Spring的Annotation可以不用再在XML文件中进行bean的配置,只需要在XML中设置好bean的搜索路径,然后在bean上使用相应的Annotation就行了。

    XML配置Bean搜索路径如下,使用<context:component-scan的base-package属性进行配置,

    还可以使用<context:include-filter和<context:exclude-filter进行Bean路径的配置,这种配置不需要在Bean类名上加注解,容器就能自动识别出bean

     1 <?xml version="1.0" encoding="UTF-8"?>
     2 <beans xmlns="http://www.springframework.org/schema/beans"
     3     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     4     xmlns:context="http://www.springframework.org/schema/context"
     5     xsi:schemaLocation="http://www.springframework.org/schema/beans
     6     http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
     7     http://www.springframework.org/schema/context
     8     http://www.springframework.org/schema/context/spring-context-4.0.xsd">
     9     <!-- 自动扫描指定包及其子包下的所有Bean类 -->
    10     <context:component-scan base-package="spi" />
    11     <context:include-filter type="regex" expression=".*Chinese" />
    12     <context:exclude-filter type="regex" expression="Stone*" />
    13 </beans>

    3.1@Componnet

    接着我们只需要将Bean类名加上Annotation注解就行了,Spring可以使用@Component, @Controller, @Service, @Repository四种注解将一个类标识为容器中的bean,最常用的是@Component.

     1 @Component
     2 public class Chinese implements Person, InitializingBean {
     3 ...
     4 
     5 @Component
     6 public class SteelAxe implements Axe { 
     7 ...
     8 
     9 @Component
    10 public class StoneAxe implements Axe { 
    11 ...

    3.2@Scope

    @Scope用来指定Bean的作用域,与XML配置中的scope属性意义一样

    1 @Scope("prototype")
    2 @Component
    3 public class Chinese implements Person, InitializingBean {
    4 ...

    3.3@Resource

    Spring通过@Resource设置依赖关系,用来修饰setter方法,相当于XML配置中的<property>元素,@Resource中有一个name属性,为name属性设置值就相当于设置ref属性。

    1     @Resource(name="stoneAxe")
    2     public void setAge(int age) {
    3         this.age = age;
    4     }

    @Resource也可以直接修饰实例变量,一样会进行依赖注入,并且括号中的name可以省略,

    1     @Resource(name="stoneAxe")
    2     private Axe axe;
    3 //或者
    4     @Resource(”stoneAxe")
    5     private Axe axe;

    3.4@PostConstruct和@PreDestroy

    这两个注解相当于XML中的init-method和destroy-method属性,会在bean初始化期间添加额外行为,只不过这两个注解不需要任何属性值,直接将注解放在对应的方法上即可

    1     @PostConstruct
    2     public void init() {
    3         System.out.println("正在执行初始化方法init...");
    4     }
    5     @PreDestroy
    6     public void afterPropertiesSet() throws Exception {
    7         // TODO Auto-generated method stub
    8         System.out.println("正在执行初始化方法afterPropertiesSet...");
    9     }

    3.5@DependsOn和@Lazy

    @DependsOn用于强制初始化其他(依赖的)多个bean,以数组形式作为此注解的参数值,

    1 @DependsOn({"steelAxe","stoneAxe"})
    2 @Scope("prototype")
    3 @Component
    4 public class Chinese implements Person, InitializingBean {
    5 ...

    3.6@Autowired

    Spring4.0中的@Autowired进行了功能增强,可以用来修饰setter方法,普通方法,实例变量和构造器,甚至是数组变量。默认使用byType(按参数类型)自动装配策略。

     1     // 修饰setter方法
     2     @Autowired
     3     public void setAge(int age) {
     4         this.age = age;
     5     }
     6 ...
     7     //修饰普通方法,支持多个参数
     8     @Autowired
     9     public void test(Axe axe, Person person) {
    10         
    11     }
    12 ...
    13     //修饰成员变量
    14     @Autowired
    15     private Axe axe;
    16 ...
    17     //修饰构造器
    18     @Autowired
    19     public Chinese(Axe axe) {
    20         this.axe = axe;
    21     }
    22 ...
    23     //修饰数组,Spring会先收集类型相符的bean,构造数组,然后进行依赖注入
    24     @Autowired
    25     private Axe[] axes;
    26 ...

    需要注意的是,@Autowired除了修饰数组之外,对于其他情况(setter方法,普通方法,成员变量,构造函数),如果Spring在容器中找到超过一个符合类型的bean,将会报错

    @Autowired默认使用byType策略进行依赖注入时bean的查找,如果希望指定依赖注入的bean id,则需要使用@Qualifier

    1     @Qualifier("steelAxe")
    2     private Axe axe;

    当然,这么做就没什么意义了,因为@Resource完全可以实现这个功能,@Qualifier还有个用处就是可以指定方法形参时的依赖注入,

    1     public void setAxe(@Qualifier("steelAxe") Axe axe) {
    2         this.axe = axe;
    3     }

     4.Resource接口

    Spring提供了一个Resource接口用来访问资源,其底层是封装了各种各种资源的访问方式,例如网络资源,本地资源,二进制数据等等,

    通过Resource实现类的对象,可以对资源进行统一方式的操作,还可以通过Rescource实现类的对象获取资源的File实例,以java IO的方式操作资源。

    Resource提供如下对资源的统一访问方法:getInputStream(), exists(), isOpen(), getDescription(), getFile(), getUrl()。

    Spring为Resource默认已经实现了如下类:UrlResource, ClassPathResource, FileSystemResource, ServletContextResource, InputStreamResource, ByteArrayResource.

    下面是几个实现类的用法,

     1     public static void test10() throws IOException {
     2         UrlResource ur = new UrlResource("file:book.xml");
     3         //ClassPathResource ur = new ClassPathResource("book.xml");
     4         //FileSystemResource ur = new FileSystemResource("book.xml");
     5         //ServletContextResource ur = new ServletContextResource(application,"WEB-INF/book.xml");
     6         //String file="Java编程核心思想";
     7         //byte[] fileBytes = file.getBytes();
     8         //ByteArrayResource ur = new ByteArrayResource(fileBytes);
     9         System.out.println(ur.getFilename());
    10         System.out.println(ur.getDescription());
    11         File file = ur.getFile();
    12         System.out.println(file.getName());
    13     }

    可以看到上面代码中,对于每一种Resource实现类,在Spring使用时仅仅是获取资源的方式不同,但是操作文件和数据一模一样,都是通过Resourcce实现类获取资源对象,以相同方法获取资源信息,都可以转化为File实例进行后续操作,当然对于二进制数据就没有文件的说法,所以getFileName之类的方法是不起作用的。

    另外还注意到,不同的Resource实现类在获取资源时,传入的参数类型不尽相同,有的需要http:前缀,有的需要file:前缀,有的不需要前缀,这是由实现类的底层性质针对不同类型的资源决定的。

    5.ResourceLoader和ResourceLoaderAware接口

    在上面的例子中,我们总是需要显式地写出要使用哪个Resource的实现类来获取资源,这显得比较麻烦,Spring又为我们提供了两个接口,

    ResourceLoader 用来获取一个Resource实例

    ResourceLoaderAware 获取Resource实例的引用

    通过ResourceLoader,我们可以实现面向接口的编程,直接获取一个资源,

    由于ApplicationContext类也实现了ResourceLoader接口,所以可以直接把ApplicationContext当作一个ResourceLoader,用ApplicationContext实例去获取一个资源,像这样:

    1 Resource res =ctx.getResource("beans2.xml");

    这样就直接获取了一个资源实例,简化了编程,不过这样获取的资源,其底层是通过什么方式(即由哪个Resource的实现类)来获取资源的呢,

    Spring采用的是典型的策略模式,根据ApplicationContext实例化时候的策略,来决定这里获取Resource的策略。应用程序只需要使用统一的接口,调用相同的实现方法,而不需要关系底层到底是用了哪个实现类。

    如果ApplicationContext实例化使用的是ClassPathXmlApplicationContext方式,那么Resource就是采用ClassPathResource实现类;

    如果ApplicationContext实例化使用的是FileSystemXmlApplicationContext方式,那么Resource就是采用FileSystemResource实现类,

    以此类推。

    因此前面获取资源的代码我们就可以简化如下:

    1     public static void test11() throws IOException {
    2         ApplicationContext ctx = new ClassPathXmlApplicationContext("beans2.xml");
    3         //ApplicationContext ctx = new FileSystemXmlApplicationContext("beans2.xml");
    4         Resource res =ctx.getResource("beans2.xml");
    5         System.out.println(res.getFilename());
    6         System.out.println(res.getDescription());
    7         File file = res.getFile();
    8         System.out.println(file.getName());
    9     }

    这样就将获取资源的Resource实现类解耦了,我们只需要直接使用Resource接口即可。

    当然,如果你非要指定具体的Resource实现类也是可以的,只需要在ctx.getResource(..)时,在传入的参数前加不同的前缀,例如

    1         //无前缀表示由ApplicationContext来决定加载策略
    2         Resource res =ctx.getResource("beans2.xml");
    3         //以ClassPathResource访问类加载路径下的资源
    4         Resource res =ctx.getResource("classpath:beans2.xml");
    5         //以UrlResource实例访问本地系统
    6         Resource res =ctx.getResource("file:beans2.xml");
    7         //以UrlResource实例访问基于HTTP的网络资源
    8         Resource res =ctx.getResource("http:beans2.xml");

    6.将Resource实例作为Bean属性

    比ResourceLoader更能解耦的方式是直接将Resource的实例作为Bean的属性,这样就能直接在XML文件中配置需要访问的资源名称,例如这样,

    1 public class Chinese implements Person, InitializingBean {
    2     private Resource res;
    3     public Resource getRes() {
    4         return res;
    5     }
    6     public void setRes(Resource res) {
    7         this.res = res;
    8     }
    9 ...

    这样便可以将具体的资源名称写入配置文件,使得能最大显得地将资源名称与具体java代码解耦

    <bean id="chinese" class="spi.Chinese" p:res="classpath:book.xml" />
  • 相关阅读:
    在Pycharm中使用GitHub
    Ubuntu20.04开启root账户的方法步骤
    使用git push文件到gitee
    Dell主机安装win10+Ubuntu20.04双系统
    Golang select 基础语法与用法
    Golang websocket 实现消息推送
    Golang + gRPC 实践
    Golang实现RPC
    unigui+fastReport实现web打印方案(43)
    [控件] 加强版 TOneSelection (改良自 Berlin 10.1 TSelection)
  • 原文地址:https://www.cnblogs.com/fysola/p/6366238.html
Copyright © 2020-2023  润新知