zoukankan      html  css  js  c++  java
  • 深入了解Spring中的容器

    1.创建Bean的3种方式

    1.1使用构造器创建bean实例

    这是最常见的方式,如果不采用构造注入,bean类需要有默认构造函数。如果采用构造注入,则需要配置xml文件的<constructor-arg>

    1.2使用静态工厂方法创建bean

    最典型的工厂方法如

     1 package spi;
     2 
     3 public class PersonFactory {
     4     public static Person getPerson(String arg) {
     5         if (arg.equalsIgnoreCase("Chinese")) {
     6             return new Chinese();
     7         } else {
     8             return new American();
     9         }
    10     }
    11 }

     如果在Spring容器中配置一个bean,bean的实例希望由上的静态工厂方法反回,则可以在bean中使用 factory-method来指定工厂方法,并用<constructor-arg>指定参数。

    1 <bean id="chinese" class="spi.PersonFactory" factory-method="getPerson">
    2   <constructor-arg value="chinese" />
    3 </bean>

    1.3实例化工厂方法创建bean

    这与上面的使用静态工厂方法创建bean高度相似,区别是这里是需要先创建工厂的实例。工厂方法如下

     1 package spi;
     2 
     3 public class PersonFactory {
     4     public Person getPerson(String arg) {
     5         if (arg.equalsIgnoreCase("Chinese")) {
     6             return new Chinese();
     7         } else {
     8             return new American();
     9         }
    10     }
    11 }

    在xml配置中,除了使用factory-method来指定bean的实例化工厂方法外,还需要使用factory-bean指定工厂实例的bean

    1 <bean id="chinese" class="spi.PersonFactory" factory-method="getPerson" factory-bean="personFactory">
    2   <constructor-arg value="chinese" />
    3 </bean>
    4 <bean id="personFactory" class="spi.PersonFactory" />

    2.bean的继承

    可以在Spring中使用abstract属性将一个bean定义一个模板配置,这叫做一个抽象bean。抽象bean不能被实例化,只是为了降低配置文件的冗余而存在的,只能由子bean继承,子类则使用parent属性指定父类bean。下面是一个例子,

    1     <bean id="personTemplate" abstract="true">
    2         <property name="name" value="zhangsan" />
    3         <property name="axe" value="stoneAxe" />
    4     </bean>
    5     
    6     <bean id="chinesePerson" parent="personTemplate">
    7         <property name="axe" value="steelAxe" />
    8     </bean>

    这个例子中,子类bean chinesePerson将会从父类继承name属性,但是会覆盖父类的axe属性。

    注意的是并非父类所有属性子类都能继承,depends-on, autowire, singleton, scope, lazy-ini 这些属性只能从子类本身获取或采用默认值。

    并且Spring容器bean的继承可以在不同类型的bean之间存在。

    3.工厂bean: FactoryBean接口

    Spring容器会检查所有bean,如果发现某个bean实现了FactoryBean接口,就会调用接口的getObject(),其返回值才会作为真正的bean。

    开发人员可以重写getObject()方法供Spring调用,例如下面,

     1 package spi;
     2 
     3 import java.lang.reflect.Field;
     4 
     5 import org.springframework.beans.factory.FactoryBean;
     6 
     7 public class GetField implements FactoryBean<Object>{
     8     private String targetClass;
     9     private String targetField;
    10     
    11     public String getTargetClass() {
    12         return targetClass;
    13     }
    14 
    15     public void setTargetClass(String targetClass) {
    16         this.targetClass = targetClass;
    17     }
    18 
    19     public String getTargetField() {
    20         return targetField;
    21     }
    22 
    23     public void setTargetField(String targetField) {
    24         this.targetField = targetField;
    25     }
    26 
    27     @Override
    28     public Object getObject() throws Exception {
    29         // TODO Auto-generated method stub
    30         Class<?> clazz = Class.forName(targetClass);
    31         Field field = clazz.getField(targetField);
    32         return field.get(null);
    33     }
    34 
    35     @Override
    36     public Class<?> getObjectType() {
    37         // TODO Auto-generated method stub
    38         return Object.class;
    39     }
    40 
    41     @Override
    42     public boolean isSingleton() {
    43         // TODO Auto-generated method stub
    44         return false;
    45     }
    46 
    47 }

    这个类实现了FactoryBean接口,可以返回任何我们需要的对象,功能非常强大,在Spring中可以做如下配置,

    1     <bean id="getField" class="spi.GetField">
    2         <property name="targetClass" value="spi.Chinese" />
    3         <property name="targetField" value="axe" />
    4     </bean>

    测试代码如下,

    1     public static void test6() {
    2         ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
    3         System.out.println(ctx.getBean("getField"));
    4     }

    程序输出:North

    Spring提供的工厂Bean,大多以FactoryBean结尾,用于生产指定类型的bean,工厂bean是Spring的重要工具之一。

    4.获取Bean本身id

    就像Bean希望获取所在的ApplicationContext实例一样,有时候Bean还希望获取XML中配置的id名称,这种情况需要bean实现BeanNameAware接口,过程与实现ApplicationContextAware类似。并且实现BeanNameAware接口的setBeanName()方法供Spring调用,例如下面,

    1 public class Chinese implements Person, BeanNameAware {
    2     private String beanName;
    3     @Override
    4     public void setBeanName(String arg0) {
    5         this.beanName = beanName;
    6         
    7     }
    8 ...

    这样容器创建bean时候就会调用这个setBeanName()方法,bean就能获取自己的id名称了。例如还可以在Chinese类中添加下面的方法,

    1     public void info(){
    2         System.out.println("我在XML中的id名称为:"+beanName);
    3     }

    5.强制初始化bean 

    某些情况下,容器中的依赖并不是非常直接,初始化bean时候,如果依赖的bean尚未初始化,有可能会出错,这种情况下我们希望先初始化依赖的bean。

    我们可以使用depebds-on属性来强制容器先初始化依赖额bean,例如

    1 <bean id="chinese" class="spi.Chinese" depends-on="steelAxe" />
    2 <bean id="steelAxe" class="spi.SteelAxe" />

    6.容器中Bean的生命周期

    对于singleton的bean,Spring可以精确控制生命周期,因此可以在bean的各个阶段指定各种行为。

    例如在bean依赖注入之后,或者销毁之前,可以插入指定动作。

    6.1依赖关系注入之后的行为

    有两种方式可以在Bean属性设置成功之后执行特定行为。

    • 一是使用init-method属性

    这种方式要求在bean中定义一个回调方法供Spring调用,并在XML中配置init-method属性,例如

    1 <bean id="chinese" class="spi.Chinese" init-method="init">

    只要在bean中实现init()方法,就能在bean被设置完属性之后调用,执行特定方法。

    • 二是让bean实现InitialializingBean接口,并实现接口的 afterPropertiesSet()方法

    这种方式不要求使用init-method属性,但是这对bean类具有侵入性,不推荐。

    6.2Bean销毁之前的行为

    要让Bean被销毁之前执行特定行为,也可以类似上面那样用两种方式实现,

    一是使用destroy-method属性

    二是实现DisposableBean接口,并实现destroy()方法

    另外,对于非web应用,如果希望在关闭应用前先关闭容器实例(即ApplicationContext实例),则可以先在JVM中注册一个钩子,程序退出时就会执行回调函数,优雅地关闭容器实例,例如,

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

    6.3协调作用域不同步的Bean

    如果在一个singleton类型的bean中注入一个prototype类型的bean时候,会发现有个问题,在整个应用程序生命周期,singleton bean只会被初始化一次,然而prototype bean每次都会重新创建并初始化,那么意味着singleton中的依赖注入并不是最新的。

    要解决这个问题,Spring使用“方法注入”,具体方式为:

    将调用bean(通常是singleton)定义为抽象类,定义一个抽象方法,用来返回一个对象,这个对象将用来对前面的bean注入值

    在调用bean中使用 <lookup-method>子元素替代<property>元素,可以使得每次调用bean时候,都会进行一次注入。

    bean定义如下

    1 class abstract class Chinese implements Person {
    2     private Dog dog;
    3     public abstract Dog getDog{}
    4     ...
    5 }

    XML配置

    1 <bean id="chinese" class="spi.Chinese">
    2     <lookup-method name="getDog" bean="gunDog" />
    3 </bean>
    4 <bean id="gunDog" class="spi.GunDog" scope="prototype" />

    对于上面的配置,Spring会做负责生成Chinese抽象类的子类,并重写getDog()方法,通常会这样写,

    1 public Dog getDog(){
    2     //获取ctx
    3     ...
    4     return ctx.getBean("gunDog");
    5 }

    这样就强行要求singleton类型的bean,每次获取Dog属性时候,都进行一次依赖注入。

     7.获取其他bean的属性值

    获取bean属性值,即调用getter方法。Spring提供一个工厂Bean : PropertyPathFactoryBean专门用来调用getter方法获取属性值。

    Spring的工厂Bean很强大,大多以*FactoryBean名称结尾,每一种工厂Bean可以生产特定产品,PropertyPathFactoryBean则是专门用来获取属性值的。

    要使用PropertyPathFactoryBean,直接将它配置成一个XML中的bean即可,同时需要配置bean的targetBeanName和propertyPath属性,下面是一个例子,

     1     <bean id="chinese" class="spi.Chinese" destroy-method="close">
     2         <property name="age" value="30" />
     3         <property name="son">
     4             <bean class="spi.Son">
     5                 <property name="age" value="11" />
     6             </bean>
     7         </property>
     8     </bean>
     9     <!-- 将指定Bean实例的getter方法返回值定义成son1 bean -->
    10     <bean id="son1" class="org.springframework.beans.factory.config.PropertyPathFactoryBean">
    11         <!-- 指定son1 bean来自哪个目标bean的getter方法 -->
    12         <property name="targetBeanName" value="chinese" />
    13         <!-- 指定目标bean的哪个getter方法, axe代表getSon() -->
    14         <property name="propertyPath" value="son" />
    15     </bean>

    targetBeanName属性用来指定将要获取哪个bean的属性,propertyPath用来指定要执行目标bean的哪个getter方法。

    上面涉及一个Son类代码如下,

     1 package spi;
     2 
     3 public class Son {
     4     private int age;
     5 
     6     public int getAge() {
     7         return age;
     8     }
     9 
    10     public void setAge(int age) {
    11         this.age = age;
    12     }
    13     public String toString(){
    14         return "Son[age="+this.age+"]";
    15     }
    16 }

    测试代码如下,

    1 System.out.println("系统获取son1: "+ctx.getBean("son1"));

    将会得到如下结果,

    系统获取son1: Son[age=11]

    获取到的bean属性值还可以用来注入另一个bean,下面是一个例子,

    1     <bean id="son2" class="spi.Son">
    2         <property name="age">
    3         <!-- 使用嵌套bean为setAge()方法指定参数 -->
    4         <!-- 以下是访问指定bean的getter方法的简单方式, 
    5         chinese.son.age代表获取chinese.getSon().getAge(), 也就是前面的son1-->
    6             <bean id="chinese.son.age" class="org.springframework.beans.factory.config.PropertyPathFactoryBean" />
    7         </property>
    8     </bean>

    测试代码,

    1 System.out.println("系统获取son2: "+ctx.getBean("son2"));

    执行结果,

    1 系统获取son2: Son[age=11]

    另外,propertyPath所指定的getter方法,也可以是一个复合方法(即属性的属性),像下面这样,

    1     <bean id="theAge" class="org.springframework.beans.factory.config.PropertyPathFactoryBean">
    2         <property name="targetBeanName" value="chinese" />
    3         <!-- 这里的son.age是一个复合属性,表示chinese.getSon().getAge() -->
    4         <property name="propertyPath" value="son.age" />
    5     </bean>

    测试代码,

    1 System.out.println("系统获取theAge: "+ctx.getBean("theAge"));

    执行结果,

    系统获取theAge: 11

    同时,目标bean也可以是嵌入的一个bean,不过这时候不是使用targetBeanName属性来指定,而是targetObject,例如这样,

    1     <bean id="theAge2" class="org.springframework.beans.factory.config.PropertyPathFactoryBean">
    2         <property name="targetObject">
    3             <bean class="spi.American">
    4                 <property name="age" value="30" />
    5             </bean>
    6         </property>
    7         <property name="propertyPath" value="age" />
    8     </bean>

    测试代码,

    1 System.out.println("系统获取theAge2: "+ctx.getBean("theAge2"));

    执行结果,

    1 系统获取theAge2: 30

    另外,对于获取其他bean属性的工厂Bean配置,还有一种简化配置如下,

    1 <util:property-path id="son1" path="person.son" />

    不过这种配置需要导入util:命名空间。

    8.获取Field值

    获取Field分为两种情况,第一种是静态Field,第二种是对象实例的Field值。

    Spring定义了专门的工厂Bean:FieldRetrievingFactoryBean来获取Field值。在XML中配置一个FieldRetrievingFactoryBean类型的bean即可。

    对于静态Field情形,需要在XML中设置FieldRetrievingFactoryBean bean的 targetClass(即目标类)和targetFiled(即目标字段)。

    对于实例对象的Field,需要指定targetObject(目标对象)和targetField(目标字段)。

    对于第二种情况,其实编程中意义不大,因为通常定义在类中的对象实例都是private的,FieldRetrievingFactoryBean无法直接获取,我们使用PropertyPathFactoryBean调用public的getter方法即可。

    对于第一种情况,典型用法如下,

    1     <bean id="theAge3" class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
    2         <property name="targetClass" value="java.sql.Connection" />
    3         <property name="targetField" value="TRANSACTION_SERIALIZABLE" />
    4     </bean>

    FieldRetrievingFactoryBean有个setStaticField()方法用来指定目标类和目标Field,因此上面的配置又可以简写为,

    1     <bean id="theAge4" class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
    2         <property name="staticField" value="java.sql.Connection.TRANSACTION_SERIALIZABLE" />
    3     </bean>

    FieldRetrievingFactoryBean返回的bean也可以用来注入其他bean,

    1     <bean id="son3" class="spi.Son">
    2         <property name="age">
    3             <bean id="java.sql.Connection.TRANSACTION_SERIALIZABLE"  class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean" />
    4         </property>
    5     </bean>

    上面三段配置的测试代码如下,

    1         System.out.println("系统获取theAge3: "+ctx.getBean("theAge3"));
    2         System.out.println("系统获取theAge4: "+ctx.getBean("theAge4"));
    3         System.out.println("系统获取son3: "+ctx.getBean("son3"));

    测试结果,

    1 系统获取theAge3: 8
    2 系统获取theAge4: 8
    3 系统获取son3: Son[age=8]

    FieldRetrievingFactoryBean也支持命名空间的简写,即 

    <util:constrant static-field="java.sql.Connection.TRANSACTION_SERIALIZABLE" />

    9.获取方法返回值

    Spring使用工厂Bean:MethodInvokingFactoryBean来执行bean中的普通方法,如果有返回值,将被赋值给xml中定义的bean

    执行普通方法也分两种情况,一种是静态类的普通方法,一种是普通对象的方法,

    对于两种情况,都将MethodInvokingFactoryBean定义为XML中一个普通bean,且设置相似的参数。

    targetClass/targetObject 用来设置目标类或者目标对象

    targetMethod用来设置目标方法

    arguments用来设置执行普通方法所需要的参数

    一个典型的用法如下,

     1     <!-- 配置 
     2     JFrame win = new JFrame("我的窗口");
     3     win.setVisible(true);
     4      -->
     5      <bean id="win" class="javax.swing.JFrame">
     6          <constructor-arg value="我的窗口" type="java.lang.String" />
     7          <property name="visible" value="true" />
     8      </bean>
     9     <!-- 配置
    10     JTextArea jta = JTextArea(7,40);
    11      -->
    12      <bean id="jta" class="javax.swing.JTextArea">
    13          <constructor-arg value="7" type="int" />
    14          <constructor-arg value="40" type="int" />
    15      </bean>
    16      
    17      <!-- 配置
    18      win.add(new JScrollPane(jta));
    19      使用MethodInvokingFactoryBean驱动Spring调用普通方法
    20       -->
    21       <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
    22           <property name="targetObject" ref="win" />
    23           <property name="targetMethod" value="add" />
    24           <property name="arguments">
    25               <list>
    26                   <bean class="javax.swing.JScrollPane">
    27                       <constructor-arg ref="jta" />
    28                   </bean>
    29               </list>
    30           </property>
    31       </bean>

    上面使用纯XML配置的方法,相当于执行了下面的java代码,

    1 JFrame win = new JFrame("我的窗口");
    2 win.setVisible(true);
    3 JTextArea jta = JTextArea(7,40);
    4 win.add(new JScrollPane(jta));

    通过上面的几点发现,Spring框架就是通过XML配置来执行java代码,因此几乎可以把所有java代码都放在Spring配置中管理,归纳一下:

    • 调用构造器创建对象,用<bean ..>元素
    • 调用setter方法,用<property..>元素
    • 调用getter方法,用工厂Bean  PropertyPathFactoryBean
    • 调用普通方法,用工厂Bean  MethodInvokingFactoryBean
    • 获取Field的值,用工厂Bean FieldRetrievingFactoryBean
  • 相关阅读:
    Microsoft Visual Studio 2010(vs10)安装与使用
    961专业课复习资料
    spring boot: 输出json
    spring boot: 热部署(一) run as – java application (spring-loader-1.2.4.RELEASE.jar)
    FastJson/spring boot: json输出方法二
    FastJson/spring boot: json输出
    FastJson中文乱码
    FastJSON 简单使用
    mytabits表关联一对一(多对一?)
    Model/ModelMap 和 ModelAndView 的区别使用
  • 原文地址:https://www.cnblogs.com/fysola/p/6363079.html
Copyright © 2011-2022 走看看