Spring IOC

一. spring

1.1 BeanFactory 和 ApplicationContext

 简单的讲 BeanFactroy 是 spring 框架的基础设施,面向 spring 本身。ApplicationContext 则面向使用 spring框架的开发者,几乎所有的场景我们都是用 ApplicationContext,而非底层的 BeanFactory。

1.1.1 BeanFactory

1.1.2 ApplicationContext

ApplicationContext初始化而很简单,如果配置文件放在类路径下,可以优先考虑使用ClassPathXmlApplicationContext实现类

ApplicationContext application = new                            ClassPathXmlApplicationContext("classpath:com/context/bean.xml");

如果配置文件放在文件系统的路径下,则优先考虑 FileSystemXmlApplicationContext 实现类。

ApplicationContext application = new FileSystemXmlApplication("file:com/context/bean.xml"):

 相比较BeanFactory来说,ApplicationContext在初始化的时候已经实例化所有的单例Bean了,不需要进行手动。所以ApplicationContext在初始化的时候所用的时间要比BeanFactory长一些,但在后面调用的时候则没有了“第一次惩罚时间”的问题。

同时,Spring为基于注解的配置提供了专门的 ApplicationContext 实现类 AnnotationConfigApplicationContext。这个是用于java类配置的。

@Configuration
public class Beans{

@Bean(name = "car")
public  Car bulidCar(){
    Car car = new Car();
    car.setColor("red");
    return car;
 }
}

public class AnnotationApplicationContext{
    publuc static void main(String[] args){
        ApplicationContext ctx = new AnnotataionConfigApplicationContext(Beans.class);
       Car car = ctx.getBean("car",Car.class);
    }
}

1.2. WebApplicationContext

  WebApplicationContext是专门为WEb应用准备的,它允许从相对于web根目录的路径中装载配置文件来完成初始化工作。在web应用中WebapplicationContext作为属性存放到 ServletContext中,以便web应用环境可以访问Spring应用上下文。spring专门为此提供了一个工具类WebapplicationContextUtils,通过该类的getWebApplicationContext(ServletContext sc);方法获取servletContext 中 webApplicationContext 的实例。

1.2.1. WebApplicationContext初始化

 与ApplicationContext和BeanFactory区别,webApplicationContext的初始化需要ServletContext实例,也就是说WebApplicationContext必须要有web容器的前提下才能完成启动工作。

spring提供了两种供WebApplicationContext启动的方式,WebApplicationContext的servlet 和 Web容器监听器。

1.org.springframework.web.context.ContextLoaderServlet

<context-param>
      <param-name>contextConfigLocation</param-name>
       <param-value>/WEB_INF/taobao.xml<param-name>
</context-param>
<servlet>
     <servlet-name>springContextLoaderServlet</servlet-name>
    <servlet-value>org.springframework.web.context.ContextLoaderServlet</servlet-value>
    <load-on-startup>1</load-on-startup>
</servlet>
  1. org.springframework.web.context.ContextLoaderListener
    ContextLoaderListener通过web容器上下文参数contextConfigLocation获取spring配置文件的位置。

    contextConfigLocation /WEB_INF/taobao.xml org.springframework.web.context.ContextLoaderListener

1.3. Bean的生命周期

1.3.1 BeanFactory中Bean的生命周期

Bean的生命周期有两种方式进行描述:1. Bean的作用范围。2. Bean从实例化到销毁所经历的各个阶段。

Bean的完整生命周期从spring着手实例化Bean开始,直到最终销毁Bean,这当中经过了许多的关键点,每个关键点都涉及特定的方法的调用,可以将这些方法大致划分为三类:

Bean自身的方法:如调用Bean构造函数实例化Bean,调用Setter设置bean的属性值,以及init-method 和 destroy-method 所指定的方法。

Bean级生命周期接口方法:如BeanNameAware,BeanFactoryAware,InitializingBean 和 DisposableBean,这些接口方法是由Bean类直接实现。

