Spring框架 - IOC和DI

Spring概述

  1. Spring是一个开源框架

  2. Spring为简化企业级开发而生,使用Spring,JavaBean就可以实现很多以前要靠EJB才能实现的功能。同样的功能,在EJB中要通过繁琐的配置和复杂的代码才能够实现,而在Spring中却非常的优雅和简洁。

  3. Spring是一个IOC(DI)和AOP容器框架。

  4. Spring的优良特性

    非侵入式:基于Spring开发的应用中的对象可以不依赖于Spring的API

    依赖注入:DI——Dependency Injection,反转控制(IOC)最经典的实现。

    面向切面编程:Aspect Oriented Programming——AOP

    容器:Spring是一个容器,因为它包含并且管理应用对象的生命周期

    组件化:Spring实现了使用简单的组件配置组合成一个复杂的应用。在 Spring 中可以使用XML和Java注解组合这些对象。

  5. 一站式:在IOC和AOP的基础上可以整合各种企业应用的开源框架和优秀的第三方类库(实际上Spring 自身也提供了表述层的SpringMVC和持久层的Spring JDBC)。

  6. Spring模块

    Spring框架 - IOC和DI

安装Spring插件【了解】

  1. eclipse可以选中在线安装Spring Tool Suite 这个插件。Help ---> Eclipse Marketplce...之后搜索spring tool suite
  2. IDEA : File ——》Settings ——》Plugins ——》Spring Assistant。

搭建Spring运行时环境

  1. 加入JAR包

    ① Spring自身JAR包:spring-framework-4.0.0.RELEASE\libs目录下

    ​ spring-beans-4.0.0.RELEASE.jar

    ​ spring-context-4.0.0.RELEASE.jar

    ​ spring-core-4.0.0.RELEASE.jar

    ​ spring-expression-4.0.0.RELEASE.jar

    ② commons-logging-1.1.1.jar

  2. 在Spring Tool Suite工具中通过如下步骤创建Spring的配置文件

    ​ ① File->New->Spring Bean Configuration File

    ​ ② 为文件取名字 例如:applicationContext.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-4.0.xsd">
    
    	<!-- 配置bean 
    		  配置方式:  基于xml的方式 ,使用的全类名的方式.
    		 <bean>: 受Spring管理的一个javaBean对象. 
    		 	id:  <bean>的唯一标识. 在整个IOC容器中唯一不重复. 
    		 	class: 指定javaBean的全类名. 目的是通过反射创建对象。 
    		 		   Class cls = Class.forName("top.fulsun.spring.helloWorld.Person");
    		 		   Object obj = cls.newInstance();   必须提供无参数构造器. 
    		 <property>: 给对象的属性赋值
    		  	name: 指定属性名  , 指定set风格的属性名. 
    		  	value:指定属性值 	
    	-->
    	<bean id="person"  class="top.fulsun.spring.helloWorld.Person">
    		<property name="name2" value="HanMeiMei"></property>
    	</bean>
    	
    	<bean id="person1"  class="top.fulsun.spring.helloWorld.Person">
    		<property name="name2" value="LiLei"></property>
    	</bean>
    
    </beans>
    
    

HelloWorld

目标:使用Spring创建对象,为属性赋值

  1. 创建Student类

  2. 创建Spring配置文件

    <!-- 使用bean元素定义一个由IOC容器创建的对象 -->
    <!-- class属性指定用于创建bean的全类名 -->
    <!-- id属性指定用于引用bean实例的标识 -->
    <bean id="student" class="top.fulsun.helloworld.bean.Student">
        <!-- 使用property子元素为bean的属性赋值 -->
        <property name="studentId" value="1001"/>
        <property name="stuName" value="Tom2015"/>
        <property name="age" value="20"/>
    </bean>
    
    
  3. 测试:通过Spring的IOC容器创建Student类实例

    //1.创建IOC容器对象
    ApplicationContext iocContainer = 
        new ClassPathXmlApplicationContext("helloworld.xml");
    //2.根据id值获取bean实例对象
    Student student = (Student) iocContainer.getBean("student");
    //3.打印bean
    System.out.println(student);
    

IOC和DI

IOC(Inversion of Control):反转控制

在应用程序中的组件需要获取资源时,传统的方式是组件主动的从容器中获取所需要的资源,在这样的模式下开发人员往往需要知道在具体容器中特定资源的获取方式,增加了学习成本,同时降低了开发效率。

反转控制的思想完全颠覆了应用程序组件获取资源的传统方式:反转了资源的获取方向——改由容器主动的将资源推送给需要的组件,开发人员不需要知道容器是如何创建资源对象的,只需要提供接收资源的方式即可,极大的降低了学习成本,提高了开发的效率。这种行为也称为查找的被动形式。

DI(Dependency Injection):依赖注入

