[置顶] Spring框架-IOC控制反转

相关文章:Spring学习笔记-AOP


慢慢接触spring框架,将所学所用的东西记录下来,点点滴滴,肯定会有所收获。

之所以使用Spring框架,是因为框架可以大量简化程序的开发工作,更加高效稳定的开发大型程序。

IoC(Inversion of Control)控制反转,就是在一般情况下,获取类的实例必须要在程序中new这个类,现在不需要这样了,直接通过配置xml文件,或者对类进行注解,在一个叫做IOC容器的东西里,就存在了类的实例,我们称之为bean,通过在IOC即可获取到要得到的实例。

1、使用spring的先期准备,磨刀不误砍柴工。

在Eclipse上使用Spring框架,首先要安装spring在Eclipse上的插件springsource-tool-suite-3.7.1.RELEASE,为了Eclipse上使用spring。

 下载下来后,在安装软件里安装好。

下载spring的jar包spring-framework-4.2.2.RELEASE-dist.zip,里边lib里有使用spring时的jar包。

下载commons-logging包,spring的日志系统所依赖。

将这些5个jar包配置在项目中:

    [置顶] Spring框架-IOC控制反转_第1张图片

2、输出Hello World

创建一个HelloWorld类:

package com.cqupt.spring.beans;

public class Helloworld {

	private String name;

	public void setName(String name){
		this.name=name;
	}
	
	public void hello(){
		System.out.println("hello :"+name);
	}
}
***配置xml文件,在src下新建spring Bean Configration File 文件,在beans标签下新建bean标签
    <bean id="helloworld" class="com.cqupt.spring.beans.Helloworld">
        <property name="name" value="CQUPT"></property>
    </bean>

注意,使用property配置属性,类中必须要有无参构造函数,因为原理是先使用无参构造函数构造类对象,然后使用set方法配置属性

id代表这个bean的唯一标示,class是映射到的类,property是类里的成员属性配置,name的识别是根据setter函数识别的,如setName2(String ),那么name="name2",value就是给成员属性配置的值。

在main函数中使用spring

	public static void main(String[] args){
		
		//1、创建spring的IOC容器
		ApplicationContext ac= new ClassPathXmlApplicationContext("applicationContext.xml");
		//2、从IOC容器中获取Bean实例
		Helloworld helloworld = (Helloworld) ac.getBean("helloworld");
		helloworld.hello();
	}
注:在执行
ApplicationContext ac= new ClassPathXmlApplicationContext("applicationContext.xml");
这条语句时,其实已将创建了Helloworld的实例,也调用了Set方法。

3、构造注入

getBean有两种方式:根据xml中的id,一种是根据Helloworld.class,区别就是两个bean标签映射到同一类时,getBean(类.class)会出错。

Spring提供两种类型的IOC容器,BeanFactory和ApplicationContext,其中ApplicationContext是对于使用Spring的开发人员的,BeanFactory是Spring的底层,不经常使用。

ApplicationContext是一个接口,有两种IOC的实现类,ClassPathXmlApplicationContext(依据类路径)和FileSystemXmlApplicationContext(依据系统文件路径);

三种注入方式:属性注入,构造器注入,工厂方法注入(不推荐,很少用)

属性注入:依据Set方法,最常用的方式;

构造方法注入:constructor-arg标签

创建一个Car类,构造函数

package com.cqupt.spring.beans;

public class Car {



	private String brand;
	private String corp;
	private int price;
	private int maxSpeed;
	
	public Car(String brand, String corp, int price) {
		super();
		this.brand = brand;
		this.corp = corp;
		this.price = price;
	}

	@Override
	public String toString() {
		return "Car [brand=" + brand + ", corp=" + corp + ", price=" + price
				+ ", maxSpeed=" + maxSpeed + "]";
	}
	
	
}
xml配置
<bean id="car" class="com.cqupt.spring.beans.Car">
        <constructor-arg value="Audi" index="0"></constructor-arg>
        <constructor-arg value="Shanghai" index="1"></constructor-arg>
        <constructor-arg value="30000" index="2"></constructor-arg>
    </bean>
index可以不需要,但是写出来时,一定要注意与第几个value值的匹配,否则出错

main函数中使用:

	public static void main(String[] args){
		
		//1、创建spring的IOC容器
		ApplicationContext ac= new ClassPathXmlApplicationContext("applicationContext.xml");
		Car car = (Car) ac.getBean("car");
		System.out.println(car);
	}
