IOC容器和Bean的配置

IOC容器和Bean的配置

1. 什么是IOC和DI

IOC(反转控制)

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

DI(依赖注入)

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

总结: IOC 就是一种反转控制的思想, 而DI是对IOC的一种具体实现

2. IOC容器在Spring中的实现

前提:

Spring中有IOC思想, IOC思想必须基于 IOC容器来完成, 而IOC容器在最底层实质上就是一个对象工厂

  1. 在通过IOC容器读取Bean的实例之前,需要先将IOC容器本身实例化。
  2. Spring提供了IOC容器的两种实现方式
    1. BeanFactory:IOC容器的基本实现,是Spring内部的基础设施,是面向Spring本身的,不是提供给开发人员使用的
    2. ApplicationContext:BeanFactory的子接口,提供了更多高级特性。面向Spring的使用者,几乎所有场合都使用ApplicationContext而不是底层的BeanFactory。

3. ApplicationContext的主要实现类

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

4. ConfigurableApplicationContext

  1. 是ApplicationContext的子接口,包含一些扩展方法

  2. refresh()和close()让ApplicationContext具有启动、关闭和刷新上下文的能力。

5. WebApplicationContext

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

(一)通过类型获取bean

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

代码

//初始化容器
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
//获取对象常用的三种方法
//1. 通过getBean()获取对象,传入参数id,具有唯一id的对象
Person person = (Person)ac.getBean("personOne");

//2. 使用此方法获取对象时,要求spring所管理的此类型的对象只能有一个
Person person = ac.getBean(Person.class);

//3. 综合以上两种方法,目标更明确
Person person = ac.getBean("personTwo", Person.class);

配置文件: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.xsd">
	<!-- 
		<bean>:定义spring所管理的一个对象
		id:该对象的唯一标示,注意:不能重复,在通过类型获取bean的过程中可以不设置
		class:此对象所属类的全限定名
	 -->
	<bean id="personOne" class="com.atguigu.spring.mod.Person">
		<!-- 
			<property>:为对象的某个属性赋值
			name:属性名
			value:属性值
		 -->
		<property name="id" value="1111"></property>
		<property name="name" value="小明"></property>
	</bean>
	
	<bean id="personTwo" class="com.atguigu.spring.mod.Person">
		<property name="id" value="2222"></property>
		<property name="name" value="小红"></property>
	</bean>

</beans>

(二)给bean的属性赋值

1. 依赖注入的方式

1)通过bean的setXxx()方法赋值

	<bean id="s1" class="com.atguigu.spring.di.Student">
    <!-- value子节点 -->
		<property name="id">
			<value>10010</value>
		</property>
    <!-- 这里的property标签里的name="name" 相当于调用实体类的setName()方法 -->
		<property name="name" value="张三"></property>
		<property name="age" value="23"></property>
		<property name="sex" value="男"></property>
	</bean>

2)通过bean的构造器赋值

  1. Spring自动匹配合适的构造器
	<bean id="s2" class="com.atguigu.spring.di.Student">
		<constructor-arg value="10086"></constructor-arg>
		<constructor-arg value="李四"></constructor-arg>
		<constructor-arg value="24"></constructor-arg>
		<constructor-arg value="女"></constructor-arg>
	</bean>
  1. 通过索引值指定参数位置,并通过类型区分重载的构造器
	<bean id="s3" class="com.atguigu.spring.di.Student">
		<constructor-arg value="10022"></constructor-arg>
		<constructor-arg value="王五"></constructor-arg>
    <!-- 
				index="2"表示构造器的第三个参数,
				type="java.lang.Double"表示这个参数是Double类型的
 		-->
		<constructor-arg value="90" index="2" type="java.lang.Double"></constructor-arg>
		<constructor-arg value="女"></constructor-arg>
	</bean>

2. p名称空间

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

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

<bean id="s4" class="com.atguigu.spring.di.Student" 
      p:id="10033" p:name="赵六" p:age="26" p:sex="男" 
      p:teacher-ref="teacher">
</bean>

3. 可以使用的值

1)字面量

  1. 可以使用字符串表示的值,可以通过value属性或value子节点的方式指定

  2. 基本数据类型及其封装类、String等类型都可以采取字面值注入的方式

  3. 若字面值中包含特殊字符,可以使用把字面值包裹起来

2)null值

对于引用数据类型,如果不需要赋值

		<property name="id">
			<null/>
		</property>

3)给bean的级联属性赋值

<property name="teacher.tname" value="小红"></property>

4)外部已声明的bean、引用其他的bean

