Skip to content
On this page

Spring管理BeanIOC

Bean 管理包括两方面

创建 bean 对象

给 bean 注入属性

Bean 配置方式

基于 xml 文件配置方式

基于注解方式

基于 xml 文件配置方式

通过 spring 的 ioc 容器, 获取一个 bean 对象

xml
<bean id="monster01" class="com.hspedu.spring.beans.Monster">
	<property name="monsterId" value="1"/>
	<property name="name" value="牛魔王"/>
	<property name="skill" value="牛魔王拳"/>
</bean>

通过类型来获取 bean 对象

java
*/***

*** 通过类型来获取容器的*bean* 对象

**/*

@Test

public void getMonsterByType() {
		ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml"); Monster monster = ioc.getBean(Monster.class);
  	System.*out*.println("monster=" + monster);
		Monster monster2 = ioc.getBean(Monster.class);
		System.out.println("monster == monster2 的值= " + (monster == monster2))
}

细节说明

1、按类型来获取 bean, 要求 ioc 容器中的同一个类的 bean 只能有一个, 否则会抛出异常 NoUniqueBeanDefinitionException

img

2、这种方式的应用场景:比如 XxxAction/Servlet/Controller, 或XxxService 在一个线程中只需要一个对象实例(单例)的情况

3、老师这里在说明一下: 在容器配置文件(比如 beans.xml)中给属性赋值, 底层是通过setter 方法完成的, 这也是为什么我们需要提供 setter 方法的原因

通过构造器配置 bean

在 spring 的 ioc 容器, 可以通过构造器来配置 bean 对象

xml
<bean id="monster02" class="com.hspedu.spring.beans.Monster">
	<constructor-arg value="2" index="0"/>
	<constructor-arg value="蜘蛛精" index="1"/>
	<constructor-arg value="吐口水" index="2"/>
</bean>


<!-- 数据类型就是对应的 Java 数据类型,按构造器参数顺序-->
<bean id="monster03" class="com.hspedu.spring.beans.Monster">
	<constructor-arg value="3" type="java.lang.Integer"/>
	<constructor-arg value="白骨精" type="java.lang.String"/>
	<constructor-arg value="白骨鞭" type="java.lang.String"/>
</bean>
java
@Test
public void getMonsterByConstructor() {
  ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
  Object monster02 = ioc.getBean("monster02");
  Object monster03 = ioc.getBean("monster03");
  System.out.println("monster02= " + monster02);
  System.out.println("monster03= " + monster03);
}

使用细节

  1. 通过 index 属性来区分是第几个参数
  2. 通过 type 属性来区分是什么类型(按照顺序)

通过 p 名称空间配置 bean

在 spring 的 ioc 容器, 可以通过 p 名称空间来配置 bean 对象

xml
<beans xmlns="http://www.springframework.org/schema/beans" 
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
       xmlns:p="http://www.springframework.org/schema/p" 
       xsi:schemaLocation="http://www.springframework.org/schema/beans
				http://www.springframework.org/schema/beans/spring-beans.xsd">
	<!-- 在 spring 的 ioc 容器, 可以通过 p 名称空间来配置 bean 对象-->
	<bean id="monster04" class="com.hspedu.spring.beans.Monster" p:monsterId="4" p:name="红孩儿" p:skill="吐火~"/>
</beans>
java
@Test
public void getMonsterByP() {
  ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
  Monster monster04 = ioc.getBean("monster04", Monster.class);
  System.out.println("monster04=" + monster04);
}

引用/注入其它 bean 对象

在 spring 的 ioc 容器, 可以通过 ref 来实现 bean 对象的相互引用

java
/**
* @author 韩顺平
* @version 1.0
*/
public class MemberDAOImpl {
  public MemberDAOImpl() {
  	System.out.println("MemberDAOImpl 构造器...");
  }
  public void add() {
  	System.out.println("MemberDAOImpl add()方法");
  }
}
java
/**
* @author 韩顺平
* @version 1.0
*/
public class MemberServiceImpl {
  private MemberDAOImpl memberDAO;
  public MemberServiceImpl() {
  	System.out.println("MemberServiceImpl 构造器~");
  }
  public void add() {
    System.out.println("MemberServiceImpl add()...");
    memberDAO.add();
  }
  public void setMemberDAO(MemberDAOImpl memberDAO) {
    this.memberDAO = memberDAO;
  }
  public MemberDAOImpl getMemberDAO() {
  	return memberDAO;
  }
}

bean 对象的相互引用

  1. 其它含义和前面一样
  2. ref 表示 memberDAO 这个属性将引用/指向 id = memberDAOImpl 对象
xml
<bean id="memberServiceImpl" class="com.hspedu.spring.service.MemberServiceImpl">
  <property name="memberDAO" ref="memberDAOImpl"/>
</bean>
<bean id="memberDAOImpl" class="com.hspedu.spring.dao.MemberDAOImpl"/>
java
@Test
public void setBeanByRef() {
  ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
  MemberServiceImpl memberServiceImpl = ioc.getBean("memberServiceImpl", MemberServiceImpl.class);
  memberServiceImpl.add();
}

引用/注入内部 bean 对象

引用/注入内部 bean 对象, 直接在配置 bean 时注入

xml
<bean id="memberServiceImpl02" class="com.hspedu.spring.service.MemberServiceImpl">
  <property name="memberDAO">
  	<bean class="com.hspedu.spring.dao.MemberDAOImpl"/>
  </property>
</bean>
java
@Test
public void setBeanByPro() {
  ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
  MemberServiceImpl memberServiceImpl02 = ioc.getBean("memberServiceImpl02", MemberServiceImpl.class);
  memberServiceImpl02.add();
}

引用/注入集合/数组类型