注:当出现重载构造函数时,可用type属性进行区分,一个构造函数对应一个bean标签,一个参数对应一个constructor-arg标签

    <bean id="car" class="com.cqupt.spring.beans.Car">
        <constructor-arg value="Audi" index="0"></constructor-arg>
        <constructor-arg value="Shanghai" index="1"></constructor-arg>
        <constructor-arg value="30000"  index="2" type="double"></constructor-arg>
    </bean>
    <bean id="car2" class="com.cqupt.spring.beans.Car">
        <constructor-arg value="Boma" index="0" type="java.lang.String"></constructor-arg>
        <constructor-arg value="Beijing" index="1" type="java.lang.String"></constructor-arg>
        <constructor-arg value="500" index="2" type="int"></constructor-arg>
    </bean>

4、配置细节

特殊字符配置属性

xml中使用特殊字符配置属性时,需用到<![CDATA[string]]>,并且使用子节点标注属性
<constructor-arg  index="1" type="java.lang.String">
            <value><![CDATA[<shanghai>]]></value>
        </constructor-arg>

bean之间的引用关系

两种方式,使用property的子节点<ref bean="beanid">,或者使用property的属性ref=“beanid”
<bean id="person" class="com.cqupt.spring.beans.Person">
        <property name="name" value="lihua"></property>
        <property name="age" value="23"></property>
        <!-- 第一种
        <property name="car" >
            <ref bean="car"></ref>
        </property>
         -->
         <property name="car" ref="car2"></property>
        
    </bean>

内部bean

在property标签下,建立子节点<bean> 其中id可有可无,外部不可引用内部bean
    <bean id="person" class="com.cqupt.spring.beans.Person">
        <property name="name" value="lihua"></property>
        <property name="age" value="23"></property>
        <property name="car">
             <!-- 内部bean -->
            <bean id="car3" class="com.cqupt.spring.beans.Car">
	            <constructor-arg   value="Ford"></constructor-arg>
	            <constructor-arg  value="changan"></constructor-arg>
	            <constructor-arg  value="200" type="int"></constructor-arg>
        	</bean>
        </property>
    </bean>

其实constructor-arg标签中也可以使用name属性,如果标注type属性且与类属性类型不同,则出错
 <constructor-arg  name="maxSpeed" value="200" ></constructor-arg>

null值

可不引用其他bean,而是为null值,使用<null/>标签
<property name="car"><null></null></property>
输出结果:Person [name=lihua, age=23, car=null]

级联属性

在存在bean引用的情况下(即不为null),可以级联属性赋值
    <bean id="person" class="com.cqupt.spring.beans.Person">
        <property name="name" value="lihua"></property>
        <property name="age" value="23"></property>
        <property name="car">
             <!-- 内部bean -->
            <bean id="car3" class="com.cqupt.spring.beans.Car">
	            <property name="brand" value="Ford"></property>
	            <property name="corp" value="changan"></property>
	            <property name="maxSpeed" value="200"></property> 
        	</bean>
        </property>
        <!-- 级联属性赋值 -->
        <property name="car.price" value="10000"></property>
    </bean>

输出结果:Person [name=lihua, age=23, car=Car [brand=Ford, corp=changan, price=10000.0, maxSpeed=200]]
当为null时,不可以级联赋值
        <property name="car"><null></null></property>
        <!-- 级联属性赋值 -->
        <property name="car.price" value="10000"></property>
出错

集合属性的配置

在property标签下,使用list、set、map标签

List

Person类有集合属性cars

	public List<Car> getCars(){
		return cars;
	}
	
	public void setCars(List<Car> cars){
		this.cars=cars;
	}

	private String name;
	private int age;
	
	private List<Car> cars;
xml配置
        <property name="cars">
            <list>
                <ref bean="car"></ref>
                <ref bean="car2"></ref>
            </list>
        </property>

输出结果:Person [name=lihua, age=23, car=[Car [brand=Audi, corp=<shanghai>, price=30000.0, maxSpeed=250], Car [brand=Boma, corp=Beijing, price=0.0, maxSpeed=500]]]

也可以配置内部bean

<property name="cars">
            <list>
                <ref bean="car"></ref>
                <ref bean="car2"></ref>
                <bean class="com.cqupt.spring.beans.Car">
                    <constructor-arg value="Ford" ></constructor-arg>
                    <constructor-arg value="changan" ></constructor-arg>
                    <constructor-arg value="250" type="int"></constructor-arg>
                </bean>
            </list>
        </property>