容器级生命周期接口方法:实现了InstantiationAwareBeanPostProcessor 和 BeanPostProcessor接口的实现类,他们被称之为后处理器。这些后处理器在spring容器创建任何Bean的时候都会发生作用,所以这些后处理器的影响是全局性的。用户可以通过合理的编写后处理器,让其仅对感兴趣的Bean进行加工处理。

如果Bean没有实现任何一个接口 ,则bean在生命周期中所要经历的过程

1.调用getBean();方法 实例化bean

2.若配置了属性信息,则调用Bean的属性设置方法设置属性。

3.若配置了init-method 方法,则执行这个方法。

4.根据作用域的不同进行分配,若为prototype作用域的 ,将把Bean返回给调用者,调用者负责Bean的后续生命周期,spring将不再管理。若为singleton作用域的,bean将被放到spring的缓存池中,将bean的引用返回给调用者,spring继续对这些bean进行后续的生命周期进行管理。

5.对于作用域为singleton的,当容器关闭时,触发后续管理工作。若destroy-method初始化有销毁方法,这时执行这个方法,完成bean的销毁和资源的释放。

1.3.2 ApplicationContext中Bean生命周期

ApplicationContext 与BeanFactory相对比有一点很大的不同之处,ApplicationContext 通过java的反射机制能够自动识别配置到配置文件里的Bean后处理器和工厂后处理器(BeanPostProcessor,InstantitionAwareBeanPostProcessor,BeanFactoryPostProcessor),并且注册到应用上下文当中,而BeanFactory则必须通过手动的方式,调用addBeanPostProcessor();方法进行注册。

二 . IOC 概念和装配Bean

IoC:控制反转;DI:依赖注入。是Spring的核心内核。

2.1. 依赖注入

spring支持几种依赖注入的方式:属性注入,构造函数注入,和基本不推荐使用的工厂方式注入

2.1.1. 属性注入

属性注入要求提供默认的无参构造函数,以及成员属性的setter方法。spring先调用默认构造函数实例化Bean,然后用java反射的方式调用setter方法注入属性值。

<bean id="car" class="com.taobaobao.Car">
   <property name="color"><value>"red"</value></property>
   <property name="maxSpeed"  value=200/>
   <property name="oner"  ref="oner"/>
</bean>
<bean id="oner" class="com.taobaobao.Oner"/>

2.1.2. 构造函数注入

要使用构造函数注入必须提供有参的构造函数。

public class Car{
public Car(String color,double maxSpeed){
   this.color = color;
  this.maxspeed = maxSpeed;  
  }
}

//使用构造函数注入配置
<bean id ="car",class="com.taobaobao.Car">
  <coustructor-arg type="java.lang.String" ><value>"红色"</value></constructor-arg>
  <coustructor-arg type="double">< value>200</value></coustructor-arg>
</bean>

如果构造函数的每一个参数都是对应一种数据类型,则配置时可以通过type属性来区分,配置的值属于哪一个成员变量。但是如果构造函数的参数里面包含几个数据类型相同的参数则仅仅通过type是没有办法区别 对应的关系的。这时需要通过参数索引的方式进行确定。

public Car(String color, String name,double maxSpeed){
  this.color = color;
  this.name = name;
  this.maxSpeed = maxSpeed;
}

//构造函数注入配置
<bean id ="car",class="com.taobaobao.Car">
  <coustructor-arg index="0"><value>"红色"</value></constructor-arg>
  <coustructor-arg index="1">< value>"中华"</value></coustructor-arg>
  <coustructor-arg index="2">< value>200</value></coustructor-arg>
</bean>

如果类里面有多个构造函数,且其中有些参数是相同的,这时就需要的index 和 type 联合起来使用了。

2.2. Bean之间的关系

2.2.1. 继承

如果多个Bean之间存在相同的配置信息,spring允许我们定义一个父Bean,来消除重复代码,子Bean自动继承父Bean的配置信息。

未使用父子的配置

<bean id="car1" class=“com.taobaobao.Car” p:color="red" p:price="2000.00"    p:maxSpeed="250.00"/>
<bean id="car1" class=“com.taobaobao.Car” p:color="red" p:price="2000.00"    p:maxSpeed="200.00"/>