IOC的另一种表述方式:即组件以一些预先定义好的方式(例如:setter 方法)接受来自于容器的资源注入。相对于IOC而言,这种表述更直接。

IOC 描述的是一种思想,而DI 是对IOC思想的具体实现.

IOC容器在Spring中的实现

  1. 在通过IOC容器读取Bean的实例之前,需要先将IOC容器本身实例化。

  2. Spring提供了IOC容器的两种实现方式

    ① BeanFactory:IOC容器的基本实现,是Spring内部的基础设施,是面向Spring本身的,不是提供给开发人员使用的。

    ② ApplicationContext:BeanFactory的子接口,提供了更多高级特性。面向Spring的使用者,几乎所有场合都使用ApplicationContext而不是底层的BeanFactory。

ApplicationContext的主要实现类

  1. ClassPathXmlApplicationContext:对应类路径下的XML格式的配置文件
  2. FileSystemXmlApplicationContext:对应文件系统中的XML格式的配置文件
  3. 默认在初始化时就创建单例的bean,也可以通过配置的方式指定创建的Bean是多实例的。

ConfigurableApplicationContext

  1. 是ApplicationContext的子接口,包含一些扩展方法
  2. refresh()和close()让ApplicationContext具有启动、关闭和刷新上下文的能力。

WebApplicationContext

  • 专门为WEB应用而准备的,它允许从相对于WEB根目录的路径中完成初始化工作

通过类型获取bean

  1. 从IOC容器中获取bean时,除了通过id值获取,还可以通过bean的类型获取。但如果同一个类型的bean在XML文件中配置了多个,则获取时会抛出异常,所以同一个类型的bean在容器中必须是唯一的。

    HelloWorld helloWorld = cxt.getBean(HelloWorld.class);

  2. 或者可以使用另外一个重载的方法,同时指定bean的id值和类型

HelloWorld helloWorld = cxt.getBean(“helloWorld”,HelloWorld.class);

给bean的属性赋值

依赖注入的方式

通过bean的setXxx()方法赋值

Hello World中使用 <property name="studentId" value="1001"/>就是这种方式

通过bean的构造器赋值

  • Spring自动匹配合适的构造器

    <bean id="book" class="top.fulsun.spring.bean.Book" >        
        <constructor-arg value= "10010"/>        
        <constructor-arg value= "Book01"/>        
        <constructor-arg value= "Author01"/>        
        <constructor-arg value= "20.2"/>     
    </bean >  
    
  • 通过索引值指定参数位置

    <bean id="book" class="top.fulsun.spring.bean.Book" >
        <constructor-arg value= "10010" index ="0"/>
        <constructor-arg value= "Book01" index ="1"/>
        <constructor-arg value= "Author01" index ="2"/>
        <constructor-arg value= "20.2" index ="3"/>
    </bean >
    
  • 通过类型区分重载的构造器
    反射获取构造器的时候,有get和set方法的时候,构造器遍历从下到上,没有则相反。遍历过程中如果数据能够类型能够赋值就会使用当前的构造器

    <bean id="book" class="top.fulsun.spring.bean.Book" >
        <constructor-arg value= "10010" index ="0" type="java.lang.Integer" />
        <constructor-arg value= "Book01" index ="1" type="java.lang.String" />
        <constructor-arg value= "Author01" index ="2" type="java.lang.String" />
        <constructor-arg value= "20.2" index ="3" type="java.lang.Double" />
    </bean >
    
    package top.fulsun.spring.di;
    
    public class Car {
    	
    	private String brand ;   // 品牌
    	private String crop ;    // 厂商
    	private Double price ;   // 价格
    	
    	private Integer speed ;  // 速度
    	
    	public Car() {
    	}
    	
    	public Car(String brand, String crop, Double price) {
    		super();
    		this.brand = brand;
    		this.crop = crop;
    		this.price = price;
    	}
    	
    	public Car(String brand, String crop, Integer speed) {
    		super();
    		this.brand = brand;
    		this.crop = crop;
    		this.speed = speed;
    	}
    	
    	
    
    	// GetXXX 和 SetXXX 方法
    
    	@Override
    	public String toString() {
    		return "Car [brand=" + brand + ", crop=" + crop + ", price=" + price + ", speed=" + speed + "]";
    	}
    	
    	
    	
    }
    
    
    <bean id="car1" class="top.fulsun.spring.di.Car">
        <constructor-arg value="宝马" index="0"></constructor-arg>
        <constructor-arg value="450000" index="2" type="java.lang.Double"></constructor-arg>
        <constructor-arg value="华晨" index="1"></constructor-arg>
    </bean>
    
    <bean id="car2" class="top.fulsun.spring.di.Car">
        <constructor-arg value="奔驰"></constructor-arg>
        <constructor-arg value="梅赛德斯"></constructor-arg>
        <constructor-arg value="300"></constructor-arg>
    </bean>
    
    
    • 如果第一个bean不指定类型,那么speed 就会被赋值为450000