Map 

 使用<map><entry key="" value-ref="beanid">标签

Person类

	public Map<String , Car> getCars(){
		return cars;
	}
	
	public void setCars(Map<String,Car> cars){
		this.cars=cars;
	}

	private String name;
	private int age;
	
	private Map<String, Car> cars;
Xml

<property name="cars">
            <map>
                <entry key="AA" value-ref="car"></entry>
                <entry key="BB" value-ref="car2"></entry>
            </map>
        </property>


Properties的配置

在property标签下,使用<props><prop></prop></props>标签

DataSource类,含有Properties属性

public class DataSource {
	
	public Properties getProperties() {
		return properties;
	}

	public void setProperties(Properties properties) {
		this.properties = properties;
	}

	private Properties properties;
	
}

xml配置

    <bean id="dataSource" class="com.cqupt.spring.beans.DataSource">
        <property name="properties">
            <props>
                <prop key="user">lihua</prop>
                <prop key="password">123456</prop>
                <prop key="Url">www.baidu.com</prop>
            </props>
        </property>
    </bean>

单利集合bean

单独配置一个bean集合,供不同的property使用,需在xml中引入util命名空间<util:list><ref bean=""/></util:list>

    <util:list id="cars">
    	<ref bean="car"></ref>
    	<ref bean="car2"></ref>
    </util:list>
    
    <bean id="person" class="com.cqupt.spring.beans.Person">
        <property name="name" value="lihua"></property>
        <property name="age" value="23"></property>
        <property name="cars" ref="cars"></property>
    </bean>

Person类中cars属性是List集合

p命名空间使用

先导入命名空间p,可以简化配置bean属性,bean引用注意,使用cars-ref=“beanid”

<bean id="person2" class="com.cqupt.spring.beans.Person" p:name="jack" p:age="18" p:cars-ref="cars"></bean>

5、自动装配 autowire

自动装配,是通过在bean标签里设置autowire属性,让系统根据名称还是类型进行自动装配。

autowire="byName"是根据引用的bean的名称与类型中的属性名称进行匹配

autowire="byType"是根据引用的bean的类型与类型中的属性类型进行装配,当多个bean指向同类型时,不能根据类型装配,否则异常。、

新建Person类,有Address和Car两个属性,Address和Car均为自定义类型

xml中的配置,先配置address和car的bean,再在person中自动装配这两个bean:

	<bean id="address" class="com.cqupt.spring.autowire.Address" p:city="Beijing" p:road="Daxing"></bean>
	<bean id="car" class="com.cqupt.spring.autowire.Car" p:brand="Boma" p:price="30000"></bean>
	<!-- 
	<bean id="person" class="com.cqupt.spring.autowire.Person" p:name="jack" p:age="28" p:address-ref="address" p:car-ref="car"></bean>
 	-->
 	<bean id="person" class="com.cqupt.spring.autowire.Person" p:name="bolt" p:age="24" autowire="byName"></bean>
	public static void main(String[] args){
		ApplicationContext ac=new ClassPathXmlApplicationContext("newApplicationContext.xml");
		Person person = (Person)ac.getBean("person");
		System.out.println(person);
	}

输出结果:Person [name=bolt, age=24, address=Address [city=Beijing, road=Daxing], car=Car [brand=Boma, price=30000.0]]

注意,autowire装配是有缺点的:1,只要指定autowire,那么所有引用bean都设置成自动装配;2、要么是byName,要么是byType,不够灵活

一般,开发过程中,不使用自动装配。在整合第三方框架时使用。

6、bean 之间的关系

继承parent

当多个bean之间的配置具有很大相似性时,多次配置相同内容会繁琐,为了简化这种操作,可使用bean的继承关系。
	<bean id="address" class="com.cqupt.spring.autowire.Address" p:city="Beijing" p:road="Daxing"></bean>
	<bean id="address2" class="com.cqupt.spring.autowire.Address" p:city="Beijing" p:road="FengTai"></bean>

上述两个bean之间只有个别属性road不同,故可使用bean继承简化操作,在bean标签中添加parent属性:parent="beanid"
	<bean id="address" class="com.cqupt.spring.autowire.Address" p:city="Beijing" p:road="Daxing"></bean>
	<bean id="address2"  p:road="FengTai" parent="address"></bean>