使用父子的配置

<bean id="abstractCar" class=“com.taobaobao.Car” p:color="red"  p:price="2000.00" abstract="true"/>
<bean id="car1"  p:maxSpeed="200.00"  parent="abstractCar"/>
<bean id="car2" p:maxSpeed="200.00" parent="abstractCar"/>

abstractCar定义为一个抽象Bean 设置abstract属性为true,则spring初始化时就不会实例化一个名为abstractCar的bean,如果不设置,则会初始化实例。

2.2.2. 依赖

例如有一对母子,kid类 必须依赖于mother类,先有mother 才会有 kid,类似这种情况spring相应提供了很好的解决方法。配置bean时通过设置depends-on 属性指定mother bean 是kid bean 的前置依赖bean。

<bean id="mother" class=“com.taobaobao.Motherr” />
<bean id="kid" class=“com.taobaobao.Kid”  depends-on="mother"/>

2.2.3. 引用

假设一个bean要引用另一个bean的id属性值,当然我们可以通过字面量的形式为其配置,但是这样两者没有建立引用关系。为此spring提供了元素标签,通过 引用另一个bean的名字。

<bean id="car1" class=“com.taobaobao.Car1” />
<bean id="boss" class=“com.taobaobao.boss”>
  <property  name="carId"><idref bean="car1"/></property>
</bean>

2.2.4. 多个配置文件的整合

大型的应用来说,拥有多个配置文件。spring允许我们通过使用将多个配置文件引入到一个文件中,进行配置文件的集成,这样spring容器启动时只需要指定这一个集成好的配置文件就可以了。

<import resource="classpath:com/taobaobao/import/bean.xml"/>
<bean id="mother" class=“com.taobaobao.Motherr” />
<bean id="kid" class=“com.taobaobao.Kid”  depends-on="mother"/>

2.3. bean 的作用域

spring低版本中bean只有两个作用域,spring 2.0后 针对WebApplicationContext新添加了3个作用域。

2.3.1. singleton 作用域

在spring容器中只存在一个Bean实例,Bean以单例形式存放。在Bean被调用是,spring会给调用者一个Bean的引用,而Bean本身一直存放在缓存池中。

2.3.2. prototype 作用域

每次从容器中调用Bean时,都返回一个新的实例。即每次调用getBean();方法时,都相当于执行了一次 new xxxBean();调用后的bean spring不在管理其生命周期,交由调用者管理。

2.3.3. Web应用环境相关的bean作用域

如果要使用WebApplicationContext环境下其他三个bean的作用域,则需要额外的一些配置。

低版本使用HTTP请求过滤器进行配置