p名称空间

为了简化XML文件的配置,越来越多的XML文件采用属性而非子元素配置信息。Spring从2.5版本开始引入了一个新的p命名空间,可以通过<bean>元素属性的方式配置Bean 的属性。

使用p命名空间后,基于XML的配置方式将进一步简化。

xmlns:p="http://www.springframework.org/schema/p"
<bean id="studentSuper" class="top.fulsun.helloworld.bean.Student"
      p:studentId="2002" 
      p:stuName="Jerry2016" 
      p:age="18" />

赋值的类型

字面量

  1. 可以使用字符串表示的值,可以通过value属性或value子节点的方式指定
  2. 基本数据类型及其封装类、String等类型都可以采取字面值注入的方式
  3. 若字面值中包含特殊字符,可以使用<![CDATA[]]>把字面值包裹起来

null值

  • <property>的标签中填充<null/>
  • 不赋值就是默认值
<bean class="top.fulsun.spring.bean.Book" id="bookNull" >
    <property name= "bookId" value ="2000"/>
    <property name= "bookName">
        <null/>
    </property>
    <property name= "author" value ="nullAuthor"/>
    <property name= "price" value ="50"/>
</bean >

给bean的级联属性赋值

  • 如果当前bean下有个对象,可以用点的形势进行级联属性赋值
<bean id="action" class="top.fulsun.spring.ref.Action">
   <property name="service" ref="service"/>
   <!-- 设置级联属性(了解) -->
   <property name="service.dao.dataSource" value="DBCP"/>
</bean>

外部已声明的bean

使用ref指定声明的bean的id

<bean id="shop" class="top.fulsun.spring.bean.Shop" >
  <property name= "book" ref ="book"/>
</bean >

内部bean

当bean实例仅仅给一个特定的属性使用时,可以将其声明为内部bean。

内部bean声明直接包含在<property><constructor-arg>元素里,不需要设置任何id或name属性,就当前bean使用,内部bean不能使用在任何其他地方

<bean id="shop2" class="top.fulsun.spring.bean.Shop" >
    <property name= "book">
        <bean class= "top.fulsun.spring.bean.Book" >
            <property name= "bookId" value ="1000"/>
            <property name= "bookName" value="innerBook" />
            <property name= "author" value="innerAuthor" />
            <property name= "price" value ="50"/>
        </bean>
    </property>
</bean >

复杂属性注入

在Spring中可以通过一组内置的XML标签来配置集合属性,例如:<list><set><map>

数组和List

配置java.util.List类型的属性,需要指定<list>标签,在标签里包含一些元素。这些标签可以通过<value>指定简单的常量值,通过<ref>指定对其他Bean的引用。通过<bean>指定内置bean定义。通过<null/>指定空元素。甚至可以内嵌其他集合。

数组的定义和List一样,都使用<list>元素。

配置java.util.Set需要使用<set>标签,定义的方法与List一样。

package top.fulsun.spring.di;

import java.util.List;

public class PersonList {

    private String  name; 

    private List<Car> cars ;    //  Car [] cars;   Set<Car> cars ; 
}
<!-- List集合 -->
<bean id="personList" class="top.fulsun.spring.di.PersonList">
    <property name="name" value="Tom"></property>
    <property name="cars">
        <!-- 构造集合 -->
        <list>
            <bean /> 
            <ref bean="car"/>
            <ref bean="car1"/>
            <ref bean="car2"/>
        </list>

        <!-- <array></array> -->
        <!-- <set></set> -->
    </property>
</bean>

Map

Java.util.Map通过<map>标签定义,<map>标签里可以使用多个<entry>作为子标签。每个条目包含一个键和一个值。必须在<key>标签里定义键。因为键和值的类型没有限制,所以可以*地为它们指定<value><ref><bean><null/>元素。

可以将Map的键和值作为<entry>的属性定义:简单常量使用key和value来定义;bean引用通过key-ref和value-ref属性定义。

package top.fulsun.spring.di;

import java.util.Map;

public class PersonMap {

    private String name ; 

    private Map<String,Car> cars;
}
<!-- Map集合 -->
<bean id="personMap" class="top.fulsun.spring.di.PersonMap">
    <property name="name" value="Jerry"></property>
    <property name="cars" >
        <!-- 构造map -->
        <map>
            <entry key="AA"  value-ref="car"></entry>	 			
            <entry key="BB"  value-ref="car1"></entry>
            <entry key="CC"  value-ref="car2"></entry>
        </map>
    </property>
</bean>

集合类型的bean

如果只能将集合对象配置在某个bean内部,则这个集合的配置将不能重用。我们需要将集合bean的配置拿到外面,供其他bean引用。

配置集合类型的bean需要引入util名称空间