<bean id="s5" class="com.atguigu.spring.di.Student">
  <property name="id" value="10055"></property>
  <property name="name" value="张三三"></property>
  <property name="age" value="23"></property>
  <property name="sex" value="男"></property>
  <property name="teacher" ref="teacher"></property>
  <property name="teacher.tname" value="小红"></property>
</bean>

<bean id="teacher" class="com.atguigu.spring.di.Teacher">
  <property name="tid" value="10000"></property>
  <property name="tname" value="小明"></property>
</bean>

5)内部bean

<bean id="s6" class="com.atguigu.spring.di.Student">
  <property name="id" value="10066"></property>
  <property name="name" value="崔八"></property>
  <property name="age" value="18"></property>
  <property name="sex" value="男"></property>
  <property name="teacher">
    <bean id="tt" class="com.atguigu.spring.di.Teacher">
      <property name="tid" value="2222"></property>
      <property name="tname" value="admin"></property>
    </bean>
  </property>
</bean>
注意

定义在某个bean内部的bean,只能在当前bean中使用

5. 集合属性

1)数组和List

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

注意

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

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

<!-- 参数值为基本类型时 -->
<bean id="t1" class="com.atguigu.spring.di.Teacher">
		<property name="tid" value="10001"></property>
		<property name="tname" value="佟刚"></property>
		<property name="cls">
			<list>
				<value>A</value>
				<value>B</value>
				<value>C</value>
			</list>
		</property>
</bean>

<!-- 参数值为引用类型时 -->
<bean id="t2" class="com.atguigu.spring.di.Teacher">
		<property name="tid" value="10002"></property>
		<property name="tname" value="婷姐"></property>
		<property name="students">
			<list>
				<ref bean="s1"/>
				<ref bean="s2"/>
				<ref bean="s3"/>
			</list>
		</property>
</bean>

2)Map

Java.util.Map通过标签定义,标签里可以使用多个作为子标签。每个条目包含一个键和一个值。

​ 必须在标签里定义键。

​ 因为键和值的类型没有限制,所以可以自由地为它们指定元素。

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

	<bean id="t3" class="com.atguigu.spring.di.Teacher">
		<property name="tid" value="10003"></property>
		<property name="tname" value="admin"></property>
		<property name="bossMap">
			<map>
				<entry>
					<key>
						<value>10001</value>
					</key>
					<value>佟老师</value>
				</entry>
				<entry>
					<key>
						<value>10002</value>
					</key>
					<ref bean="teacher"/>
				</entry>
			</map>
		</property>
	</bean>

3)集合类型的bean

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

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

<!-- 在<beans>标签内写入以下语句 -->
xmlns:util="http://www.springframework.org/schema/util"

	<bean id="t4" class="com.atguigu.spring.di.Teacher">
		<property name="tid" value="10004"></property>
		<property name="tname" value="root"></property>
		<property name="students" ref="students"></property>
	</bean>
	
	<util:list id="students">
		<ref bean="s4"/>
		<ref bean="s5"/>
		<ref bean="s6"/>
	</util:list>
	
	<util:map id="map">
		<entry>
			<key>
				<value>1</value>
			</key>
			<value>张三</value>
		</entry>
	</util:map>

6. FactoryBean

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

​ 工厂bean跟普通bean不同,其返回的对象不是指定类的一个实例,其返回的是该工厂bean的getObject方法所返回的对象。

​ 工厂bean必须实现org.springframework.beans.factory.FactoryBean接口。

<bean id="factory" class="com.atguigu.spring.factorybean.MyFactory"></bean>
import org.springframework.beans.factory.FactoryBean;

public class MyFactory implements FactoryBean<Car> {
	@Override
	public Car getObject() throws Exception {
		Car car = new Car();
		car.setBrand("奥迪");
		car.setPrice(200000.0);
		return car;
	}
	@Override
	public Class<?> getObjectType() {
		// TODO Auto-generated method stub
		return Car.class;
	}
	@Override
	public boolean isSingleton() {
		// TODO Auto-generated method stub
		return false;
	}
}

/**********************************************/

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

public class Test {
	public static void main(String[] args) {		
		ApplicationContext ac = new ClassPathXmlApplicationContext("factory-bean.xml");
    //返回值是MyFactory中getObject()方法的返回值
		Object object = ac.getBean("factory");
		System.out.println(object);		
	}
}

7. bean的作用域

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

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

	<bean id="student" class="com.atguigu.ioc.scope.Student" scope="singleton">
		<property name="sid" value="1001"></property>
		<property name="sname" value="张三"></property>
	</bean>