在 spring 的 ioc 容器, 看看如何给 bean 对象的集合/数组类型属性赋值

java
/**
* @author 韩顺平
* @version 1.0
*/
public class Master {
  private String name;
  private List<Monster> monsterList;
  private Map<String, Monster> monsterMap;
  private Set<Monster> monsterSet;
  private String[] monsterName;
  //这个 Properties 是 Hashtable 的子类 , 是 key-value 的形式//这里 Properties key 和 value 都是 String
  private Properties pros;
  	public Master() {
  }
  public Master(String name) {
  	this.name = name;
  }
  public Set<Monster> getMonsterSet() {
  	return monsterSet;
  }
  public void setMonsterSet(Set<Monster> monsterSet) {
    this.monsterSet = monsterSet;
  }
  public String[] getMonsterName() {
  	return monsterName;
  }
  public void setMonsterName(String[] monsterName) {
  	this.monsterName = monsterName;
  }
  public String getName() {
  	return name;
  }
  public void setName(String name) {
  	this.name = name;
  }
  public List<Monster> getMonsterList() {
  	return monsterList;
  }
  public void setMonsterList(List<Monster> monsterList) {
  	this.monsterList = monsterList;
  }
  public Map<String, Monster> getMonsterMap() {
  	return monsterMap;
  }
  public void setMonsterMap(Map<String, Monster> monsterMap) {
  	this.monsterMap = monsterMap;
  }
  public Properties getPros() {
  	return pros;
  }
  public void setPros(Properties pros) {
  	this.pros = pros;
  }
}
xml
<!-- 给集合属性注入值
-->
<bean id="master01" class="com.hspedu.spring.beans.Master">
  <property name="name" value="太上老君"/>
  <!-- 给 bean 对象的 list 集合赋值 -->
  <property name="monsterList">
    <list>
    <ref bean="monster03"/>
    <ref bean="monster02"/>
    </list>
  </property>
  <!-- 给 bean 对象的 map 集合赋值 -->
  <property name="monsterMap">
    <map>
      <entry>
        <key>
          <value>monsterKey01</value>
        </key>
        <ref bean="monster01"/>
      </entry>
      <entry>
        <key>
          <value>monsterKey02</value>
        </key>
        <ref bean="monster02"/>
      </entry>
    </map>
	</property>
  <!-- 给 bean 对象的 properties 集合赋值 -->
  <property name="pros">
    <props>
      <prop key="k1">Java 工程师</prop>
      <prop key="k2">前端工程师</prop>
      <prop key="k3">大数据工程师</prop>
    </props>
  </property>
  <!-- 给 bean 对象的数组属性注入值 -->
  <property name="monsterName">
    <array>
      <value>银角大王</value>
      <value>金角大王</value>
    </array>
	</property>
  <!-- 给 bean 对象的 set 属性注入值 -->
  <property name="monsterSet">
    <set>
      <ref bean="monster01"/>
      <bean class="com.hspedu.spring.beans.Monster">
        <property name="monsterId" value="10"/>
        <property name="name" value="玉兔精"/>
        <property name="skill" value="钻地洞"/>
      </bean>
    </set>
  </property>
</bean>
java
/**
* 测试 引用/注入集合/数组类型
*/
@Test
public void setCollectionByPro() {
  ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
   Master master01 = ioc.getBean("master01", Master.class);
  
  //获取 list 集合
  System.out.println("======list=======");
  List<Monster> monster_list = master01.getMonsterList();
  for (Monster monster : monster_list) {
  	System.out.println(monster);
  }
  
  //获取 map 集合
  System.out.println("======map=======");
  Map<String, Monster> monster_map = master01.getMonsterMap();
  Set<Map.Entry<String, Monster>> entrySet = monster_map.entrySet();
  for (Map.Entry<String, Monster> entry : entrySet) {
  	System.out.println(entry);
  }
  
  //获取 properties 集合
  System.out.println("======properties=======");
  Properties pros = master01.getPros();
  String property1 = pros.getProperty("k1");
  String property2 = pros.getProperty("k2");
 String property3 = pros.getProperty("k3");
  System.out.println(property1 + "\t" + property2 + "\t" + property3);
  
  
  //获取数组
  System.out.println("======数组=======");
  String[] monsterName = master01.getMonsterName();
  for (String s : monsterName) {
  	System.out.println("妖怪名= " + s);
  }
  
  
  //获取 set
  System.out.println("======set=======");
  Set<Monster> monsterSet = master01.getMonsterSet();
  for (Monster monster : monsterSet) {
  	System.out.println(monster);
  }
}

使用细节

主要掌握 List/Map/Properties 三种集合的使用.

Properties 集合的特点

  1. 这个 Properties 是 Hashtable 的子类 , 是 key-value 的形式

  2. key 是 string 而 value 也是 string

通过 util 名称空间创建 list

spring 的 ioc 容器, 可以通过 util 名称空间创建 list 集合

java
public class BookStore {//书店
	private List<String> bookList;
  public BookStore() {
	}
  public List<String> getBookList() {
  	return bookList;
  }
  public void setBookList(List<String> bookList) {
  	this.bookList = bookList;
  }
}
xml
<beans xmlns="http://www.springframework.org/schema/beans" 
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 		   		
       xmlns:p="http://www.springframework.org/schema/p" 
       xmlns:util="http://www.springframework.org/schema/util" 
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/util
        http://www.springframework.org/schema/util/spring-util.xsd">
  	<!--通过 util 名称空间来创建 list 集合,可以当做创建 bean 对象的工具来使用-->
    <util:list id="myListBook">
      <value>三国演义</value>
      <value>西游记</value>
      <value>红楼梦</value>
      <value>水浒传</value>
  	</util:list>
    <bean id="bookStore" class="com.hspedu.spring.beans.BookStore">
    	<property name="bookList" ref="myListBook"/>
    </bean>
 </beans>