<!-- 集合Bean -->
<util:list id="listBean">
    <ref bean="car"/>
    <ref bean="car1"/>
    <ref bean="car2"/>
    <!-- <bean>
   </bean> -->
</util:list>

<bean id="personList" class="top.fulsun.spring.di.PersonList">
    <property name="name" value="Tom"></property>
    <property name="cars" ref="listBean">
    </property>
</bean>

Properties 注入

<!-- private Properties info -->
<property name="info">
  <props>
    <prop key="age">99</prop>
    <prop key="name">javaboy</prop>
  </props>
</property>

FactoryBean

Spring中有两种类型的bean,一种是普通bean,另一种是工厂bean,即FactoryBean。

工厂bean跟普通bean不同,其返回的对象不是指定类的一个实例,其返回的是该工厂bean的getObject方法所返回的对象。工厂bean必须实现org.springframework.beans.factory.FactoryBean接口。

package org.springframework.beans.factory;

import org.springframework.lang.Nullable;

public interface FactoryBean<T> {
    // 将创建好的bean返回给IOC
    @Nullable
    T getObject() throws Exception;

     // 返回bean类型
    @Nullable
    Class<?> getObjectType();

    //创建的bean是否单例
    default boolean isSingleton() {
        return true;
    }
}

Spring FactoryBean和BeanFactory 区别

  • BeanFactory 是ioc容器的底层实现接口,是ApplicationContext *接口spring不允许我们直接操作 BeanFactory bean工厂,所以为我们提供了ApplicationContext 这个接口 此接口集成BeanFactory 接口,ApplicationContext包含BeanFactory的所有功能,同时还进行更多的扩展。

  • FactoryBean 接口提供三个方法,用来创建对象,
    FactoryBean 具体返回的对象是由getObject 方法决定的。

package top.fulsun.spring.factorybean;

import org.springframework.beans.factory.FactoryBean;

import top.fulsun.spring.di.Car;

public class CarFactoryBean implements FactoryBean<Car> {
	/**
	 * 工厂bean具体创建的bean对象是由getObject方法来返回的. 
	 */
	@Override
	public Car getObject() throws Exception {
		return  new Car("五菱宏光", "五菱", 50000);
	}

	/**
	 *  返回具体的bean对象的类型
	 */
	@Override
	public Class<?> getObjectType() {
		return Car.class;
	}
	
	/**
	 * bean 可以是单例的   也可以是原型的(非单例): 后面讲bean的作用域再研究.
	 */
	@Override
	public boolean isSingleton() {
		return true;
	}

	
}

<bean id="car" class="top.fulsun.spring.factorybean.CarFactoryBean"></bean>
public class Main {

    public static void main(String[] args) {

        ApplicationContext ctx = 
            new ClassPathXmlApplicationContext("spring-factorybean.xml");

        Car car = ctx.getBean("car",Car.class);

        System.out.println(car);
    }
}	

bean的高级配置

配置信息的继承

背景

查看下面两个Employee的配置,其中city属性是重复的。

<bean id="address1"  abstract="true">
   <property name="city" value="BeiJing"></property>
   <property name="street" value="ChangAnJie"></property>
</bean>

<bean id="address2" class="top.fulsun.spring.relation.Address">
   <property name="city" value="BeiJing"></property> 
   <property name="street" value="WuDaoKou"></property>
</bean>

配置信息的继承

设置parent属性的值,Spring允许继承bean的配置,被继承的bean称为父bean。继承这个父bean的bean称为子bean,子bean从父bean中继承配置,包括bean的属性配置,子bean也可以覆盖从父bean继承过来的配置。

<bean id="address1" class="top.fulsun.spring.relation.Address">
    <property name="city" value="BeiJing"></property>
    <property name="street" value="ChangAnJie"></property>
</bean>

<!-- 以address1作为父bean,继承后可以省略公共属性值的配置 --> 
<bean id="address2" class="top.fulsun.spring.relation.Address"  parent="address1">
    <!-- <property name="city" value="BeiJing"></property> -->
    <property name="street" value="WuDaoKou"></property>
</bean>
package top.fulsun.spring.relation;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestRelation {

   @Test
   public void testExtends() {
       ApplicationContext ctx  = 
           new ClassPathXmlApplicationContext("spring-relation.xml");

       //		Address address1 = ctx.getBean("address1",Address.class);
       //		System.out.println("address1:" +address1);

       Address address2 = ctx.getBean("address2",Address.class);
       System.out.println("address2:" + address2) ;
   }
}

补充说明

  • 父bean可以作为配置模板,也可以作为bean实例。

  • 若只想把父bean作为模板,可以设置<bean>abstract属性为true,这样Spring将不会实例化这个bean,

  • 如果一个bean的class属性没有指定,则必须是抽象bean。

  • 并不是<bean>元素里的所有属性都会被继承。比如:autowire,abstract等。

  • 也可以忽略父bean的class属性,让子bean指定自己的类,而共享相同的属性配置。 但此时abstract必须设为true。