父bean可以使实例化bean,也可以是抽象bean,用作模板使用,bean标签设置属性abstract="true",此时父bean 不能实例化,否则出错
注意,当父bean的class属性不设置的时候,必须把父bean设置为抽象bean,此时父bean只需设置些必要属性即可

依赖 depends-on

依赖关系设置的作用,主要就是,当前bean实例化之前必须实例一个前置bean,这个bean当然不一定在当前bean中引用,起到限制作用:必须先实例化前置依赖bean,才能实例当前bean,所以前置依赖bean不能是抽象bean。在当前bean标签里设置属性:depends-on="beanid"
	<bean id="car2" class="com.cqupt.spring.autowire.Car" p:brand="Audi" ></bean>
	<bean id="person" class="com.cqupt.spring.autowire.Person" p:name="jack" p:age="26" p:address-ref="address" 
	 depends-on="car2">

如果没有配置过bean car2,那么抛出异常
配置多个依赖bean时,可以用逗号或者空格隔开depends-on="car car2 car3"

7、bean的作用域scope

在spring配置文件中,每个bean代表一个实例,在程序中通过容器的getBean方法获取实例,那么一个bean多次获取实例,这些实例是相同还是不同的呢?这就关系到bean的scope属性,分为四种:singleton  prototype  session request,后两种不经常使用,暂且不讲。bean标签属性scope="singleton"
singleton:默认值,单例模式,某个bean的所有获得的实例均指向同一内存单元,即只存在一个实例,在容器加载时便创建实例;
prototype:每一个bean的所有实例都是不同的,容器初始化时不创建实例,只有在程序运行中第一次获取该实例时,才进行实例化

<bean id="car" class="com.cqupt.spring.autowire.Car" p:brand="Audi" p:price="300000" scope="singleton"></bean>
	public static void main(String[] args) {

		ApplicationContext ctx= new ClassPathXmlApplicationContext("applicationContext-scope.xml");
		Car car= (Car) ctx.getBean("car");
		Car car2= (Car)ctx.getBean("car");
		System.out.println(car==car2);
	}
输出结果为true
在Car的无参构造函数添加
	System.out.println("Car Constructing...");
public static void main(String[] args) {

		ApplicationContext ctx= new ClassPathXmlApplicationContext("applicationContext-scope.xml");
	}
此时输出结果为:Car Constructing...

当bean的scope属性设置为prototype时:
	public static void main(String[] args) {

		ApplicationContext ctx= new ClassPathXmlApplicationContext("applicationContext-scope.xml");
		Car car= (Car) ctx.getBean("car");
		Car car2= (Car)ctx.getBean("car");
		System.out.println(car==car2);
	}

输出结果为:
Car Constructing...
Car Constructing...
false

8、外部属性文件

在开发过程中,经常需要配置各种各样的系统属性,像硬件地址、用户名、密码、网站地址等。spring架构开发中同样需要,其方法就是在类路径下建立properties文件,将配置参数以键值对的形式记录,在spring配置文件中配置bean,通过变量${}的方式获取properties文件中的配置参数。

用连接mysql数据库为例:
建立properties文件db.properties
user=root
password=root
driverClass=com.mysql.jdbc.Driver
jdbcUrl=jdbc:mysql:///test
spring配置文件中配置bean,在spring2.5后使用引入命名空间context的方式加载properties文件:<context:property-placeholder location="classpath:name.properties"/>
<?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"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd">
	
	<context:property-placeholder location="classpath:db.properties"/>
	<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
		<property name="user" value="${user}"></property>
		<property name="password" value="${password}"></property>
		<property name="driverClass" value="${driverClass}"></property>
		<property name="jdbcUrl" value="${jdbcUrl}"></property>
	</bean>

</beans>
程序
	public static void main(String[] args) throws SQLException {

		ApplicationContext ctx= new ClassPathXmlApplicationContext("properties.xml");
		DataSource dataSource= (DataSource) ctx.getBean("dataSource");
		System.out.println(dataSource.getConnection());
	}
输出结果为com.mchange.v2.c3p0.impl.NewProxyConnection@17776a8 [wrapping: com.mysql.jdbc.JDBC4Connection@69a10787]
代表已经连接上本机mysql数据库,在上边properties文件中,用户和密码均设置的是正确数值

9、SpEL的功能与使用