java
@Test
public void getListByUtil() {
  ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
  BookStore bookStore = ioc.getBean("bookStore", BookStore.class);
  List<String> bookList = bookStore.getBookList();
  for (String s : bookList) {
    System.out.println(s);
  }
}

级联属性赋值

spring 的 ioc 容器, 可以直接给对象属性的属性赋值, 即级联属性赋值

java
public class Dept {
  private String name;
  public Dept() {
  }
  public String getName() {
  	return name;
  }
  public void setName(String name) {
  	this.name = name;
  }
}
java
public class Emp {
  private String name;
  private Dept dept;
  public Emp() {
  }
  public String getName() {
    return name;
  }
  public void setName(String name) {
  	this.name = name;
  }
  public Dept getDept() {
  	return dept;
  }
  public void setDept(Dept dept) {
  	this.dept = dept;
  }
}
xml
<!-- 级联属性赋值
-->
<bean id="emp" class="com.hspedu.spring.beans.Emp">
  <property name="name" value="jack"/>
  <property name="dept" ref="dept"/>
  <property name="dept.name" value="Java 开发部"/>
 </bean>
<bean id="dept" class="com.hspedu.spring.beans.Dept"/>
java
/**
* 测试 级联属性赋值
*/
@Test
public void setProByRelation() {
  ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
  Emp emp = ioc.getBean("emp", Emp.class);
  System.out.println(emp.getDept().getName());
}

通过静态工厂获取对象

在 spring 的 ioc 容器, 可以通过静态工厂获取 bean 对象

java
public class MyStaticFactory {
  private static Map<String, Monster> monsterMap;
  static {
    monsterMap = new HashMap<String, Monster>();
    monsterMap.put("monster_01", new Monster(100, "黄袍怪", "一阳指"));
    monsterMap.put("monster_02", new Monster(200, "九头金雕", "如来神掌"));
  }
  public static Monster getMonster(String key) {
  	return monsterMap.get(key);
  }
}
xml
<!-- 通过静态工厂来获取 bean 对象 -->
<bean id="my_monster" class="com.hspedu.spring.factory.MyStaticFactory"
  factory-method="getMonster">
  <!-- constructor-arg 标签提供 key -->
  <constructor-arg value="monster_01"/>
</bean>
java
@Test
public void getBeanByStaticFactory() {
  ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
	Monster my_monster = ioc.getBean("my_monster", Monster.class);
	System.out.println(my_monster);
}

通过实例工厂获取对象

在 spring 的 ioc 容器, 可以通过实例工厂获取 bean 对象

java
/**
* @author 韩顺平
* @version 1.0
*/
public class MyInstanceFactory {
  private Map<String, Monster> monster_map;
  //非静态代码块
  {
    monster_map = new HashMap<String, Monster>();
    monster_map.put("monster_01", new Monster(100, "猴子精", "吃人"));
    monster_map.put("monster_02", new Monster(200, "九头金雕", "如来神掌"));
  }
  public Monster getMonster(String key) {
  	return monster_map.get(key);
  }
}
xml
<bean id="myInstanceFactory" class="com.hspedu.spring.factory.MyInstanceFactory"/><bean id="my_monster2" factory-bean="myInstanceFactory"
	factory-method="getMonster">
	<constructor-arg value="monster_02"/>
</bean>
java
@Test
public void getBeanByInstanceFactory() {
  ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
  Monster my_monster = ioc.getBean("my_monster2", Monster.class);
  System.out.println(my_monster);
}

通过 FactoryBean 获取对象(重点)

在 spring 的 ioc 容器,通过 FactoryBean

java
public class MyFactoryBean implements FactoryBean<Monster> {
  private String keyVal;
  private Map<String, Monster> monster_map;
  {
    monster_map = new HashMap<String, Monster>();
  	monster_map.put("monster_01", new Monster(100, "黄袍怪", "一阳指"));
  	monster_map.put("monster_02", new Monster(200, "九头金雕", "如来神掌"));
  }
  public void setKeyVal(String keyVal) {
  	this.keyVal = keyVal;
  }
  @Override
  public Monster getObject() throws Exception {
    // TODO Auto-generated method stub
    return this.monster_map.get(keyVal);
  }
  @Override
  public Class getObjectType() {
    // TODO Auto-generated method stub
    return Monster.class;
  }
  @Override
  public boolean isSingleton() {
    // TODO Auto-generated method stub
    return true;
  }
}
xml
<!-- 老韩解读
1. 通过 FactoryBean 来获取 bean 对象
2. name="keyVal" 就是 MyFactoryBean 定义的 setKeyVal 方法3. value="monster_01" ,就是给 keyVal 的值
-->
<bean id="myFactoryBean" class="com.hspedu.spring.factory.MyFactoryBean">
	<property name="keyVal" value="monster_01"/>
</bean>
java
@Test
public void getBeanByFactoryBean() {
  ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
  Monster monster = ioc.getBean("myFactoryBean", Monster.class);
  System.out.println(monster);
}

bean 配置信息重用(继承)

在 spring 的 ioc 容器, 提供了一种继承的方式来实现 bean 配置信息的重用

xml
<!-- 继承的方式来实现 bean 配置信息的重用 -->
<bean id="monster10" class="com.hspedu.spring.beans.Monster">
  <property name="monsterId" value="10"/>
  <property name="name" value="蜈蚣精"/>
  <property name="skill" value="蜇人"/>