<bean id="address1"  abstract="true">
   <property name="city" value="BeiJing"></property>
   <property name="street" value="ChangAnJie"></property>
</bean>

<bean id="address2" class="top.fulsun.spring.relation.Address"  parent="address1">
   <!-- <property name="city" value="BeiJing"></property> -->
   <property name="street" value="WuDaoKou"></property>
</bean>

bean之间的依赖

有的时候创建一个bean的时候需要保证另外一个bean也被创建,这时我们称前面的bean对后面的bean有依赖。

例如:要求创建Employee对象的时候必须创建Department。这里需要注意的是依赖关系不等于引用关系,Employee即使依赖Department也可以不引用它。

<!-- 依赖关系 -->
<bean id="address3" class="top.fulsun.spring.relation.Address" 
      parent="address1"  depends-on="address4">
</bean>


<bean id="address4" class="top.fulsun.spring.relation.Address"></bean>
@Test
public void testDepends() {
    ApplicationContext ctx  = 
        new ClassPathXmlApplicationContext("spring-relation.xml");

	// 如果注释address4 运行就会报错
    Address address3 = ctx.getBean("address3",Address.class);
    System.out.println("address3:" + address3) ;
}

bean的作用域★

​ 在Spring中,可以在<bean>元素的scope属性里设置bean的作用域,以决定这个bean是单实例的还是多实例的。

​ 默认情况下,Spring只为每个在IOC容器里声明的bean创建唯一一个实例,整个IOC容器范围内都能共享该实例:所有后续的getBean()调用和bean引用都将返回这个唯一的bean实例。该作用域被称为singleton,它是所有bean的默认作用域。

Spring框架 - IOC和DI

  • 当bean的作用域为单例时,Spring会在IOC容器对象创建时就创建bean的对象实例。后续每次通过getBean()方法获取bean对象时,返回的都是同一个对象.
  • 而当bean的作用域为prototype时,在整个IOC容器中可有多个bean的对象。 在IOC容器对象被创建时, 不会创建原型的bean的对象,而是等到没次通过getBean()方法获取bean对象时,才会创建一个新的bean对象返回.
  • request: 一次请求对应一个bean对象
  • session: 一次会话对应一个bean对象
<bean id="car" class="top.fulsun.spring.scope.Car" scope="prototype">
    <property name="brand" value="奥迪"></property>
    <property name="price" value="400000"></property>
</bean>
public class Car {

    private String brand;

    private Double price;
    public Car() {
        System.out.println("===>1. 调用构造器创建bean对象 ");
    }

}

public class TestScope {

    @Test
    public void testScope() {
        //创建了IOC容器对象
        ApplicationContext ctx = 
            new ClassPathXmlApplicationContext("spring-scope.xml");

        Car car1 = ctx.getBean("car",Car.class);
        //		
        Car car2 = ctx.getBean("car",Car.class);
        //		
        System.out.println(car1 == car2);  // singleton: true || prototype:false
    }

}

===>1. 调用构造器创建bean对象 
===>1. 调用构造器创建bean对象 
false