在配置bean中,为了实现更多功能,采用SpEL,可以为属性设置字符值、引用其他类的静态变量、引用其他bean以及其他bean的属性、运算等
value="#{...}" 或者 p:city="#{...}"
	<!-- 使用SpEL 为属性赋字面值 -->
	<bean id="address" class="com.cqupt.spring.spel.Address" 
	p:city="#{'BeiJing'}" p:road="DaXing" ></bean>
	
	<!-- 使用SpEL引用其他类中的静态属性 #{T(java.lang.Math).PI} -->
	<bean id="car" class="com.cqupt.spring.spel.Car" 
	p:brand="Audi" p:price="500000" p:tyrePerimeter="#{T(java.lang.Math).PI * 80}"></bean>
	
	<!-- 1.使用SpEL 引用其他bean 的 属性
		2.使用SpEL 做运算,动态配置bean属性
	-->
	<bean id="person" class="com.cqupt.spring.spel.Person"
	 p:name="jack" p:car="#{car}" p:city="#{address.city}" 
	 p:info="#{car.price > 300000 ? '金领':'白领'}">
	 </bean>

程序:
	public static void main(String[] args){
		ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext-spel.xml");
		
		Address address = (Address) ctx.getBean("address");
		System.out.println(address);
		
		Car car = (Car) ctx.getBean("car");
		System.out.println(car);
		
		Person person = (Person) ctx.getBean("person");
		System.out.println(person);
	}

输出结果:
Address [city=BeiJing, road=DaXing]
Car [brand=Audi, price=500000.0, tyrePerimeter=251.32741228718345]
Person [name=jack, city=BeiJing, car=Car [brand=Audi, price=500000.0, tyrePerimeter=251.32741228718345], info=金领]

10、bean的生命周期

生命周期

每个bean的生命周期可以分成五个部分:
1:利用构造函数或者工厂化方法实例化bean
2:利用set方法对bean的属性进行对实例赋值
3:调用bean的初始化方法
4:bean实例的使用
5:容器关闭,调用bean的销毁方法

通过代码看这五个部分,可以在bean标签中指定属性init-method="init2"和destroy-method="destroy"的方式指定bean的初始化方法和销毁方法
Car类型
public class Car {

	private String brand;
	
	public Car(){
		System.out.println("Car Constructor...");
	}
	
	public void setBrand(String brand){
		System.out.println("Car setter()...");
		this.brand = brand;
	}
	
	public void init2(){
		System.out.println("Car init()...");
	}
	
	public void destroy(){
		System.out.println("Car destroy()...");
	}
}
xml配置
	<bean id="car" class="com.cqupt.spring.cycle.Car" 
	p:brand="Audi" init-method="init2" destroy-method="destroy"></bean>
主方法:
	public static void main(String[] args){
 
		ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("cycle.xml");
		Car car = (Car) ctx.getBean("car");
		System.out.println("使用过程 println:"+car);
		ctx.close();
	}
输出结果:
Car Constructor...
Car setter()...
Car init()...
使用过程 println:com.cqupt.spring.cycle.Car@490ab905
Car destroy()...

bean后置处理器

基于生命周期五部分的划分,其实可以利用bean后置处理器对生命周期进行更细粒度的划分。
bean后置处理器实际上是一个继承BeanPostProcessor接口的类,配置到spring xml文件中,可以在容器初始化时自动加载。
功能:对每个bean进行检查,在每个bean的init生命周期前后进行操作。
创建一个后置处理器:MyBeanPostProcessor
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

public class MyBeanPostProcessor implements BeanPostProcessor {

	@Override
	public Object postProcessAfterInitialization(Object arg0, String arg1)
			throws BeansException {
		System.out.println("postProessAfterInitialization... "+arg0+" "+arg1);
		return arg0;
	}

	@Override
	public Object postProcessBeforeInitialization(Object arg0, String arg1)
			throws BeansException {
		System.out.println("postProessBeforeInitialization... "+arg0+" "+arg1);
		return arg0;
	}

}
Object arg0 :就是传入进的bean实体
String arg1 : bean的id名字
注意:两个函数一般是返回bean的原型,一个在bean的初始化方法前调用,一个在初始化方法后调用,如果返回为null,则后续的使用阶段getBean将无法使用

后置处理器的bean配置
<bean class="com.cqupt.spring.cycle.MyBeanPostProcessor"></bean>
无需指定id,IOC容器会自动加载该后置处理器的bean,不过指定了也不会出错,其实没必要指定
主函数:
	public static void main(String[] args){
 
		ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("cycle.xml");
		Car car = (Car) ctx.getBean("car");
		System.out.println("使用过程 println:"+car);
		ctx.close();
	}