</bean>
<!-- parent="monster10" 就是继承使用了 monster10 的配置信息-->
<bean id="monster11" class="com.hspedu.spring.beans.Monster" parent="monster10"/>
<!-- 当我们把某个bean设置为 abstract="true" 这个bean只能被继承,而不能实例化了-->
<bean id="monster12" class="com.hspedu.spring.beans.Monster" abstract="true">
  <property name="monsterId" value="12"/>
  <property name="name" value="美女蛇"/>
  <property name="skill" value="吃人"/>
</bean>
<!-- parent="monster12" 就是继承使用了 monster12 的配置信息-->
<bean id="monster13" class="com.hspedu.spring.beans.Monster" parent="monster12"/>
java
@Test
public void getBeanByExtends() {
  ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
  Monster monster1 = ioc.getBean("monster11", Monster.class);
  System.out.println(monster1);
  Monster monster2 = (Monster) ioc.getBean("monster13", Monster.class);
  System.out.println(monster2);
}

bean 创建顺序

xml
<bean id="student01" class="com.hspedu.bean.Student" />
<bean id="department01" class="com.hspedu.bean.Department" />
<!--在 spring 的 ioc 容器, 默认是按照配置的顺序创建 bean 对象 会先创建 student01 这个 bean 对象,然后创建 department01 这个bean 对象-->
xml
<bean id="student01" class="com.hspedu.bean.Student" depends-on="department01"/>
<bean id="department01" class="com.hspedu.bean.Department" />
<!--如果这样配置 会先创建 department01 对象,再创建 student01 对象-->

先看下面的配置, 请问两个 bean 创建的顺序是什么? 并分析执行流程

  1. 先创建 id=memberDAOImpl

  2. 再创建 id = memberServiceImpl

  3. 调用 memberServiceImpl.setMemberDAO() 完成引用

img

先看下面的配置, 请问两个 bean 创建的顺序是什么, 并分析执行流程

  1. 先创建 id = memberServiceImpl

  2. 再创建 id=memberDAOImpl

  3. 用 memberServiceImpl.setMemberDAO()

img

bean 对象的单例和多例

在 spring 的 ioc 容器, 在默认是按照单例创建的,即配置一个bean 对象后,ioc 容器只会创建一个 bean 实例。

如果,我们希望 ioc 容器配置的某个 bean 对象,是以多个实例形式创建的则可以通过配置scope="prototype" 来指定

java
public class Car {
  public Car() {
  	System.out.println("car 构造器");
  }
}
xml
<!--如果,我们希望 ioc 容器配置的某个 bean 对象,是以多个实例形式创建的则可以通过配置scope="prototype" 来指定-->
<bean name="car" scope="prototype" class="com.hspedu.spring.beans.Car"/>
java
@Test
public void getBeanByPrototype() {
  ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
  for (int i = 0; i < 3; i++) {
    Car car = ioc.getBean("car", Car.class);
    System.out.println(car);
  }
}

使用细节

  1. 默认是单例 singleton, 在启动容器时, 默认就会创建 , 并放入到singletonObjects 集合
  2. <bean scope="prototype" >设置为多实例机制后, 该bean 是在getBean()时才创建
  3. 如果是单例singleton, 同时希望在getBean时 才创建, 可以指定懒加载lazy-init="true" (注意默认是 false)
  4. 通常情况下, lazy-init 就使用默认值 false , 在开发看来, 用空间换时间是值得的, 除非有特殊的要求.
  5. 如果 scope="prototype" 这时你的 lazy-init 属性的值不管是ture, 还是false都是在getBean 时候,才创建对象.

bean 的生命周期

bean 对象创建是由 JVM 完成的,然后执行如下方法

  1. 执行构造器
  2. 执行 set 相关方法
  3. 调用 bean 的初始化的方法(需要配置) init-method="" destroy-method=""
  4. 使用 bean
  5. 当容器关闭时候,调用 bean 的销毁方法(需要配置)
java
public class House {
  private String name;
  public House() {
  	System.out.println("House() 构造器");
  }
  public String getName() {
  	return name;
  }
  public void setName(String name) {
    System.out.println("House setName()...");
    this.name = name;
  }
  public void init() {
  	System.out.println("House init()..");
  }
  public void destory() {
  	System.out.println("House destory()..");
  }
}
xml
<!-- 配置 bean 的初始化方法和销毁方法 -->
<bean id="house" class="com.hspedu.spring.beans.House"
	init-method="init" destroy-method="destory">
	<property name="name" value="北京豪宅"/>
</bean>
java
@Test
public void beanLife() {
  ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
  House house = ioc.getBean("house", House.class);
  System.out.println(house);
  //关闭容器
  ((ConfigurableApplicationContext) ioc).close();
}

使用细节

  1. 初始化 init 方法和 destory 方法, 是程序员来指定
  2. 销毁方法就是当关闭容器时,才会被调用.

配置 bean 的后置处理器 【这个比较难】

  1. 在 spring 的 ioc 容器,可以配置 bean 的后置处理器
  2. 该处理器/对象会在 bean 初始化方法调用前和初始化方法调用后被调用
  3. 程序员可以在后置处理器中编写自己的代码
java
public class MyBeanPostProcessor implements BeanPostProcessor {
  /**
  * 在 bean 初始化之前完成某些任务
  * @param bean : 就是 ioc 容器返回的 bean 对象, 如果这里被替换会修改,则返回的 bean 对象也会被修改
  * @param beanName: 就是 ioc 容器配置的 bean 的名称
  * @return Object: 就是返回的 bean 对象
  */
  public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    // TODO Auto-generated method stub
    System.out.println("postProcessBeforeInitialization 被调用" + beanName+"bean= " + bean.getClass());
    return bean;
  }
  /**
  * 在 bean 初始化之后完成某些任务
  * @param bean : 就是 ioc 容器返回的 bean 对象, 如果这里被替换会修改,则返回的 bean 对象也会被修改
  * @param beanName: 就是 ioc 容器配置的 bean 的名称
  * @return Object: 就是返回的 bean 对象
  */
  public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    System.out.println("postProcessAfterInitialization 被调用" + beanName +" bean="+ bean.getClass());
    return bean;
	}
}
xml
<!-- 配置 bean 的初始化方法和销毁方法 -->
<bean id="house" class="com.hspedu.spring.beans.House"
	init-method="init" destroy-method="destory">
	<property name="name" value="北京豪宅"/>