其他的作用域:

  • singleton:在SpringIOC容器中仅存在一个Bean实例,Bean以单例的方式存在
  • prototype:每次调用getBean()时都会返回一个新的实例
  • request:每次HTTP请求都会创建一个新的Bean,该作用域仅适用于WebApplicationContext环境
  • session:同一个HTTP Session 共享一个Bean ,不同的HTTP Session 使用不同的Bean。该作用仅适用于WebApplicationContext环境

注意:当bean的作用域为单例时,Spring会在IOC容器对象创建时就创建bean的对象实例。而当bean的作用域为prototype时,IOC容器在获取bean的实例时创建bean的实例对象。

8. bean的生命周期

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

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

    1. 通过构造器或工厂方法创建bean实例
    2. 为bean的属性设置值和对其它bean的引用
    3. 调用bean的初始化方法
    4. bean可以使用了
    5. 当容器关闭时,调用bean的销毁方法
  3. 在配置bean时,通过init-method 和 destroy-method 属性为bean指定初始化和销毁方法

    <bean id="person" class="com.atguigu.ioc.life.Person" init-method="init" destroy-method="destory">
      <property name="id" value="1001"></property>
      <property name="sex" value="男"></property>
    </bean>
    
    <!-- 配置后置处理器 -->
    <bean class="com.atguigu.ioc.life.AfterHandler"></bean>
    
  4. bean的后置处理

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

    2. bean 后置处理器对IOC容器里的所有bean 实例逐一处理,为非单一实例。

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

    3. bean 后置处理器需要实现接口:org.springframework.beans.factory.config.BeanPostProcessor。在初始化方法被调用前后,Spring将把每个bean实例分别传递给上述接口的以下两个方法:

      • postProcessBeforeInitialization(Object, String)
      • postProcessAfterInitialization(Object, String)
      public class AfterHandler implements BeanPostProcessor {
      	@Override
      	public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
      		Person person = (Person) bean;
      		if(person.getSex().equals("男")) {
      			person.setName("张无忌");
      		}else {
      			person.setName("赵敏");
      		}
      		return person;
      	}
      	@Override
      	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
      		// 注意此处不做处理时,要返回原 bean
      		return bean;
      	}
      }
      
  5. 添加 bean 后置处理器后 bean 的生命周期

    1. 通过构造器或工厂创建 bean 实例
    2. 为 bean 的属性设置值和对其他 bean 的引用
    3. 将 bean 实例传递给bean 后置处理器的postProcessBeforeInitialization()方法
    4. 调用 bean 的初始化方法
    5. 将bean实例传递给bean后置处理器的postProcessAfterInitialization()方法
    6. bean 可以使用了
    7. 当容器关闭时,调用 bean 的销毁方法

(三)引用外部属性文件

以properties格式的属性文件保存起来,同时在bean的配置文件中引用properties属性文件中的内容,从而实现一部分属性值在发生变化时仅修改properties属性文件即可。这种技术多用于连接数据库的基本信息的配置。

1. 直接配置

<!-- 直接配置 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
	<property name="user" value="root"/>
	<property name="password" value="root"/>
	<property name="jdbcUrl" value="jdbc:mysql:///test"/>
	<property name="driverClass" value="com.mysql.jdbc.Driver"/>
</bean>

2. 使用外部的属性文件

  1. 创建properties属性文件

    jdbc.driver=com.mysql.jdbc.Driver
    jdbc.url=jdbc:mysql://localhost:3306/test
    jdbc.username=root
    jdbc.password=123456
    
  2. 引入 context 名称空间

  3. 指定 properties 属性文件位置

  4. 从 properties 属性文件中引入属性值

<!-- 加载资源文件(方式1) -->
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
  <property name="location" value="db.properties"></property>
</bean>

<!-- 加载资源文件(方式2) -->
<context:property-placeholder location="db.properties"/>

<bean id="datasource" class="com.alibaba.druid.pool.DruidDataSource">
  <!-- 从 properties 属性文件中引入属性值 -->
  <property name="driverClassName" value="${jdbc.driver}"></property>
  <property name="url" value="${jdbc.url}"></property>
  <property name="username" value="${jdbc.username}"></property>
  <property name="password" value="${jdbc.password}"></property>
</bean> 

(四)自动装配

1. 概念

  • 手动装配:以value或ref的方式明确指定属性值都是手动装配
  • 自动装配:根据指定的装配规则,不需要明确指定,Spring自动将匹配的属性值注入bean中