输出结果:
Car Constructor...
Car setter()...
postProessBeforeInitialization... com.cqupt.spring.cycle.Car@2f686d1f car
Car init()...
postProessAfterInitialization... com.cqupt.spring.cycle.Car@2f686d1f car
使用过程 println:com.cqupt.spring.cycle.Car@2f686d1f
Car destroy()...
生命周期在bean的初始化方法前后进行细化,分为七个部分。
后置处理器的两个函数,可以对传入的bean实体进行修改、检查等操作,当然也可以返回其他的bean实例,例如新建一个类对象返回,但必须是同类型,否则出错

11、工厂方法配置bean

配置bean,我们前边一直是用的bean对应的全类名,即bean标签的属性class=“全类名”。现在使用工厂方法配置bean,分为两种:静态工厂方法、实例工厂方法。

静态工厂方法:

通过定义一个静态工厂,在配置bean时,让class="静态工厂全类名",和factory-method来配置bean

定义一个Car的静态工厂:

public class StaticFactory {

	private static Map<String, Car> cars= new HashMap<String, Car>();
	
	static{
		cars.put("audi", new Car("audi",300000));
		cars.put("Boma", new Car("Boma",400000));
	}
	public static Car getCar(String brand){
		return cars.get(brand);
	}
}
利用这个静态工厂的静态方法getCar配置bean标签的factory-method属性

<bean id="car" class="com.cqupt.spring.factory.StaticFactory" factory-method="getCar">
		<constructor-arg  value="Boma"></constructor-arg>
	</bean>
使用constructor-arg子节点为静态工厂方法进行传参。

实例工厂方法:

与静态工厂方法不同得是,必须要配置实例工厂的bean,才能调用工厂的方法

创建非静态工厂:

public class InstanceFactory {
	
	private Map<String, Car> cars;
	public InstanceFactory(){
		cars = new HashMap<String, Car>();
		cars.put("Ford", new Car("Ford",200000));
		cars.put("Boma", new Car("Boma",200000));
	}
	public Car getCar(String brand){
		return cars.get(brand);
	}
}
配置工厂方法的bean,以及Car的bean

	<bean id="instanceFactory" class="com.cqupt.spring.factory.InstanceFactory"></bean>
	
	<bean id="car2" factory-bean="instanceFactory" factory-method="getCar">
		<constructor-arg value="Ford"></constructor-arg>
	</bean>
使用factory-bean将工厂的bean引用,factory-method指定方法,constructor-arg传参

12、FactoryBean配置bean

目前配置bean已经详述了 全类名、工厂方法两种方式,还有一种就是利用FactoryBean配置

FactoryBean是一个接口,继承此接口的类,可以用来配置bean

创建一个继承此接口的类:CarFactoryBean

public class CarFactoryBean implements FactoryBean<Car> {

	String brand;
	
	public void setBrand(String brand){
		this.brand = brand;
	}
	@Override
	public Car getObject() throws Exception {
		return new Car("BMW",300000);
	}

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

	@Override
	public boolean isSingleton() {
		return true;
	}	
}
继承FactoryBean接口的类都会从接口中继承三个方法:getObject 返回bean原型,getObjectType 返回bean类型,isSingleton 限定是否单利模式。

bean配置,class指向继承接口的类,property子节点时间上是配置的CarFactoryBean的属性,该bean返回的是FactoryBean的getObject方法返回的实例。

	<bean id="car3" class="com.cqupt.spring.factory.CarFactoryBean" >
		<property name="brand" value="Ford"></property>
	</bean>
目前只是熟悉了工厂方法,FactoryBean 配置bean,但这两种方式的功效具体在哪些方面实现,有待于深入学习,现在是觉得不如全类名配置简单。

13、基于注解的方式配置bean

前边讲的三种配置bean的方式:全类名,工厂方法,FactoryBean都是基于xml文件的方式,基于注解的方式配置bean也是一种常用的方式。

基于注解的方法就是在程序中,在类或者类属性的前边通过添加@形式的注解,spring会依据配置自动扫描这些添加了注解的类进入IOC容器中,IOC中的bean的默认名字就是类名第一个字母小写。

基于注解配置bean