</bean>
<!-- bean 后置处理器的配置 -->
<bean id="myBeanPostProcessor"class="com.hspedu.spring.beans.MyBeanPostProcessor" />
java
@Test
public void testBeanPostProcessor() {
  ApplicationContext ioc = new ClassPathXmlApplicationContext("beans02.xml");
  House house = ioc.getBean("house", House.class);
  System.out.println(house);
  //关闭容器
  ((ConfigurableApplicationContext) ioc).close();
}

其它说明

1、怎么执行到这个方法:使用 AOP(反射+动态代理+IO+容器+注解)

2、有什么用:可以对 IOC 容器中所有的对象进行统一处理,比如日志处理/权限的校验/安全的验证/事务管理. -初步体验案例: 如果类型是 House 的统一改成 上海豪宅

3、针对容器的所有对象吗:是的 切面编程特点

4、后面我们会自己实现这个底层机制,这个是一个比较难理解的知识点, 现在老韩不做过多的纠结,后面我会带小伙伴实现这个机制

通过属性文件给 bean 注入值

在 spring 的 ioc 容器,通过属性文件给 bean 注入值

properties
name=\u9EC4\u888D\u602A
id=10
skill=\u72EE\u5B50\u543C
xml
<beans xmlns="http://www.springframework.org/schema/beans" 
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 	xmlns:p="http://www.springframework.org/schema/p" 
       xmlns:util="http://www.springframework.org/schema/util" 
       xmlns:context="http://www.springframework.org/schema/context" 	
       xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/util
        http://www.springframework.org/schema/util/spring-util.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">
  <!-- 老韩解读:
		1. 通过属性文件给 bean 注入值, 
		2. 需要导入: xmlns:context 名字空间,并指定属性文件路径
	-->
  <context:property-placeholder location="classpath:my.properties"/>
  <bean id="monster100" class="com.hspedu.spring.beans.Monster">
    <property name="monsterId" value="${id}"/>
    <property name="name" value="${name}"/>
    <property name="skill" value="${skill}"/>
  </bean>
</beans>
java
@Test
public void setProByProFile() {
  ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
  Monster monster100 = ioc.getBean("monster100", Monster.class);
  System.out.println(monster100);
}

基于 XML 的 bean 的自动装配

在 spring 的 ioc 容器,可以实现自动装配 bean

  1. 这个知识点作为了解即可,后面我们主要还是使用基于注解的方式(重点)
  2. 但是机制和原理类似
java
public class OrderDao {
  public void saveOrder() {
    System.out.println("保存 一个订单...");
  }
}
java
public class OrderService {
  private OrderDao orderDao;
  public OrderDao getOrderDao() {
  	return orderDao;
  }
  public void setOrderDao(OrderDao orderDao) {
  	this.orderDao = orderDao;
  }
}
java
public class OrderAction {
  private OrderService orderService;
  public OrderService getOrderService() {
  	return orderService;
  }
  public void setOrderService(OrderService orderService) {
  	this.orderService = orderService;
  }
}
xml
<beans>
	<bean id="orderAction" autowire="byName" class="com.hspedu.spring.action.OrderAction"></bean>
  <bean id="orderService" autowire="byName" class="com.hspedu.spring.service.OrderService"></bean>
  <bean id="orderDao" class="com.hspedu.spring.dao.OrderDao"></bean>
</beans>

基于xml的bean的自动装配 演示 特别说明: autowire = "byName" 会自动去找 id为setXxxx后面Xxxx的bean自动组装,如果找到就装配,如果找不到就报错, 比如这里的 bean id="orderAction" autowire="byName" class="com.hspedu.bean.OrderAction" 就会去找OrderAction 类中定义的 setOrderService 的id 为orderService的OrderService bean组装,找到就阻装,找不到就组装失败

spring_el 表达式

  1. Spring Expression Language,Spring 表达式语言,简称 SpEL。支持运行时查询并可以操作对象。
  2. 和 EL 表达式一样,SpEL 根据 JavaBean 风格的 getXxx()、setXxx()方法定义的属性访问对象
  3. SpEL 使用#{…}作为定界符,所有在大框号中的字符都将被认为是SpEL 表达式。
  4. 不是重点,如果看到有人这样使用,能看懂即可
java
public class SpELBean {
  private String name;
  private Monster monster;
  private String monsterName;
  private String crySound;
  private String bookName;
  private Double result;
  public SpELBean() {
  }
  public String getName() {
  	return name;
  }
  public void setName(String name) {
  	this.name = name;
  }
  public Monster getMonster() {
  	return monster;
  }
  public void setMonster(Monster monster) {
  	this.monster = monster;
  }
  public String getMonsterName() {
  	return monsterName;
  }
  public void setMonsterName(String monsterName) {
  	this.monsterName = monsterName;
  }
  public String getCrySound() {
  	return crySound;
  }
  public void setCrySound(String crySound) {
  	this.crySound = crySound;
  }
  public String getBookName() {
  	return bookName;
  }
  public void setBookName(String bookName) {
  	this.bookName = bookName;
  }
  public Double getResult() {
  	return result;
  }
  public void setResult(Double result) {
  	this.result = result;
  }
  public String cry(String sound) {
  	return "发出 " + sound + "叫声...";
  }
  public static String read(String bookName) {
  	return "正在看 " + bookName;
  }
}
xml
<bean id="spELBean" class="com.hspedu.spring.beans.SpELBean">
  <!-- sp el 给字面量 -->
  <property name="name" value="#{'韩顺平教育'}"/>
  <!-- sp el 引用其它 bean -->
  <property name="monster" value="#{monster01}"/>
  <!-- sp el 引用其它 bean 的属性值 -->
  <property name="monsterName" value="#{monster02.name}"/>
  <!-- sp el 调用普通方法 赋值 -->
  <property name="crySound" value="#{spELBean.cry('喵喵的..')}"/>
  <!-- sp el 调用静态方法 赋值 -->
  <property name="bookName" value="#{T(com.hspedu.spring.beans.SpELBean).read(' 天龙八部')}"/>
  <!-- sp el 通过运算赋值 -->
  <property name="result" value="#{89*1.2}"/>