bean的生命周期

  1. Spring IOC容器可以管理bean的生命周期,Spring允许在bean生命周期内特定的时间点执行指定的任务。

  2. Spring IOC容器对bean的生命周期进行管理的过程:

    ​ ① 通过构造器或工厂方法创建bean实例

    ​ ② 为bean的属性设置值和对其他bean的引用

    ​ ③ 调用bean的初始化方法

    ​ ④ bean可以使用了

    ​ ⑤ 当容器关闭时,调用bean的销毁方法

  3. 在配置bean时,通过init-methoddestroy-method 属性为bean指定初始化和销毁方法

  4. bean的后置处理器

    ​ ① bean后置处理器允许在调用初始化方法前后对bean进行额外的处理

    ​ ② bean后置处理器对IOC容器里的所有bean实例逐一处理,而非单一实例

    ​ 其典型应用是:检查bean属性的正确性或根据特定的标准更改bean的属性。

    ​ ③ bean后置处理器时需要实现接口:

    org.springframework.beans.factory.config.BeanPostProcessor

    ​ 在初始化方法被调用前后,Spring将把每个bean实例分别传递给上述接口的以下两个方法:

    postProcessBeforeInitialization(Object, String)

    postProcessAfterInitialization(Object, String)

  5. 添加bean后置处理器后bean的生命周期

    ​ ①通过构造器或工厂方法创建bean实例

    ​ ②为bean的属性设置值和对其他bean的引用

    ​ ③将bean实例传递给bean后置处理器的postProcessBeforeInitialization()方法

    ​ ④调用bean的初始化方法

    ​ ⑤将bean实例传递给bean后置处理器的postProcessAfterInitialization()方法

    ​ ⑥bean可以使用了

    ​ ⑦当容器关闭时调用bean的销毁方法

  • 实体类

    package top.fulsun.spring.lifecycle;
    
    public class Car {
    
        private String brand ; 
    
        private Double price ;
    
        public Car() {
            System.out.println("===>1. 调用构造器创建bean对象 ");
        }
        /**
    	 * 初始化方法
    	 * 需要通过 init-method来指定初始化方法
    	 */
        public void init() {
            System.out.println("===>3. 调用初始化方法");
        }
    
        /**
    	 * 销毁方法: IOC容器关闭, bean对象被销毁.
    	 */
        public void destroy() {
            System.out.println("===>5. 调用销毁方法");
        }
    
    
        //GetXXX 和 SetXXX 方法省略
    
        @Override
        public String toString() {
            return "Car [brand=" + brand + ", price=" + price + "]";
        } 
    
    
    }
    
    
  • bean的后置处理器 : 对IOC容器中所有的bean都起作用.

    package top.fulsun.spring.lifecycle;
    
    import org.springframework.beans.BeansException;
    import org.springframework.beans.factory.config.BeanPostProcessor;
    
    /**
     * bean的后置处理器 : 对IOC容器中所有的bean都起作用. 
     */
    public class MyBeanPostProcessor implements BeanPostProcessor {
    	
    	/**
    	 * 在bean的生命周期的初始化方法之前执行
    	 * Object bean: 正在被创建的bean对象. 
    	 * String beanName: bena对象的id值. 
    	 */
    	@Override
    	public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    		System.out.println("postProcessBeforeInitialization");
    		return bean;
    	}
    	/**
    	 * 在bean的生命周期的初始化方法之后执行
    	 */
    	@Override
    	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    		System.out.println("postProcessAfterInitialization");
    		return bean;
    	}
    
    }
    
    
  • 指定初始化和销毁方法

    <bean id="car" class="top.fulsun.spring.lifecycle.Car" 
          init-method="init"  destroy-method="destroy">
        <property name="brand" value="宝马"></property>
        <property name="price" value="450000"></property>
    </bean>
    
    <!-- 配置后置处理器 : Spring能自动识别是一个后置处理器 -->
    <bean class="top.fulsun.spring.lifecycle.MyBeanPostProcessor"></bean>
    
  • 测试

    @Test
    public void testLifeCycle() {
        ConfigurableApplicationContext ctx = 
            new ClassPathXmlApplicationContext("spring-lifecycle.xml");
        Car car = ctx.getBean("car",Car.class);
    
        System.out.println("===>4. 使用bean对象" + car);
        //关闭容器
    
        ctx.close();
    }
    
    
    ===>1. 调用构造器创建bean对象 
    ===>2. 调用set方法给对象的属性赋值
    postProcessBeforeInitialization
    ===>3. 调用初始化方法
    postProcessAfterInitialization
    ===>4. 使用bean对象Car [brand=宝马, price=450000.0]
    ===>5. 调用销毁方法
    

自动装配

自动装配的概述

  • 手动装配:以value或ref的方式明确指定属性值都是手动装配。

  • 自动装配:根据指定的装配规则,不需要明确指定,Spring自动将匹配的属性值注入bean中。

装配模式

  1. 根据类型自动装配:将类型匹配的bean作为属性注入到另一个bean中。若IOC容器中有多个与目标bean类型一致的bean,Spring将无法判定哪个bean最合适该属性,所以不能执行自动装配

  2. 根据名称自动装配:必须将目标bean的名称和属性名设置的完全相同

  3. 通过构造器自动装配:当bean中存在多个构造器时,此种自动装配方式将会很复杂。不推荐使用。

<!-- Person  : 演示自动装配 

   byName: 使用bean的属性名与IOC容器中<bean>的id值进行匹配. 匹配成功则装配成功. 

   byType: 使用bean的属性的类型与IOC容器中<bean>的class进行匹配。 如果唯一匹配则装配成功
                       如果匹配到多个兼容类型的bean。则跑出异常。
 -->
<bean id="person" class="top.fulsun.spring.autowire.Person" autowire="byType">
    <property name="name" value="Tom"></property>	
</bean>
@Test
public void testAutoWire() {

    ApplicationContext ctx = 
        new ClassPathXmlApplicationContext("spring-autowire.xml");

    Person person = ctx.getBean("person",Person.class);
    System.out.println(peson);
}

选用建议

相对于使用注解的方式实现的自动装配,在XML文档中进行的自动装配略显笨拙,在项目中更多的使用注解的方式实现。

通过注解配置bean

概述

​ 相对于XML方式而言,通过注解的方式配置bean更加简洁和优雅,而且和MVC组件化开发的理念十分契合,是开发中常用的使用方式。

使用注解标识组件

  1. 普通组件:@Component