<web-app>
  <filter>
      <filter-name>requestContextFilter</filter-name>
      <filter-class>org.springframework.web.filter.RequestContextFilter</filer-class>
 </filter>
 <filter-mapping>
     <filter-name>requestContextFilter</filter-name>
     <url-pattern> /* </url-pattern>
</filter-mapping>
</web-app>

高版本使用HTTP请求监听器进行配置。

<web-app>
    <listener>
        <listener-class>org.springframework.web.contect.request.RequestContextListener</listener-class>
   </listener>
</web-app>

为什么要新添加RequestContextListener监听器呢,这是因为RequestContextListener实现了ServletRequestListener监听接口,web服务器接受的每一次请求都会通知该监听器。

1.request作用域:设置为该作用域后,每一次HTTP请求调用bean时,spring为其创建一个新的bean,请求处理完毕后,销毁这个bean。

2.session作用域:设置为该作用域后,session中的所有http请求共享这一个bean,当http session 结束后,这个bean才会被销毁。

3.globalSession作用域:这个作用域仅在Portlet的web中使用。如果不在Portlet web应用环境中,该作用域相当于session。

重要的一点:将web相关作用域的bean注入到singleton或prototype的bean中,我们希望bean能够正常工作,即从指定的作用域中获取到bean,但是不做一些额外的配置是不能实现我们预期的效果。这种情况下就需要aop出手了。当一个request作用域的bean要被注入到一个singleton作用域的bean中时,为了能够让singleton作用域的bean能够正常获取到request作用域的bean,这是我们需要通过aop为request作用域的bean配置代理。同样的,session作用域的也是如此。

<bean id="car" class="com.taobaobao.Car"  scope="request"> // scope设置bean的作用域
      < aop:scoped-proxy />     //创建代理
</bean>

2.3.4. FactoryBean 工厂类

   使用配置文件配置Bean,在一些情况下实例化Bean的过程比较复杂,会提供大量的配置信息,配置方式的灵活性是受限的,这是采用编码的方式可能会的得到一个简单的方案。Spring为此提供了一个org.springframework.beans.factory.FactoryBean工厂类接口定制实例化bean的逻辑。(简单的讲就是实现这个接口之后,可以自己编写自己想要实例化一些bean的过程,通过自己的方式去实现bean的实例化。)

  从spring3.0开始该接口支持泛型,接口中定义了三个方法:
  1. T getObject();返回由FactoryBean创建的实例,如果isSingleton();方法返回的是true,实例化的bean将存放到spring的单实例缓存池中。
  2. boolean isSingleton();确定由FactoryBean创建的实例的作用范围是singleton 还是prototype的。返回true 作用域则是singleton。
  3. Class<?> getObjectType();返回创建bean的类型。

举个例子,我们想通过逗号分隔属性值的方式一次性为Car的所有属性赋值,如下代码

public class CarFactoryBean implements FactoryBean<Car> {

private String carinfo;
//省略get set 方法。
public boolean isSingleton(){
return false;
}

public Class <Car>  getObjectType(){
return Car.class;
}

public  Car getObject() throws  Exception{
  Car car = new Car();
  String[] arg = carinfo.split(",");
  car.setColor(arg[0]);
  car.setMaxSpeed(Integer.parseInt(arg[1]));
  car.setPrice(Integer.parseInt(arg[2]));
  return car;
 }
}

//配置文件
<bean id="car1" class="com.taobaobao.CarFactoryBean"  p:carinfo="红色,200,200000"/>

如果希望的到FactoryBean的实例,则需要在getBean方法的beanName前显示的添加 & 前缀,如需要获取CarFactoryBean的实例, getBean("&Car");

2.4. 基于注解的配置

2.4.1. 使用注解定义Bean

spring定义了四个注解来来对bean进行标注

@Component,

@Controller, 用于对Controller实现类进行标注。

@Service,用于对Service实现类进行标注

@Repository.用于对Dao实现类进行标注。

其实上面的后三个标注完全可以只是用第一个注解进行代替的,但是为什么没有这样做呢,这是因为我们要使用特定的注解标注特定的Bean。

要想使用标注进行配置Bean,我们还需要加一个配置。告诉spring我们这些注解是有用的,如果没有进行配置,那我们所写的注解则没有效用。spring 2.5 提供了context命名空间,他提供了扫描类包以应用注解定义Bean 的方式。

xmlns:context ="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-3.0.xsd"
<context:component-scan base-package="com.taobaobao"/>

这样配置会扫描taobaobao类包下所有的类,如果只需要扫描指定的类而非所有的类,那我可以设置resource-pattern属性来过滤出特定的类,例如:

<context :component-scan base-package="com.taobaobao resource-pattern="anno/*.class"/>

设置基包为com.taobaobao,resource-pattern的默认值是**/ *.class,即基包里所有的类,这里设置为anno/ *.class,则spring只会扫描anno子包中的类。

当然,在一些情况下单单设置resource-pattern属性是满足不了的过滤要求的,如实现了XxxService接口的类或者标注了特定标注的类等等。这些情况则可以通过的过滤子元素来实现。

<context :component-scan base-package="com.taobaobao">
  < context:include-filter  type=""  expression="" />
  < context: exclude-filter type="" expression=""/>
</context :component-scan>

< context:include-filter >表示要包含的目标类。< context: exclude-filter> 表示要排除在外的目标类。