</bean>
java
@Test
public void setProBySpel() {
  ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
  SpELBean spELBean = ioc.getBean("spELBean", SpELBean.class);
  System.out.println(spELBean.getName());
  System.out.println(spELBean.getMonster());
  System.out.println(spELBean.getMonsterName());
  System.out.println(spELBean.getCrySound());
  System.out.println(spELBean.getBookName());
  System.out.println(spELBean.getResult());
}

基于注解配置 bean

基于注解的方式配置 bean, 主要是项目开发中的组件,比如Controller、Service、和Dao.

组件注解的形式

  1. @Component 表示当前注解标识的是一个组件
  2. @Controller 表示当前注解标识的是一个控制器,通常用于Servlet
  3. @Service 表示当前注解标识的是一个处理业务逻辑的类,通常用于Service 类
  4. @Repository 表示当前注解标识的是一个持久化层的类,通常用于Dao 类

快速入门

引入 spring-aop-5.3.8.jar , 在 spring/libs 下拷贝即可

java
package com.hspedu.spring.component; 
@Repository
public class UserDao {
}
java
package com.hspedu.spring.component; 
@Service
public class UserService {
}
java
package com.hspedu.spring.component; 
@Controller
public class UserAction {
}
java
package com.hspedu.spring.component; 
@Component
public class MyComponent {
}
xml
<!-- 配置自动扫描的包,注意需要加入 context 名称空间 -->
<context:component-scan base-package="com.hspedu.spring.component" />
java
@Test
public void getBeanByAnnotation() {
  ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
  UserAction userAction = ioc.getBean(UserAction.class);
  System.out.println(userAction);
  UserDao userDao = ioc.getBean(UserDao.class);
  System.out.println(userDao);
  MyComponent myComponent = ioc.getBean(MyComponent.class);
  System.out.println(myComponent);
  UserService userService = ioc.getBean(UserService.class);
  System.out.println(userService);
}

注意事项和细节说明

  1. 需要导入 spring-aop-5.3.8.jar , 别忘了

  2. 必须在 Spring 配置文件中指定"自动扫描的包",IOC 容器才能够检测到当前项目中哪些类被标识了注解, 注意到导入 context 名称空间

    context:component-scan base-package="com.hspedu.spring.component" 可以使用通配符 * 来指定 ,比如 com.hspedu.spring.* 表示

    com.hspedu.spring.component 会不会去扫描它的子包? 答:会的

  3. Spring 的 IOC 容器不能检测一个使用了@Controller 注解的类到底是不是一个真正的控制器。注解的名称是用于程序员自己识别当前标识的是什么组件。其它的@Service@Repository 也是一样的道理 [也就是说 spring 的 IOC 容器只要检查到注解就会生成对象,但是这个注解的含义 spring 不会识别,注解是给程序员编程方便看的]

  4. resource-pattern="User*.class": 表示只扫描满足要求的类.[使用的少,不想扫描,不写注解就可以, 知道这个知识点即可]

  5. context:component-scan base-package="com.hspedu.spring.component"

    context:exclude-filter type="annotation"expression="org.springframework.stereotype.Service"/ /context

xml
<!-- 老韩解读
1. <context:exclude-filter> 放在<context:component-scan>内,表示扫描过滤掉当前包的某些类
2. type="annotation" 按照注解方式进行过滤. 
3. expression :就是注解的全类名,比如 org.springframework.stereotype.Service就是@Service 注解的全类名,其它比@Controller @Repository 等 依次类推
4. 上面表示过滤掉 com.hspedu.spring.component 包下,加入了@Service 注解的类-->
<context:component-scan base-package="com.hspedu.spring.component">
	<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
</context:component-scan>

img

img

  1. 指定自动扫描哪些注解类
xml
<!-- 
1. use-default-filters="false": 不再使用默认的过滤机制
2. context:include-filter: 表示只是扫描指定的注解的类
3. expression="org.springframework.stereotype.Controller": 注解的全类名-->
<context:component-scan base-package="com.hspedu.spring.component"use-default-filters="false">
  <context:include-filter type="annotation"expression="org.springframework.stereotype.Service"/>
  <context:include-filter type="annotation"expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

img

java
@Test
public void testBeanByIncludefilter() {
  ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
  System.out.println("----------");
  UserAction userAction = ioc.getBean(UserAction.class);
  System.out.println(userAction);
  UserService userService = ioc.getBean(UserService.class);
  System.out.println(userService);
  //下面这个就会报错
  UserDao userDao = ioc.getBean(UserDao.class);
  System.out.println(userDao);
}
  1. 默认情况:标记注解后,类名首字母小写作为 id 的值。也可以使用注解的value属性指定 id 值,并且 value 可以省略。[代码演示]