@对类进行注解有四种形式:Component,Repository,Controller,Service,这四种的区别有待于研究。

建立四个包:其他三个包是第一个的子包

com.cqupt.spring.beans.annotation

com.cqupt.spring.beans.annotation.repository

com.cqupt.spring.beans.annotation.service

com.cqupt.spring.beans.annotation.controller

在annotation包下建立一个TestObject类,并通过@Component对类进行注解

@Component
public class TestObject {
	
}

在repository包中建立UserRepository接口,注意,接口是不能进行注解的,因为接口没必要配置bean

public interface UserRepository {

	public void save();
}

在建立这个接口的实现类UserRepositoryImpl,对此接口进行注解,@Repository

@Repository(value="userRepository")
public class UserRepositoryImpl implements UserRepository {
	
	public void save(){
		System.out.println("UserRepository save...");
	}
}
如果bean不使用默认的第一字母小写的类名,可以用上述形式指定名称,也可以去掉value。

同样在service包中创建UserService类,@Service注解

@Service
public class UserService {

	public void add(){
		System.out.println("UserService add...");
	}
}
在controller中创建UserController包,@Controller注解

@Controller
public class UserController {

	public void execute(){
		System.out.println("UserController execute...");
	}
}
当类建立完成并且注解之后,需要在xml中配置,使得spring依据这个配置检测注解的类,并在IOC中生成bean。

使用context:component-scan标签,base-package是指定检测的包及其子包,所以四个包都被IOC进行检测

<context:component-scan base-package="com.cqupt.spring.annotation"></context:component-scan>

主函数:

	public static void main(String[] args) {

		ApplicationContext ctx= new ClassPathXmlApplicationContext("beans-annotation.xml");
		
		TestObject to= (TestObject) ctx.getBean("testObject");
		System.out.println(to);
		
		UserService userService = (UserService) ctx.getBean("userService");
		System.out.println(userService);
		userService.add();
		
		UserController userController = (UserController) ctx.getBean("userController");
		System.out.println(userController);
		userController.execute();

		
		UserRepository userRepository = (UserRepository) ctx.getBean("userRepository");
		System.out.println(userRepository);
		userRepository.save();
	}
输出结果:

com.cqupt.spring.annotation.TestObject@727803de
com.cqupt.spring.annotation.service.UserService@704921a5
UserService add...
com.cqupt.spring.annotation.controller.UserController@df27fae
UserController execute...
com.cqupt.spring.annotation.repository.UserRepositoryImpl@24a35978
UserRepository save...

另外:context:component-scan 标签的resource-pattern="repository/*.class"属性指定了IOC检测类的范围限定在repository包下。

	<context:component-scan 
		base-package="com.cqupt.spring.annotation"
		resource-pattern="repository/*.class">
	</context:component-scan>

此时在Main中只能获取到UserRepository的对象,因为IOC只创建了名为userRespository的bean

IOC检测的范围限制

通过注解机制,可以使IOC检测程序中被注解的类生产bean,但是如果不希望所以注解的类均生成bean,该如何呢?

使用范围限制:两种:一是使用resource-pattern的属性,另一种就是在context:component-scan 标签子节点context:exclude-filter排除和context:include-filter包含。

context:include-filter经常和use-default-filters="false"结合使用:表示只包含之意。

context:exclude-filter排除和context:include-filter包含这两个节点依据属性type="annotation"和type="assignable“分为两种类型:依据注解组件类型,和依据类的类型

依据组件类型的排除:

	<context:component-scan base-package="com.cqupt.spring.annotation" use-default-filters="true">
		<!-- 通过子节点context:exclude-filter type="annotation" 排除被IOC检测的组件类型-->
		<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
	</context:component-scan>
这样,IOC容器中不会有被Repository注解的bean,注意expression="全类名"

依据类类型的排除:

	<context:component-scan base-package="com.cqupt.spring.annotation" >
		<context:exclude-filter type="assignable" expression="com.cqupt.spring.annotation.repository.UserRepository"></context:exclude-filter>
	</context:component-scan>
所有继承接口UserRepository的类均不会在IOC容器中生成bean

依据类类型的包含(只包含)

	<context:component-scan base-package="com.cqupt.spring.annotation" use-default-filters="false">
		<context:include-filter type="assignable" expression="com.cqupt.spring.annotation.repository.UserRepository"></context:include-filter>
	</context:component-scan>
在IOC中只有继承接口UserRepository的类才会生成bean