2.4.2. 自动装配Bean

  使用 @Autowired 进行自动装配。@Autowired 默认按照类型进行匹配的。在容器当中寻找相匹配的Bean,当有且仅有一个 Bean 时,spring将其自动注入到标注 @Autowired 的变量中。如果没有找到相匹配的Bean 则抛出 NoSunchBeanDefinitionException 异常.如果希望找不到Bean时不要抛出异常,那么可以使用@Autowired(required=false) 进行标注。默认 required=true 的。

使用 @Autowired 对集合类进行标注。spring如果发现标注的变量是一个集合类,则他会将匹配集合元素的所有Bean全部注入进来。

   使用 @scope 注解 配置 Bean 的作用范围 即作用域。通过注解 @PostConstruct 标注初始化执行的方法,通过注解 @PreDestroy 标注容器销毁时执行的方法。

2.5. 基于java类的配置

使用基于java类的配置必须保证将spring aop 和 CGLIB 类包加载到类路径下。

spring 几种bean配置方法的对比

三 Spring容器技术内幕

3.1. 内部工作机制

spring 启动时执行的各项操作

  1. ResourceLoader 从存储介质中加载spring的配置信息,并使用Resource来表示这个配置资源。
    https://blog.csdn.net/shadow_zed/article/details/72540927 (对于 ResourceLoader 详细讲解)

  2. BeanDefinitionReader 读取 Resource 所指向的配置文件资源,然后解析配置文件,配置文件中的每个解析成一个BeanDefinition对象,并保存到BeanDefinitionRegistry中。

  3. 容器扫描BeanDefinitionRegistry 中的BeanDefinition, 使用java反射机制自动识别出Bean 工厂后处理器(实现了BeanFactoryPostProcessor 接口)的Bean , 然后调用这些Bean工厂后处理器对BeanDefinitionRegistry 中的 BeanDefinition 进行加工处理。主要完成以下两项工作:
    1) 对使用占位符的 元素标签进行解析,得到最终的配置值,这意味对一些半成品式的BeanDefinntion 对象进行加工处理并得到成品的BeanDefinitionRegistry对象。
    2)对BeanDefinitionRegistry 中的BeanDefinition 进行扫描,通过java反射机制找出所有属性编辑器的Bean(实现了java.beans.PropertyEditor 接口的Bean),并自动将他们注册到Spring容器的属性编辑器注册表中(PropertyEditorRegistry)。

  4. Spring容器从BeanDefinitionRegistry 中取出加工后的BeanDefinition ,并调用InstantiationStrategy 着手进行Bean实例化的工作。

  5. 实例化Bean时, Spring 容器使用BeanWrapper 对Bean 进行封装,BeanWrapper 提供了很多以java反射机制操作Bean的方法,它将结合该Bean的BeanDefinition 以及容器中属性编辑器,完成Bean 属性的设置工作。

  6. 利用容器中注册的Bean后处理器(实现BeanPostProcessor 接口的Bean) 对已经完成属性设置工作的Bean进行后续的加工,直接装配出一个准备就绪的Bean。

3.2. Spring 组件介绍

3.2.1. BeanDefinition

   org.springframework.beans.factory.config.Beandefinition 是配置文件<bean> 元素标签在容器中内部表示形式。<bean>元素标签拥有的class, scope , lazy-init 等配置属性,BeanDefinition 则提供了相应的beanClass, scope, lazyinit属性。BeanDefinition 就像是<bean> 的镜中人,两者是一一对应的。

  一般情况下 BeanDefinition 只有在容器启动的时候加载并解析,除非容器重启或者刷新,否则这些信息是不会变化的。

创建BeanDefinition 主要包括两个步骤:

1) 解析配置文件时获得 BeanDefinition 对象,当然可能是半成品对象,因为配置文件中我们可能使用占位符引用外部属性文件的属性。占位符在这一步还没有被解析出来。