java
@Controller(value="userAction01")
@Controller("userAction01")
java
//修改 UserAction.java
@Controller("userAction01")
public class UserAction{
  
}
java
@Test
public void getBeanByAnnotationId() {
  ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
  //默认 id 获取
  // UserAction userAction = ioc.getBean("userAction",UserAction.class);
  // System.out.println(userAction);
  //指定 id 获取
  UserAction userAction01 = ioc.getBean("userAction01", UserAction.class);
  System.out.println(userAction01);
}
  1. 扩展-@Controller 、@Service、@Component 区别 : (回去看看一下老师的讲解的注解基础) https://zhuanlan.zhihu.com/p/454638478

手动开发简单的Spring基于注解配置的程序

自 己 写 一 个 简 单 的 Spring 容 器 , 通 过 读 取 类 的 注解(@Component @Controller@Service @Reponsitory),将对象注入到 IOC 容器

也就是说,不使用 Spring 原生框架,我们自己使用 IO+Annotaion+反射+集合技术实现, 打通 Spring 注解方式开发的技术痛点

思路分析

思路分析+程序结构

  1. 我们使用注解方式完成, 这里老韩不用 xml 来配置

  2. 程序框架图

img

代码实现

  1. 手动实现注解的方式来配置 Controller/Service/Respository/ Component
  2. 我们使用自定义注解来完成.
java
/**
* @author 韩顺平
* @version 1.0
* 定义我们的 ComponentScan 注解
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ComponentScan {
	String value();
}
java
/**
* @author 韩顺平
* @version 1.0
* 作用类似我们的 beans.xml 文件, 用于对 spring 容器指定配置信息*/
//指定要扫描的包
@ComponentScan("com.hspedu.spring.component")
   public class HspSpringConfig {
}
java
/**
 * @author 韩顺平
 * @version 1.0
 * HspSpringApplicationContext 类的作用类似Spring原生ioc容器
 */
public class HspSpringApplicationContext {
    private Class configClass;
    //ioc我存放的就是通过反射创建的对象(基于注解方式)
    private final ConcurrentHashMap<String, Object> ioc =
            new ConcurrentHashMap<>();