2. 装配模式

  1. 根据类型自动装配:将类型匹配的bean作为属性注入到另一个bean中。若IOC容器中有多个与目标bean类型一致的bean,Spring将无法判定哪个bean最合适该属性,所以不能执行自动装配。
  2. 根据名称自动装配:必须将目标bean的名称和属性名设置的完全相同。
  3. 通过构造器自动装配:当bean中存在多个构造器时,此种自动装配方式将会很复杂。不推荐使用。
	<!-- 
		autowire:根据某种策略自动为非字面量属性赋值
		autowire="byName|byType"
		byName:通过属性名和spring容器中bean的id进行比较,若一致则可直接赋值
		byType:通过spring容器中bean的类型,为兼容性的属性赋值
			   在使用byType的过程中,要求spring容器中只能有一个能为属性赋值的bean
		选用建议: 当设置autowire属性,会作用于该bean中所有的非字面量属性,因此谁都不用
	 -->
	
	<bean id="emp" class="com.atguigu.ioc.auto.Emp" autowire="byType">
		<property name="eid" value="1001"></property>
		<property name="ename" value="张三"></property>
	</bean>

3. 选用建议

以上两种均不建议使用

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

(五)通过注解配置 bean(重点)

1. 使用注解标识组件

普通组件

@Component :标识一个受 SpringIOC 容器管理的组件

持久化层组件

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

业务逻辑层组件

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

表述层控制器组件

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

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

2. 扫描组件

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

  1. 指定被扫描的package
  2. base-package 属性指定一个需要扫描的基类包,Spring容器将会扫描这个基类包及其包中的所有类。
  3. 当需要扫描多个包时可以使用逗号分隔
  4. 如果仅希望扫描特定的类而非基包下的所有类,可使用resource-pattern 属性过滤特定类
  5. 包含和排除
	<!-- 
  <context:component-scan>:扫描组件,对设置的包下面的类进行扫苗,会将加上注解的类作为spring的组件进行加载
  组件:指spring中管理的bean

  作为spring的组件进行加载:会自动在spring的配置文件中生成相对应的bean,这些bean的id会以类的首字母小写为值

  <context:include-filter>:在设定的包结构下,再次通过注解或类型具体包含到某个或某几个类
		注意:在使用包含时,一定要设置use-default-filters="false",将默认的过滤(即扫描包下所有的类)关闭

  <context:exclude-filter>:在设定的包结构下,再次通过注解或类型排除某个或某几个类
		注意:在使用排除时,一定要设置use-default-filters="true",将默认的过滤(即扫描包下所有的类)打开
	
 		切记:一个<context:component-scan>中可以出现多个include,也可以同时出现多个exclude,但是两个不能同时出现
	 -->
	<context:component-scan base-package="com.atguigu.ioc.userMod" use-default-filters="true">
    
    <!--
				type="annotation" :表示注解类型
				type="assignable" :表示类的类型
		-->
    
		<!-- <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> -->
		<!-- <context:include-filter type="assignable" expression="com.atguigu.ioc.userMod.service.UserServiceImpl"/> -->
		<!-- <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/> -->
		<!-- <context:exclude-filter type="assignable" expression="com.atguigu.ioc.userMod.dao.UserDaoImpl"/> -->
	</context:component-scan>

完成组件化管理的过程:

  • 在需要被spring管理的类上加上相应注解
  • 在配置文件中通过<context:component-scan>对所设置的包结构进行扫描,就会将加上注解的类,作为spring的组件进行加载

组件:

指spring中管理的bean

作为spring的组件进行加载:会自动在spring的配置文件中生成相对应的bean,这些bean的id会以类的首字母小写为值;也可以通过@Controller("beanId")为自动生成的bean指定id

//@Controller(value="aaa")
@Controller("aaa")
public class UserController {

	@Autowired
	private UserService userService;
	
	public void addUser() {
		userService.addUser();
	}
  
	public UserController() {
		System.out.println("UserController");
	}	
}

3. 基于注解的自动装配(重重点)

在需要赋值的非字面量属性上,加上@Autowired,就可以在spring容器中,通过不同的方式匹配到相对应的bean。

@Autowired装配时,会默认使用byType的方式,此时要求spring容器中只有一个bean能够为其赋值。

当byType实现不了装配时,会自动切换到byName,此时要求spring容器中,有一个bean的id和属性名一致。

若自动装配时,匹配到多个能够复制的bean,可使用@Qualifier(value="beanId")指定使用的bean

@Autowired和@Qualifier(value="beanId")可以一起作用域一个带形参的方法上,此时,@Qualifier(value="beanId"),所指定的bean作用于形参。

@Autowired
@Qualifier(value="userDaoMybatisImpl")
public void setUserDao(UserDao userDao) {//自动装配的是形式参数 userDao
  this.userDao = userDao;
}
原文地址:https://www.cnblogs.com/chaozhengtx/p/13792388.html