2)利用容器中注册的BeanFactoryPostProcessor 对半成品的BeanDefinition 进行加工处理,将占位符解析成最终的实际值,这样半成品的BeanDefinition 就成为了 成品的 BeanDefinition。

3.2.2. InstantiationStrategy

    org.springframework.beans.factory.support.InstantiationStrategy  负责根据 BeanDefinition 对象创建一个Bean 实例。SimpleInstantiationStrategy 策略是最常用的实现类,该策略利用Bean实现类的默认构造函数、带参构造函数 或 工厂方法创建Bean实例。

 InstantiationStrategy 仅负责实例化 Bean 操作, 相当于执行java语言new 的功能,它并不会参与Bean属性的设置工作。

3.2.3. BeanWrapper

 org.springframework.beans.BeanWrapper 是spring框架中重要的组件类。BeanWrapper 相当于一个代理器,spring通过BeanWrapper 完成Bean属性的填充工作。在Bean被实例被InstantiationStrategy 创建出来之后,容器主控程序将bean实例通过BeanWrapper 包装起来。这是通过调用BeanWrapper#setWrappedIndtance(Object Obj); 方法完成的。BeanWrapper 有两个顶级接口 PropertyAccessor 和 PropertyEditorRegistry,PropertyAccessor 接口定义了各种访问Bean属性的方法,如setPropertyValue(String, Object), setPropertyValues(PropertyValues pvs)等。而PropertyEditorRegistry 是属性编辑器的注册表。

3.3. 属性编辑器

   在配置文件中 bean的各种属性信息都是通过字面量以字符串的形式表示的,那BeanWrapper 要为Bean填充各种类型的属性值是怎样将字符串形式的字面量转换成各种Bean所对应的int,double 等内部类型呢,这里面肯定存在一个类型转换器在起作用,这个转换器其实就是属性编辑器(类型转换器)。

  任何实现了 java.beans.PropertyEditor 接口的都是属性编辑器。

3.3.1. JavaBean 编辑器

   javaBean 规范通过java.beans.PropertyEditor 定义了设置JavaBean属性的方法,通过BeanInfo 描述了JavaBean 那些属性是可制定的,此外还描述了可定制属性与 PropertyEditor 的对应关系。BeanInfo 与 JavaBean 之间的对应关系,通过两者之间的规范的命名确立:对应的BeanInfo 采用如下的命名规范:<Bean>BeanInfo,如ChartBean 对应的BeanInfo 为ChartBeanBeanInfo。

 JavaBean 规范提供了一个管理默认属性编辑器的管理器: PropertyEditorManager , 该管理器内保存着一些常见的属性编辑器,如果某个JavaBean的常见类型属性没有通过BeanInfo 显示指定属性编辑器,IDE 将自动使用PropertyEditorManager 中注册的对应默认属性编辑器。

3.3.2. propertyEditor

PropertyEditor 是属性编辑器接口,它规定了将外部设置值转换为内部 JavaBean 属性值的转换接口方法。PropertyEditorManager 主要的接口方法说明如下:

1) Object getValue(); 返回属性的当前值。基本类型被封装成对应的封装类实例。

2)void setValue(Object new Value); 设置属性的值,基本类型以封装传入。

3)String getAsText();将属性对象用一个字符串表示,以便外部的属性编辑器能以可视化的方式显示。缺省返回null,表示该属性不能以字符串表示。

4)void setAsText(String text); 用一个字符串去更新属性的内部值,这个字符串一般从外部属性编辑器传入。

5)String[] getTags(); 返回表示有效属性值的字符串数组,以便属性编辑器能以下拉框的方式显示出来。缺省返回null 便是属性没有匹配的字符值有限集合。

6)String getJavaInitializationString(); 为属性提供一个表示初始值的字符串,属性编辑器以此值作为属性的默认值。

可以看出PropertyEditorManager 接口方法是内部属性和外部设置值的沟通桥梁。 Java为PropertyEditorManager 提供了一个方便类 PropertyEditorSupport,该类实现了propertyEditor 接口 并提供了默认的实现。