​ 标识一个受Spring IOC容器管理的组件

  1. 持久化层组件:@Repository

    标识一个受Spring IOC容器管理的持久化层组件

  2. 业务逻辑层组件:@Service

    标识一个受Spring IOC容器管理的业务逻辑层组件

  3. 表述层控制器组件:@Controller

    标识一个受Spring IOC容器管理的表述层控制器组件

  4. 组件命名规则

    ​ ① 默认情况:使用组件的简单类名首字母小写后得到的字符串作为bean的id

    ​ ② 使用组件注解的value属性指定bean的id

注意:事实上Spring并没有能力识别一个组件到底是不是它所标记的类型,即使将@Respository注解用在一个表述层控制器组件上面也不会产生任何错误,所以 @Respository、@Service、@Controller这几个注解仅仅是为了让开发人员自己明确当前的组件扮演的角色。

扫描组件

组件被上述注解标识后还需要通过Spring进行扫描才能够侦测到。

  1. 指定被扫描的package

    <!-- 组件扫描:  扫描加了注解的类,并管理到IOC容器中 
      base-package: 基包. Spring会扫描指定包以及子包下所有的类,将带有注解的类管理到IOC容器中
     -->
    <context:component-scan base-package="top.fulsun.spring.annotation" use-default-filters="true">
    
  2. 详细说明

    • base-package属性指定一个需要扫描的基类包,Spring容器将会扫描这个基类包及其子包中的所有类。

    • 当需要扫描多个包时可以使用逗号分隔。

    • 如果仅希望扫描特定的类而非基包下的所有类,可使用resource-pattern属性过滤特定的类,示例:

      <context:component-scan 
                              base-package="top.fulsun.component" 
                              resource-pattern="autowire/*.class"/>
      
  3. 包含与排除

    • <context:include-filter>子节点表示要包含的目标类

    注意:通常需要与use-default-filters属性配合使用才能够达到“仅包含某些 组件”这样的效果。

    即:通过将use-default-filters属性设置为false禁用默认过滤器,然后扫描的就只是include-filter中的规则指定的组件了。

    • <context:exclude-filter>子节点表示要排除在外的目标类

    • component-scan下可以拥有若干个include-filter和exclude-filter子节点

    • 过滤表达式

      类别 示例 说明
      annotation top.fulsun.XxxAnnotation 过滤所有标注了XxxAnnotation的类。这个规则根据目标组件是否标注了指定类型的注解进行过滤。
      assignable top.fulsun.BaseXxx 过滤所有BaseXxx类的子类。这个规则根据目标组件是否是指定类型的子类的方式进行过滤。
      aspectj top.fulsun.*Service+ 所有类名是以Service结束的,或这样的类的子类。这个规则根据AspectJ表达式进行过滤。
      regex top\.fulsun\.anno\.* 所有top.fulsun.anno包下的类。这个规则根据正则表达式匹配到的类名进行过滤。
      custom top.fulsun.XxxTypeFilter 使用XxxTypeFilter类通过编码的方式自定义过滤规则。该类必须实现org.springframework.core.type.filter.TypeFilter接口
      <context:component-scan base-package="top.fulsun.spring.annotation" use-default-filters="true">
         <!-- 指定扫描  必须 设置use-default-filters="false"
          	 排除扫描   use-default-filters="true"   -->
         <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
         
         <context:include-filter type="assignable" expression="top.fulsun.spring.annotation.controller.UserController"/>
      
         <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
         <context:exclude-filter type="assignable" expression="top.fulsun.spring.annotation.controller.UserController"/> 
      
      </context:component-scan>
      
  4. JAR包

    必须在原有JAR包组合的基础上再导入一个:spring-aop-4.0.0.RELEASE.jar

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>4.0.0.RELEASE</version>
    </dependency>
    

组件装配

  1. 需求

    Controller组件中往往需要用到Service组件的实例,Service组件中往往需要用到 Repository组件的实例。Spring可以通过注解的方式帮我们实现属性的装配。

  2. 实现依据

    在指定要扫描的包时,<context:component-scan> 元素会自动注册一个bean的后置处理器:AutowiredAnnotationBeanPostProcessor的实例。该后置处理器可以自动装配标记 了@Autowired@Resource@Inject注解的属性。

  3. @Autowired注解

    默认优先ByType进行自动装配,当发现装配类型于spring容器中存在两个及以上实例时,会采用ByName的方式继续寻找对应的实例进行装配。

    • 根据类型实现自动装配。

    • 构造器、普通字段(即使是非public)、一切具有参数的方法都可以应用@Autowired 注解

    • 默认情况下,所有使用@Autowired注解的属性都需要被设置。当Spring找不到匹配的bean装配属性时,会抛出异常。

    • 若某一属性允许不被设置,可以设置@Autowired注解的required属性为 false

    • 默认情况下,当IOC容器里存在多个类型兼容的bean时

      • Spring会尝试匹配bean的id值是否与变量名相同,如果相同则进行装配。如果bean的id值不相同,通过类型的自动装配将无法工作。
      • 此时可以在@Qualifier注解里提供bean的名称。
      • Spring甚至允许在方法的形参上标注@Qualifiter注解以指定注入bean的名称。
    • @Autowired注解也可以应用在数组类型的属性上,此时Spring将会把所有匹配的bean进行自动装配。

    • @Autowired注解也可以应用在集合属性上,此时Spring读取该集合的类型信息,然后自动装配所有与之兼容的bean。

    • @Autowired注解用在java.util.Map上时,若该Map的键值为String,那么 Spring将自动装配与值类型兼容的bean作为值,并以bean的id值作为键。

  1. @Resource
    • @Resource注解要求提供一个bean名称的属性,若该属性为空,则自动采用标注处的变量或方法名作为bean的名称。
  2. @Inject
    • @Inject@Autowired注解一样也是按类型注入匹配的bean,但没有reqired属性。