当然上述这些范围限定都是在类被注解的基础上的,如果类没有被注解,无论怎么包含,是annotation还是assignable,都不会生成bean

属性基于注解的自动装配

在使用基于注解配置bean时,会使用context:component-scan标签,这个标签还会自动注册一个bean后置处理器AutowiredAnnotationBeanPostProcessor,来实现自动装配bean的属性

使用注解@Autowired @Resource @Inject,最常用的是@Autowired注解

详述@Autowired使用:

使用位置,可以放在普通字段、构造器、一切具有参数的方法前

放在普通字段前:

@Controller
public class UserController {
	@Autowired
	UserService userService;

	public void execute(){
		System.out.println("UserController execute...");
		userService.add();
	}
}

放在方法前:

@Controller
public class UserController {
	
	UserService userService2;
	
	@Autowired
	public void setUserService2(UserService userService2) {
		this.userService2 = userService2;
	}
	public void execute(){
		System.out.println("UserController execute...");
		userService2.add();
	}
}

@Autowired是按照兼容类型自动装配的,如果IOC中已经有生成了类型UserService的bean,不管名字是userService,还是userService2,都可以自动装配进来。

如果IOC中没有生成UserService的bean,那么程序会抛出异常。解决方法是指定@Autowired的属性required为false, @Autowired(required=false)

既然@Autowired是根据类型自动装配的,那么如果IOC中有两个以上同类型的bean的,只是bean名称不一样,就需要注解@Qualifier("beanid"),根据bean名称指定自动装配的bean,实际上在这种情况下,不指定@Qualifier,系统会自动选择名字与属性名一样的bean

@Service
public class UserService {
	@Autowired
	UserRepository userRepository;

	public void add(){
		System.out.println("UserService add...");
		userRepository.save();
	}
}
有两个UserRepository的bean

@Repository("userRepository")
public class UserRepositoryImpl implements UserRepository {
	
	public void save(){
		System.out.println("UserRepository save...");
	}
}
第一个指定bean名称为userRepository
@Repository
public class UserJdbcRepository implements UserRepository {

	@Override
	public void save() {
		System.out.println("UserJdbcRepository save...");
	}

}
第二个没有指定,就是默认的userJdbcRepository

此时@Autowired注解的属性将默认选择名为与属性名相同的bean,即userRepository

如果第一个bean没有指定名字为userRepository

@Repository
public class UserRepositoryImpl implements UserRepository {
	
	public void save(){
		System.out.println("UserRepository save...");
	}
}
那么ICO中两个bean分别是userRepositoryImpl和userJdbcRepository

就会抛出异常:Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [com.cqupt.spring.annotation.repository.UserRepository] is defined: expected single matching bean but found 2: userJdbcRepository,userRepositoryImpl

此时就必学添加@Qualifier注解:

@Service
public class UserService {
	@Autowired
	@Qualifier("userJdbcRepository")
	UserRepository userRepository;

	public void add(){
		System.out.println("UserService add...");
		userRepository.save();
	}
}
注意:也经常把@Qualifier放在方法的中:

	UserRepository userRepository;
	
	@Autowired
	public void setUserRepository(@Qualifier("userJdbcRepository") UserRepository userRepository) {
		this.userRepository = userRepository;
	}

@Autowired也可以放在数组、集合、java.util.Map上,进行自动装配。

泛型依赖注入

泛型依赖注入与普通注入的区别在于,使用了泛型,在父类之间形成注入依赖,被子类继承,子类可以自动进行依赖注入

创建两个泛型父类:BaseRepository,BaseService,BaseService的属性与BaseRepository形成依赖注入关系

public class BaseRepository<T> {

}

public class BaseService<T> {
	
	@Autowired
	protected BaseRepository<T> repository;
	
	public void add(){
		System.out.println("Service add...");
		System.out.println(repository);
	}
}
创建两个父类的子类UserRepository和UserService,并注解给IOC

@Repository
public class UserRepository extends BaseRepository<User> {

}

@Service
public class UserService extends BaseService<User> {

}
通过测试:

	public static void main(String[] args) {

		ApplicationContext ctx = new ClassPathXmlApplicationContext("beans-module.xml");
		UserService us=(UserService) ctx.getBean("userService");
		us.add();
	}

输出结果:

Service add...
com.cqupt.spring.generic.di.UserRepository@5d11346a

注:注解不可以继承















你可能感兴趣的:(spring)