3.3.2. BeanInfo

 BeanInfo 主要描述了JavaBean哪些属性可以编辑以及对应的属性编辑器,每一个属性对应一个属性描述器 PropertyDescriptor。PropertyDescriptor的构造函数有两个入参 PropertyDescriptor(String propertyName, Class beanClass) 其中 propertyName 为属性名,而 beanClass 为JavaBean 对应的Class。此外,PropertyDescriptor 还有一个 setPropertyEditorClass(Class propertyEditorClass);方法,为JavaBean提供属性编辑器。BeanInfo 接口最重要的方法就是 PropertyDescriptor[] getPropertyDescriptor(); 返回JavaBean 的属性描述器数组。

3.3.3. 自定义属性编辑器

   Spring大部分默认属性编辑器都是直接扩展于java.beans.PropertyEditorSupport类,用户也可以通过扩展PropertyEditorSupport实现自己的属性编辑器,Spring 环境下使用属性编辑器的功能非常单一:仅需要将配置文件中字面量值转换为属性类型的对象即可,并不需要提供UI界面,因此仅需要简单覆盖PropertyEditorSupport 的setAsText() 方法就可以了。

举个例子

public class Car{
  private String name;
  private int maxSpeed;
  private String brand;
//省略get set 方法
}

public class Boss{
  private Car Car;
  private String name;
//省略get set 方法
}

Boss类拥有两个属性 name 和 Car , Spring拥有String类型的默认属性编辑器,所以我们只需要为 Car 类型 自定义 一个属性编辑器。

Car类型的属性编辑器

public class CarEditor extends PropertyEditorSupport{
public void setAsText(String text){
  if(text == null  || text.indexOf("," == -1)){
     throw new lllegalArgumentException("设置的字符串格式不正确");
  }
  String [] infos = text.split(",");
  Car car = new Car();
  car.setBrand(infos[0]);
  car.setMaxSpeed(infos[1]);
  setValue(car);
 }
}

注册自定义属性编辑器

<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
     <property name="customEditor">
           <map>
            <entry key="com.taobaobao.Car">
                    <bean class="com.taobaobao.CarEditor"/>
             </entry>
         </map>
    </property>
</bean>
<bean id="boss" class="com.taobaobao.Boss">
     <property name="name" value="john"/>
     <property name="car" value="红旗CA72,200,20000.00"/>
</bean>

原来我们通过car-ref 为boss类配置Car 类型的属性值,而现在我们直接通过value 为car属性提供配置,BeanWrapper 在为boss 设置 car 属性时, 它将检测自定义属性编辑器注册表, 发现Car 属性类型拥有对应的属性编辑器 CarEditor 时,它就会利用CarEditor 将 “红旗CA72,200,20000.00” 转换成Car 对象。

注意:按照JavaBean的规范,JavaBeans的基础设施会在JavaBean的相同类包下查找是否存在Editor 的类,如果存在,自动使用Editor 作为 该JavaBean 的 PropertyEditor。如com.taobaobao.UserEditor 会自动成为com.taobaobao.User 对应的 PropertyEditor 。 Spring也支持这个规范,如果采用了这种规约命名PropertyEditor, 就无须显式的在 CustomEditorConfigurer 中注册了。Spring将自动查找并注册PropertyEditor 。

3.4. 使用外部属性文件

3.4.1. 使用属性文件

     Spring 为我们提供了一个 PropertyPlaceholderConfigurer ,它能够使Bean在配置时引用外部属性文件。PropertyPlaceholderConfigurer 实现了 BeanFactoryPostProcessorBean 接口, 因此也是一个Bean工厂后处理器。

之前我们的配置是

< bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" 
   destory-method="close" 
   p:driverClassName = "com.mysql.jdbc.Driver" 
   p:url="jdbc:mysql://localhost:3306/sampleb"  
   p:username="root"  p:password="12345" />

现在在实际的应用中的最佳实战,可以将这些需要调整的配置信息抽取到一个配置文件中。这里,我们使用一个名为jdbc.properties 的配置文件:

driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/sampleb
username=root
password=12345