例子

controller层

package top.fulsun.spring.annotation.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

import top.fulsun.spring.annotation.service.UserService;
import top.fulsun.spring.annotation.service.UserServiceImpl;


/**
 * @Cotroller 注解的作用: 
 * 相当于在xml文件中: 
 * <bean id="userController" class="top.fulsun.spring.annotation.controller.UserController">
 * 
 * 注解默认的id值 就是类名首字母小写, 可以在注解中手动指定id值:@Controller(value="id值"),可以简写为:@Controller("id值")
 * 
 *
 */
@Controller
public class UserController {

    @Autowired
    private UserService userService;


    public void  regist() {

        userService.handleAddUser();
    }

}

service层

public interface UserService {
	
	public void  handleAddUser();
}

//==============分隔线===============

package top.fulsun.spring.annotation.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

import top.fulsun.spring.annotation.dao.UserDao;

@Service
public class UserServiceImpl implements UserService {
	
	/**
	 *  @Autowired 完成bean属性的自动装配
	 *  
	 *  工作机制:  首先会使用byType的方式进行自动装配,如果能唯一匹配,则装配成功, 
	 *           如果匹配到多个兼容类型的bean, 还会尝试使用byName的方式进行唯一确定. 
	 *           如果能唯一确定,则装配成功,如果不能唯一确定,则装配失败,抛出异常. 
	 *  
	 *  默认情况下, 使用@Autowired标注的属性必须被装配,如果装配不了,也会抛出异常. 
	 *  可以使用required=false来设置不是必须要被装配. 
	 *  
	 *  如果匹配到多个兼容类型的bean,可以使用@Qualifier来进一步指定要装配的bean的id值 。
	 *  
	 *  @Autowired @Qualifier 注解即可在成员变量上,也可以加在对应的set方法上.. 
	 *  
	 */
	@Autowired(required=false)   // 自动装配  byName  byType ?????
	@Qualifier("userDaoJdbcImpl")
	private UserDao userDao ; 
	
	
	
	
//	@Autowired(required=false)   // 自动装配  byName  byType ?????
//	@Qualifier("userDaoJdbcImpl")
//	public void setUserDao(UserDao userDao) {
//		this.userDao = userDao;
//	}
	
	@Override
	public void handleAddUser() {
		//处理业务
		
		userDao.addUser();
	}
}

Dao层

package top.fulsun.spring.annotation.dao;

public interface UserDao {

	
	public void addUser();
}

//==============分隔线===============

package top.fulsun.spring.annotation.dao;

import org.springframework.stereotype.Repository;

@Repository
public class UserDaoJdbcImpl  implements UserDao{

	
	@Override
	public void addUser() {
		System.out.println("UserDao  Jdbc .....");
	}
}

//==============分隔线===============

package top.fulsun.spring.annotation.dao;

import org.springframework.stereotype.Repository;

@Repository
public class UserDaoMybatisImpl implements UserDao {
	
	
	@Override
	public void addUser() {
		System.out.println("UserDao  Mybatis .....");
	}
}

配置文件

<context:component-scan base-package="top.fulsun.spring.annotation" use-default-filters="true">
</context:component-scan>

测试

@Test
public void testAnnotation() {
    ApplicationContext ctx = 
        new ClassPathXmlApplicationContext("spring-annotation.xml");
    UserController  uc =  ctx.getBean("userController",UserController.class);
    System.out.println("userController:" + uc );

    //		UserService us = ctx.getBean("userServiceImpl",UserService.class);
    //		System.out.println("userService:" + us);
    //		
    //		UserDao  ud = ctx.getBean("userDaoJdbcImpl",UserDao.class);
    //		System.out.println("userDao: " + ud );

    uc.regist();

}
上一篇:前缀和与差分


下一篇:TypeError: A string has ambiguous TensorShape, please wrap in a list or convert to an int: hidden_di