    //构造器
    public HspSpringApplicationContext(Class configClass) {

        this.configClass = configClass;
        //System.out.println("this.configClass=" + this.configClass);
        //获取要扫描的包
        //1. 先得到HspSpringConfig配置的的@ComponentScan(value = "com.hspedu.spring.component")
        ComponentScan componentScan =
                (ComponentScan) this.configClass.getDeclaredAnnotation(ComponentScan.class);
        //2. 通过componentScan的value=> 即要扫描的包
        String path = componentScan.value();
        System.out.println("要扫描的包= " + path);

        //得到要扫描的包下的所有资源(类 .class)
        //1.得到类的加载器
        ClassLoader classLoader =
                HspApplicationContext.class.getClassLoader();
        //2. 通过类的加载器获取到要扫描的包的资源 url=》类似一个路径
        path = path.replace(".", "/");//一定要把. 替换成 /
        URL resource =
                classLoader.getResource(path);
        System.out.println("resource=" + resource);
        //3. 将要加载的资源(.class) 路径下的文件进行遍历=>io
        File file = new File(resource.getFile());
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            for (File f : files) {
                System.out.println("=====================");
                System.out.println("=" + f.getAbsolutePath());
                //D:\hspedu_spring\spring\out\production\spring\com\hspedu\spring\component\UserService.class
                //获取到 com.hspedu.spring.component.UserService
                String fileAbsolutePath = f.getAbsolutePath();

                //这里我们只处理.class文件
                if (fileAbsolutePath.endsWith(".class")) {

                    //1. 获取到类名
                    String className =
                            fileAbsolutePath.substring(fileAbsolutePath.lastIndexOf("\\") + 1, fileAbsolutePath.indexOf(".class"));
                    //System.out.println("className=" + className);
                    //2. 获取类的完整的路径(全类名)
                    //老师解读 path.replace("/",".") => com.hspedu.spring.component.
                    String classFullName = path.replace("/", ".") + "." + className;
                    //System.out.println("classFullName=" + classFullName);

                    //3. 判断该类是不是需要注入容器, 就看该类是不是有注解 @Component @Service..
                    try {
                        //这时,我们就得到老该类的Class对象
                        //Class clazz = Class.forName(classFullName)
                        //老师说一下
                        //1. Class clazz = Class.forName(classFullName) 可以反射加载类
                        //2. classLoader.loadClass(classFullName); 可以反射类的Class
                        //3. 区别是 : 上面方式后调用来类的静态方法, 下面方法不会
                        //4. aClass.isAnnotationPresent(Component.class) 判断该类是否有 @Component
                        Class<?> aClass = classLoader.loadClass(classFullName);
                        if (aClass.isAnnotationPresent(Component.class) ||
                                aClass.isAnnotationPresent(Controller.class) ||
                                aClass.isAnnotationPresent(Service.class) ||
                                aClass.isAnnotationPresent(Repository.class)) {

                            //这里老师演示一个Component注解指定value,分配id
                            //老师就是演示了一下机制.
                            if(aClass.isAnnotationPresent(Component.class)) {
                                //获取到该注解
                                Component component = aClass.getDeclaredAnnotation(Component.class);
                                String id = component.value();
                                if(!"".endsWith(id)) {
                                    className = id;//替换
                                }
                            }

                            //这时就可以反射对象,并放入到容器中
                            Class<?> clazz = Class.forName(classFullName);
                            Object instance = clazz.newInstance();
                            //放入到容器中, 将类名的首字母小写作为id
                            //StringUtils

                            ioc.put(StringUtils.uncapitalize(className) , instance);
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }

    }

    //编写方法返回对容器中对象
    public Object getBean(String name) {
        return ioc.get(name);
    }
}
java
public class HspSpringAnnoationMain {
  public static void main(String[] args) {
    //创建我们的 spring 容器对象
    HspSpringApplicationContext hspSpringApplicationContext =
      new HspSpringApplicationContext(HspSpringConfig.class);
    //可以看看注入了哪些 bean. ConcurrentHashMap<String, Object> ioc =
    hspSpringApplicationContext.getIoc();
    //遍历一把
    Enumeration<String> keys = ioc.keys();
    for (String key : ioc.keySet()) {
      System.out.println("bean id= " + key + " bean 对象= " + ioc.get(key));
    }
    //指定获取 bean
    UserDao userDao =
      (UserDao) hspSpringApplicationContext.getBean("UserDao");
    //调用方法. userDao.hi();
  }
}

注意事项和细节说明

还可以通过@Component(value = "xx") @Controller(value = "yy") @Service(value="zz")中指定的 value, 给 bean 分配 id (上面的实现代码已经实现该功能)

java
/**
 * @author 韩顺平
 * @version 1.0
 * @Component 标识该类是一个组件, 是一个通用的注解
 */
@Component(value = "hsp1")
public class MyComponent {
}

自动装配

基于注解配置 bean,也可实现自动装配,使用的注解是:@AutoWired 或者@Resource

@AutoWired 的规则说明

  1. 在 IOC 容器中查找待装配的组件的类型,如果有唯一的bean 匹配,则使用该bean装配

  2. 如待装配的类型对应的 bean 在 IOC 容器中有多个,则使用待装配的属性的属性名作为 id 值再进行查找, 找到就装配,找不到就抛异常

@Resource 的规则说明

  1. @Resource 有两个属性是比较重要的,分是 name 和 type,Spring 将@Resource注解的name 属性解析为 bean 的名字,而 type 属性则解析为 bean 的类型.所以如果使用name属性,则使用 byName 的自动注入策略,而使用 type 属性时则使用byType 自动注入策略
  2. 如果@Resource 没有指定 name 和 type ,则先使用byName注入策略, 如果匹配不上, 再使用 byType 策略, 如果都不成功,就会报错

应用实例

java
@Service
public class UserService {
  public void hi(){
  	System.out.println("UserService hi()~");
  }
}
java
@Controller("userAction01")
public class UserAction {
  //@Autowired //自动装配 UserService, 这时是以 id=userService 的UserService对象进行组装. @Autowired
  private UserService userService;
  public void sayOk(){
  System.out.println("UserAction.userService= " + userService);
  	userService.hi();
  }
  //不写这个方法,也可以完成组装
  public void setUserService(UserService userService) {
    this.userService = userService;
  }
}
java
/**
* 基于注解的方式配置 bean-自动装配
*/
@Test
public void setProByAnnotationAutowired() {
  ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
  UserAction userAction01 = ioc.getBean(UserAction.class);
  userAction01.sayOk();
}

注意事项和细节说明

如待装配的类型对应的 bean 在 IOC 容器中有多个,则使用待装配的属性的属性名作为 id 值再进行查找, 找到就装配,找不到就抛异常

xml
<!-- 增加一个 UserService 对象-->
<bean id="userService02" class="com.hspedu.spring.component.UserService"/>
java
@Controller(value="userAction01")
// @Controller("userAction01")
public class UserAction {
  //@Autowired //自动装配 UserService, 这时是以 id=userService 的UserService对象进行组装.
  //@Autowired
  //指定 id 进行组装, 这时,是装配的 id=userService02 , 需要两个注解都需要写上@Autowired
  @Qualifier(value = "userService02")//指定 id=userService02 的UserService对象进行组装
  private UserService userService;
  public void sayOk(){
    System.out.println("UserAction.userService= " + userService);
    userService.hi();
  }
  public void setUserService(UserService userService) {
  	this.userService = userService;
  }
}
java
@Test
public void setProByAnnotationAutowired() {
  ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
  UserAction userAction01 = ioc.getBean(UserAction.class);
  userAction01.sayOk();
  UserService userService = ioc.getBean("userService", UserService.class);
  System.out.println("userService= " + userService);
  UserService userService02 = ioc.getBean("userService02", UserService.class);
  System.out.println("userService02= " + userService02);
}

泛型依赖注入

泛型依赖解释

  1. 为了更好的管理有继承和相互依赖的 bean 的自动装配,spring 还提供基于泛型依赖的注入机制
  2. 在继承关系复杂情况下,泛型依赖注入就会有很大的优越性

应用实例

img

传统方法是将 PhoneDao /BookDao 自动装配到 BookService/PhoneSerive 中,当这种继承关系多时,就比较麻烦,可以使用 spring 提供的泛型依赖注入

java
public class Book {
  
}
java
public class Phone {
  
}
java
public abstract class BaseDao<T> {
	public abstract void save();
}
java
@Repository
public class BookDao extends BaseDao<Book> {
  @Override
  public void save() {
  	System.out.println("BookDao 的 save()");
  }
}
java
@Repository
public class PhoneDao extends BaseDao<Phone> {
  @Override
  public void save() {
  	System.out.println("PhoneDao 的 save()");
  }
}
java
public class BaseService<T> {
  @Autowired
  private BaseDao<T> baseDao;
  public void save() {
  	baseDao.save();
  }
}
java
@Service
public class BookService extends BaseService<Book> {
  
}
java
@Service
public class PhoneService extends BaseService<Phone> {
  
}
xml
<context:component-scan base-package="com.hspedu.spring.depinjection"/>
java
/**
* 测试 spring 基于泛型依赖的 bean 的自动装配
*/
@Test
public void setProByDepinjectionAutowired() {
  ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
  BookService bookService = ioc.getBean(BookService.class);
  bookService.save();
  PhoneService phoneService = ioc.getBean(PhoneService.class);
  phoneService.save();
}