属性文件可以定义多个属性,每个属性都由一个属性名和一个属性值组成,两者用“=”隔开。下面通过PropertyPlaceholderConfigurer 引入jdbc.properties 属性文件,调整数据源Bean的配置。

<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfiguer"                     p:location="classpath:com/taobaobao/placeholder/jdbc.properties"  
    p:fileEncoding="utf-8"/>
<bean id="dataSource" class="com.apache.commons.dbcp.BasicDataSource" destory-method="close"         p:driverClassName="${driverClassName}"   
  p:url="${url}"    
  p:username="${username}"
  p:password="${password}"/>

3.4.2. 属性文件中属性的相互引用

jdbc.properties 的配置文件:

dbname=sampleb
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/${dbname}
username=root
password=12345

3.4.3. 引用Bean的属性值

public class Car{

  private String name;
  private int maxSpeed;
  private String brand;
//省略get set 方法
}

<bean id="car" class="com.taobaobao.Car" />
<bean id="boss" class="com.taobaobao.Boss"
    p:name = "#{car.name}"
    p:maxSpeed = "#{car.maxSpeed }"/>

3.5. Spring容器事件

3.5.1. Spring 事件类 结构

 ApplicationEvent 的唯一构造函数 是 ApplicationEvent(Object source), 通过指定source指定事件源,它的两个子类分别是:

1) ApplicationContextEvent :容器事件,拥有四个子类 分别表示 容器 启动,刷新,停止 以及 关闭事件。

2) RequestHandleEvent : 这是一个与Web 应用相关的事件,当一个HTTP 请求被处理后,产生事件。只有在web.xml

中定义了DispatcherServlet 时才会产生该事件。拥有两个子类,分别代表Servlet 和 Portlet 的请求事件。

  当然我们也可以根据自己的需要扩展ApplicationEvent 定义自己的事件,完成其他特殊的需求。

3.5.2. Spring 事件监听器

Spring 只有一个事件监听器接口 ApplicationListener 。而 ApplicationListener 接口只定义了一个方法 onApplicationEvent(E event); 该方法接受 ApplicationEvent 事件对象,在该方法中编写事件响应处理逻辑。Spring 3.0新增 SmartApplicationListener 接口,它定义了两个方法:

1) boolean supportEventType(Class<? extends ApplicationEvent> eventType); 指定监听器支持哪种类型的容器事件。即它只对该类型的事件作出响应。

2) boolean supportSoourceType(Class<?> sourceType); 该方法指定监听器仅对何种事件源作出响应。

注意:要想拥有事件发布能力,则必须实现 ApplicationContextAware 接口,调用

publishEvent( ApplicationContextEvent E);方法发布事件。

public class MailSendEvent extends ApplicationContextEvent{
    private String to;
    
    public MailSendEvent(ApplicationContext source,String to){
        super(source);
        this.to = to;
    }
    public String getTo(){
        return this.to
    }
}

public class MailSendListener implements ApplicationListener<MailSendEvent>{
    public void onApplicationEvent(MailSendEvent event){
        MailSendEvent mse = (MailSendEvent)event;
        System.out.println("MailSendListener:向" + mes.getTo() + "发送完一封邮件");
    }
}

public class MailSender implements ApplicationContextAware{
    private ApplicationContext ctx;
    
    public void setApplicationContext(ApplicationContext ctx){
        this.ctx = ctx;
    }
    
    public void sendMail(String to){
        System.out.println("MailSender 发送了一封邮件");
        MailSendEvent mse = new MailSendEvent(this.ctx,to);
        ctx.publishEvent(mse);
    }
}

//配置
<bean class="com.taobaobao.MailSendListener"/>
<bean id="mailSender"class="com.taobaobao.MailSender"/>

测试

ApplicationCOntext ctx = new ClassPathXmlApplicationContext("beans.xml");
MailSender mailSender = (MilSender)ctx.getBean("mailSender");
mailSender.sendMail("香港");

//运行结果
MailSender 发送了一封邮件
MailSendListener:向 香港 发送完一封邮件
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值