IOC
Spring 配置/管理 bean 介绍
Bean 管理包括两方面
- 创建 bean 对象 2. 给 bean 注入属性
Bean 配置方式
- 基于 xml 文件配置方式 2. 基于注解方式
基于 XML 配置 bean
通过id来获取 bean
Spring5基本介绍这一章中都是通过id来获取 bean,在此不再赘述。
通过类型来获取 bean
代码实现
<!--配置Monster,通过类型来获取-->
<bean class="com.lzw.spring.bean.Monster">
<!--
1.当我们给某个bean对象设置属性的时候
2.底层是使用对应的setter方法完成的, 比如setName()
3.如果没有这个方法,就会报错
-->
<property name="monsterID" value="100"/>
<property name="name" value="牛魔王"/>
<property name="skill" value="蛮牛冲撞"/>
</bean>
//通过Bean类型来获取对象
@Test
public void getBeanByType(){
ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
//直接传入class类型
Monster bean = ioc.getBean(Monster.class);
System.out.println(bean);
}
细节说明
- 按类型来获取 bean,要求 ioc 容器中的同一个类的 bean 只能有一个,否则会抛出异常 NoUniqueBeanDefinitionException。

这种方式的应用场景:比如 XxxAction/Servlet/Controller,或 XxxService 等在一个线程中只需要一个对象实例(单例)的情况。
在容器配置文件(比如 beans.xml)中给属性赋值, 底层是通过 setter 方法完成的,这也是为什么我们需要提供 setter 方法的原因。
通过构造器配置 bean
代码实现
<!--
配置Monster对象,并且指定构造器
1. constructor-arg标签可以指定使用构造器的参数
2. index表示构造器的第几个参数 从0开始计算的
3. 除了可以通过index 还可以通过 name / type 来指定参数方式
4. 解除大家的疑惑, 类的构造器,不能有完全相同类型和顺序的构造器,所以可以通过type来指定
-->
<bean id="monster03" class="com.lzw.spring.bean.Monster">
<constructor-arg value="200" index="0"/>
<constructor-arg value="白骨精" index="1"/>
<constructor-arg value="吸血" index="2"/>
</bean>
<bean id="monster04" class="com.lzw.spring.bean.Monster">
<constructor-arg value="200" name="monsterID"/>
<constructor-arg value="白骨精" name="name"/>
<constructor-arg value="吸血~" name="skill"/>
</bean>
<bean id="monster05" class="com.lzw.spring.bean.Monster">
<constructor-arg value="300" type="java.lang.Integer"/>
<constructor-arg value="白骨精~" type="java.lang.String"/>
<constructor-arg value="吸血~~" type="java.lang.String"/>
</bean>
//通过构造器来设置属性
@Test
public void getBeanByConstructor(){
ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
Monster monster03 = ioc.getBean("monster03",Monster.class);
System.out.println(monster03);
}
细节说明
- 通过 index 属性来区分是第几个参数。2. 通过 type 属性来区分是什么类型(按照顺序)。
通过 p 名称空间配置 bean
报错的时候,需要添加xmlns


代码实现
<bean id="monster06" class="com.lzw.spring.bean.Monster"
p:monsterID="500"
p:name="红孩儿"
p:skill="喷火"
/>
//通p名称空间来设置属性
@Test
public void getBeanByP(){
ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
Monster monster06 = ioc.getBean("monster06",Monster.class);
System.out.println(monster06);
}
引用/注入其它 bean 对象
在 spring 的 ioc 容器, 可以通过 ref 来实现 bean 对象的相互引用。
代码实现
package com.lzw.spring.dao;
/**
* @author LiAng
* DAO对象
*/
public class MemberDAOImpl {
//构造器
public MemberDAOImpl() {
System.out.println("MemberDAOImpl 构造器...");
}
//方法
public void add() {
System.out.println("MemberDAOImpl add()方法");
}
}
package com.lzw.spring.service;
import com.lzw.spring.dao.MemberDAOImpl;
/**
* @author LiAng
* Service类
*/
public class MemberServiceImpl {
private MemberDAOImpl memberDAO;
public MemberDAOImpl getMemberDAO() {
return memberDAO;
}
public void setMemberDAO(MemberDAOImpl memberDAO) {
this.memberDAO = memberDAO;
}
public void add(){
System.out.println("MemberServiceImpl add() 被调用");
memberDAO.add();
}
}
<!--配置MemberDAOImpl对象
1. ref="memberDAO"表示 MemberServiceImpl对象属性memberDAO引用的对象是id=memberDAO的对象
2. 这里就体现出spring容器的依赖注入
3. 注意再spring容器中, 他是作为一个整体来执行的, 即如果你引用到一个bean对象, 对你配置的顺序没有要求
4. 建议还是按顺序,好处是阅读的时候,比较方便
-->
<bean class="com.lzw.spring.dao.MemberDAOImpl" id="memberDAO"/>
<!--配置MemberServiceImpl-->
<bean class="com.lzw.spring.service.MemberServiceImpl" id="memberService">
<property name="memberDAO" ref="memberDAO"/>
</bean>
//通过ref设置bean属性
@Test
public void setBeanByRef(){
ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
MemberServiceImpl monsterService = ioc.getBean("memberService", MemberServiceImpl.class);
monsterService.add();
}

引用/注入内部 bean 对象
代码实现
<!--配置MemberServiceImpl对象-使用内部bean-->
<bean class="com.lzw.spring.service.MemberServiceImpl" id="memberService2">
<!--自己配置一个内部bean-->
<property name="memberDAO">
<bean class="com.5lzw.spring.dao.MemberDAOImpl"/>
</property>
</bean>
//通过内部bean设置属性
@Test
public void setBeanByPro(){
ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
MemberServiceImpl monsterServi5ce2 = ioc.getBean("memberService2", MemberServiceImpl.class);
monsterService2.add();
}
引用/注入集合/数组类型
代码实现
package com.lzw.spring.bean;
import java.util.*;
/**
* @author LiAng
*/
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 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 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 Properties getPros() {
return pros;
}
public void setPros(Properties pros) {
this.pros = pros;
}
@Override
public String toString() {
return "Master{" +
"name='" + name + '\'' +
", monsterList=" + monsterList +
", monsterMap=" + monsterMap +
", monsterSet=" + monsterSet +
", monsterName=" + Arrays.toString(monsterName) +
", pros=" + pros +
'}';
}
}
<!--配置Master对象
体会 spring 容器配置特点 依赖注入-非常灵活
-->
<bean class="com.lzw.spring.bean.Master" id="master">
<property name="name" value="太上老君"/>
<!--给list属性赋值-->
<property name="monsterList">
<list>
<!--引用其他bean-->
<ref bean="monster01"/>
<ref bean="monster02"/>
<!--内部bean-->
<bean class="com.lzw.spring.bean.Monster">
<property name="name" value="老鼠精"/>
<property name="monsterID" value="100"/>
<property name="skill" value="吃粮食"/>
</bean>
</list>
</property>
<!--给map属性赋值-->
<property name="monsterMap">
<map>
<entry>
<key>
<value>monster03</value>
</key>
<!--这里使用外部bean引入-->
<ref bean="monster03"/>
</entry>
<entry>
<key>
<value>monster04</value>
</key>
<ref bean="monster04"/>
</entry>
</map>
</property>
<!--给set属性赋值-->
<property name="monsterSet">
<set>
<ref bean="monster05"/>
<ref bean="monster06"/>
<bean class="com.lzw.spring.bean.Monster">
<property name="name" value="金角大王"/>
<property name="skill" value="吐水"/>
<property name="monsterID" value="666"/>
</bean>
</set>
</property>
<!--给数组属性赋值
array标签中使用 value 还是 bean , ref .. 要根据你的业务决定
-->
<property name="monsterName">
<array>
<value>小妖怪</value>
<value>大妖怪</value>
<value>老妖怪</value>
</array>
</property>
<!--给Properties属性赋值 结构k(String)-v(String)-->
<property name="pros">
<props>
<prop key="username">root</prop>
<prop key="password">123456</prop>
<prop key="ip">127.0.0.1</prop>
</props>
</property>
</bean>
@Test
public void setBeanByCollection(){
ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
Master master = ioc.getBean("master", Master.class);
System.out.println(master);
}
细节说明
- 主要掌握List/Map/Properties三种集合的使用
- Properties集合的特点。
(1)Properties 是 Hashtable 的子类 , 是 key-value 的形式。(2)key 和 value 都是 String。
通过 util 名称空间创建 list
代码实现
package com.lzw.spring.bean;
import java.util.List;
/**
* @author LiAng
*/
public class BookStore {
private List<String> bookList;
public BookStore() {
}
public List<String> getBookList() {
return bookList;
}
public void setBookList(List<String> bookList) {
this.bookList = bookList;
}
@Override
public String toString() {
return "BookStore{" +
"bookList=" + bookList +
'}';
}
}
<!--定义一个util:list 并且指定id 可以达到数据复用
在使用util:list 名称空间时候,需要引入相应的标签, 一般来说通过alt+enter会自动加入
, 如果没有就手动添加一下即可.
-->
<util:list id="myBookList">
<value>三国演义</value>
<value>红楼梦</value>
<value>西游记</value>
<value>水浒传</value>
</util:list>
<!--配置BookStore对象-->
<bean class="com.lzw.spring.bean.BookStore" id="bookStore">
<property name="bookList" ref="myBookList">
</property>
</bean>
//通过util:list名称空间给属性赋值
@Test
public void setBeanByUtilList(){
ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
BookStore bookStore = ioc.getBean("bookStore", BookStore.class);
System.out.println(bookStore);
}
级联属性赋值
spring 的 ioc 容器, 可以直接给对象属性的属性赋值, 即级联属性赋值。
代码实现
package com.lzw.spring.bean;
/**
* @author LiAng
* 部门类
*/
public class Dept {
private String name;
public Dept() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Dept{" +
"name='" + name + '\'' +
'}';
}
}
package com.lzw.spring.bean;
/**
* @author LiAng
* 员工类
*/
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;
}
@Override
public String toString() {
return "Emp{" +
"name='" + name + '\'' +
", dept=" + dept +
'}';
}
}
<!--配置Dept对象-->
<bean class="com.lzw.spring.bean.Dept" id="dept"/>
<!--配置Emp对象-->
<bean class="com.lzw.spring.bean.Emp" id="emp">
<property name="name" value="jack"/>
<property name="dept" ref="dept"/>
<!--这里我希望给dept的name属性指定值[级联属性赋值]-->
<property name="dept.name" value="Java开发部门"/>
</bean>
//给属性进行级联赋值
@Test
public void setBeanByRelation() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
Emp emp = ioc.getBean("emp", Emp.class);
System.out.println("emp=" + emp);
}

通过静态工厂获取bean
在 spring 的 ioc 容器,可以通过静态工厂获取 bean 对象。
代码实现
package com.lzw.spring.factory;
import com.lzw.spring.bean.Monster;
import java.util.HashMap;
import java.util.Map;
/**
* @author LiAng
* 静态工厂类-可以返回Monster对象
*/
public class MyStaticFactory {
private static Map<String, Monster> monsterMap;
//使用static代码块进行初始化
static {
monsterMap = new HashMap<String, Monster>();
monsterMap.put("monster_01", new Monster(100, "黄袍怪", "一阳指"));
monsterMap.put("monster_02", new Monster(200, "九头金雕", "如来神掌"));
}
//提供一个方法,返回Monster对象
public static Monster getMonster(String key) {
return monsterMap.get(key);
}
}
<!--配置monster对象,通过静态工厂获取
1. 通过静态工厂获取/配置bean
2. class 是静态工厂类的全路径
3. factory-method 表示是指定静态工厂类的哪个方法返回对象
4. constructor-arg value="monster02" value是指定要返回静态工厂的哪个对象
-->
<bean id="my_monster01" class="com.lzw.spring.factory.MyStaticFactory" factory-method="getMonster">
<constructor-arg value="monster_02"/>
</bean>
<bean id="my_monster02" class="com.lzw.spring.factory.MyStaticFactory" factory-method="getMonster">
<constructor-arg value="monster_02"/>
</bean>
//通过静态工厂获取bean
@Test
public void getBeanByStaticFactory() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
Monster my_monster01 = ioc.getBean("my_monster01", Monster.class);
Monster my_monster02 = ioc.getBean("my_monster02", Monster.class);
System.out.println("my_monster01=" + my_monster01);
System.out.println(my_monster01 == my_monster02);//true
}

通过实例工厂获取对象
在 spring 的 ioc 容器,可以通过实例工厂获取 bean 对象。
代码实现
package com.lzw.spring.factory;
import com.lzw.spring.bean.Monster;
import java.util.HashMap;
import java.util.Map;
/**
* @author LiAng
* 实例工厂类
*/
public class MyInstanceFactory {
private Map<String, Monster> monster_map;
//通过普通代码块进行初始化
{
monster_map = new HashMap<>();
monster_map.put("monster_03", new Monster(300, "牛魔王~", "芭蕉扇~"));
monster_map.put("monster_04", new Monster(400, "狐狸精~", "美人计~"));
}
//写一个方法返回Monster对象
public Monster getMonster(String key) {
return monster_map.get(key);
}
}
<!--配置2个实例工厂对象-->
<bean class="com.lzw.spring.factory.MyInstanceFactory" id="myInstanceFactory"/>
<bean class="com.lzw.spring.factory.MyInstanceFactory" id="myInstanceFactory2"/>
<!--配置monster对象, 通过实例工厂
1. factory-bean 指定使用哪个实例工厂对象返回bean
2. factory-method 指定使用实例工厂对象的哪个方法返回bean
3. constructor-arg value="monster03" 指定获取到实例工厂中的哪个monster
-->
<bean id="my_monster03" factory-bean="myInstanceFactory" factory-method="getMonster">
<constructor-arg value="monster_03"/>
</bean>
<bean id="my_monster04" factory-bean="myInstanceFactory2" factory-method="getMonster">
<constructor-arg value="monster_03"/>
</bean>
//通过实例工厂获取bean
@Test
public void getBeanByInstanceFactory() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
Monster my_monster03 = ioc.getBean("my_monster03", Monster.class);
Monster my_monster04 = ioc.getBean("my_monster04", Monster.class);
System.out.println("my_monster03=" + my_monster03);
System.out.println("my_monster04=" + my_monster04);
System.out.println(my_monster03 == my_monster04);//false
}

通过 FactoryBean 获取对象(重点)
在 spring 的 ioc 容器,通过 FactoryBean 获取 bean 对象(重点)。
代码实现
package com.lzw.spring.factory;
import com.lzw.spring.bean.Monster;
import org.springframework.beans.factory.FactoryBean;
import java.util.HashMap;
import java.util.Map;
/**
* @author LiAng
* 是一个FactoryBean
*/
public class MyFactoryBean implements FactoryBean<Monster> {
//这个就是你配置时候,指定要获取的对象对应key
private String key;
private Map<String, Monster> monster_map;
{ //代码块,完成初始化
monster_map = new HashMap<>();
monster_map.put("monster03", new Monster(300, "牛魔王~", "芭蕉扇~"));
monster_map.put("monster04", new Monster(400, "狐狸精~", "美人计~"));
}
public void setKey(String key) {
this.key = key;
}
@Override
public Monster getObject() throws Exception {
return monster_map.get(key);
}
@Override
public Class<?> getObjectType() {
return Monster.class;
}
@Override
public boolean isSingleton() {//这里指定返回是否是单例
return false;
}
}
<!--配置monster对象,通过FactoryBean获取
1. class 指定使用的FactoryBean
2. key表示就是 MyFactoryBean 属性key
3. value就是你要获取的对象对应key
-->
<bean id="my_monster05" class="com.lzw.spring.factory.MyFactoryBean">
<property name="key" value="monster04"/>
</bean>
//通过FactoryBean获取bean
@Test
public void getBeanByFactoryBean() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
Monster my_monster05 = ioc.getBean("my_monster05", Monster.class);
System.out.println("my_monster05=" + my_monster05);
}

bean 配置信息重用(继承)
在 spring 的 ioc 容器,提供了一种继承的方式来实现 bean 配置信息的重用。
代码实现
<!--配置Monster对象-->
<bean id="monster10" class="com.lzw.spring.bean.Monster">
<property name="monsterID" value="10"/>
<property name="name" value="蜈蚣精"/>
<property name="skill" value="蜇人"/>
</bean>
<!--
1. 配置Monster对象
2.但是这个对象的属性值和 id="monster10"对象属性一样
3.parent="monster10" 指定当前这个配置的对象的属性值从 id=monster10的对象来
-->
<bean id="monster11" class="com.lzw.spring.bean.Monster" parent="monster10"/>
<!--配置Monster对象
1. 如果bean指定了 abstract="true", 表示该bean对象, 是用于被继承
2. 本身这个bean就不能被获取/实例化
-->
<bean id="monster12" class="com.lzw.spring.bean.Monster" abstract="true">
<property name="monsterID" value="100"/>
<property name="name" value="蜈蚣精~"/>
<property name="skill" value="蜇人~"/>
</bean>
<bean id="monster13" class="com.lzw.spring.bean.Monster" parent="monster12"/>
//配置Bean通过继承
@Test
public void getBeanByExtends() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
Monster monster11 = ioc.getBean("monster11", Monster.class);
System.out.println("monster11=" + monster11);
Monster monster13 = ioc.getBean("monster13", Monster.class);
System.out.println("monster13=" + monster13);
}

bean 创建顺序
说明
- 在 spring 的 ioc 容器,默认是按照配置的顺序创建 bean 对象
<bean id="student01" class="com.lzw.bean.Student" />
<bean id="department01" class="com.lzw.bean.Department" />
会先创建 student01 这个 bean 对象,然后创建 department01 这个 bean 对象。
- 如果这样配置
<bean id="student01" class="com.lzw.bean.Student" depends-on="department01"/>
<bean id="department01" class="com.lzw.bean.Department" />
会先创建 department01 对象,再创建 student01 对象。
package com.lzw.spring.bean;
/**
* @author LiAng
*/
public class Department {
public Department() {
System.out.println("Department() 被执行...");
}
}
package com.lzw.spring.bean;
/**
* @author LiAng
*/
public class Student {
public Student() {
System.out.println("Student()构造器被执行..");
}
}

1在默认情况下,bean创建的顺序是按照配置顺序来的。但如果增加了 depends-on 属性,这时就会先创建有这个属性的bean。
两个问题
- 看下面的配置,请问两个 bean 创建的顺序是什么?并分析执行流程。
<bean class="com.lzw.spring.dao.MemberDAOImpl" id="memberDAO"/>
<bean class="com.lzw.spring.service.MemberServiceImpl" id="memberService">
<property name="memberDAO" ref="memberDAO"/>
</bean>
(1)先创建 id=memberDAO
(2)再创建 id = memberService
(3)调用 memberServiceImpl.setMemberDAO() 完成引用
- 看下面的配置,请问两个 bean 创建的顺序是什么?并分析执行流程。
<bean class="com.lzw.spring.service.MemberServiceImpl" id="memberService">
<property name="memberDAO" ref="memberDAO"/>
</bean>
<bean class="com.lzw.spring.dao.MemberDAOImpl" id="memberDAO"/>
(1)先创建 id = memberService
(2)再创建 id=memberDAO
(3)调用 memberServiceImpl.setMemberDAO() 完成引用
bean 对象的单例和多例(重要)
说明
在 spring 的 ioc 容器,在默认是按照单例创建的,即配置一个 bean 对象后,ioc 容器只会创建一个 bean 实例。
如果我们希望 ioc 容器配置的某个 bean 对象,是以多个实例形式创建的则可以通过配置 scope="prototype" 来指定。
代码演示
package com.lzw.spring.bean;
/**
* @author LiAng
*/
public class Cat {
private Integer id;
private String name;
public Cat() {
System.out.println("Cat()构造器执行了");
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Cat{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
<!--配置Cat对象
1. 在默认情况下 scope属性是 singleton
2. 在ioc容器中, 只有一个这个bean对象
3. 当程序员执行getBean时, 返回的的是同一个对象
4. 如果我们希望每次getBean返回一个新的Bean对象,则可以scope="prototype"
5. 如果bean的配置是 scope="singleton" lazy-init="true" 这时,ioc容器就不会提前创建该对象,
而是当执行getBean方法的时候,才会创建对象
-->
<bean id="cat" class="com.lzw.spring.bean.Cat" scope="prototype" lazy-init="false">
<property name="id" value="100"/>
<property name="name" value="小花猫"/>
</bean>
//测试Scope
@Test
public void testBeanScope() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
Cat cat = ioc.getBean("cat", Cat.class);
Cat cat2 = ioc.getBean("cat", Cat.class);
System.out.println("cat=" + cat);
System.out.println("cat2=" + cat2);
System.out.println(cat == cat2);//false
}
使用细节
默认是单例 singleton,在启动容器时,默认就会创建,并放入到 singletonObjects 集合。
当
<bean scope="prototype" >
设置为多实例机制后, 该 bean 是在 getBean()时才创建。
如果是单例singleton,同时希望在getBean时才创建,可以指定懒加载 lazy-init="true" (注意默认是 false)。
通常情况下,lazy-init 就使用默认值 false,在开发看来,用空间换时间是值得的,除非有特殊的要求。
如果 scope="prototype" 这时 lazy-init 属性的值不管是 ture,还是 false 都是在 getBean 时候,才创建对象。
bean 的生命周期
说明
bean 对象创建是由 JVM 完成的,然后执行如下方法。
- 执行构造器
- 执行 set 相关方法
- 调用 bean 的初始化的方法(需要配置)
- 使用 bean
- 当容器关闭时候,调用 bean 的销毁方法(需要配置)
代码演示
package com.lzw.spring.bean;
/**
* @author LiAng
*/
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()=" + name);
this.name = name;
}
//1. 这个方法是程序员来编写的.
//2. 根据自己的业务逻辑来写.
public void init() {
System.out.println("House init()..");
}
//1. 这个方法是程序员来编写的.
//2. 根据自己的业务逻辑来写.
//3. 名字也不是固定的
public void destroy() {
System.out.println("House destroy()..");
}
@Override
public String toString() {
return "House{" +
"name='" + name + '\'' +
'}';
}
}
<!--配置House对象,演示整个Bean的生命周期
1. init-method="init" 指定bean的初始化方法 , 在setter方法后执行
2. init方法执行的时机,由spring容器来控制
3. destroy-method="destroy" 指定bean的销毁方法, 在容器关闭的时候执行
4. destroy方法执行的时机,由spring容器来控制
-->
<bean class="com.lzw.spring.bean.House" id="house" init-method="init" destroy-method="destroy">
<property name="name" value="大豪宅"/>
</bean>
//测试Bean的生命周期
@Test
public void testBeanLife() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
House house = ioc.getBean("house", House.class);
System.out.println("house=" + house);
//关闭容器
//1. ioc的编译类型 ApplicationContext , 运行类型 ClassPathXmlApplicationContext
//2. 因为ClassPathXmlApplicationContext 实现了 ConfigurableApplicationContext
//3. ClassPathXmlApplicationContext 是有close
//4. 将ioc 转成ClassPathXmlApplicationContext,再调用close
//ioc.close();
//关闭ioc容器.
((ClassPathXmlApplicationContext)ioc).close();
}

使用细节
- 初始化 init 方法和 destory 方法,是程序员来指定。2. 销毁方法就是当关闭容器时,才会被调用。
*配置 bean 的后置处理器(重难点)
说明
- 在 spring 的 ioc 容器,可以配置 bean 的后置处理器。
- 该处理器/对象会在 bean 初始化方法(init)调用前和初始化方法调用后被调用 。
- 程序员可以在后置处理器中编写自己的代码。
代码演示
package com.lzw.spring.bean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
/**
* @author LiAng
* 这是一个后置处理器, 需要实现 BeanPostProcessor接口
*/
public class MyBeanPostProcessor implements BeanPostProcessor {
/**
* 什么时候被调用:在Bean的init方法前被调用
* @param bean 传入的在IOC容器中创建/配置Bean
* @param beanName 传入的在IOC容器中创建/配置Bean的id
* @return 程序员对传入的bean 进行修改/处理【如果有需要的话】 ,返回
* @throws BeansException
*/
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("postProcessBeforeInitialization().. bean=" + bean + " beanName=" + beanName);
//初步体验案例: 如果类型是House的统一改成 上海豪宅
//对多个对象进行处理/编程==>切面编程
if(bean instanceof House) {
((House)bean).setName("上海豪宅~");
}
return bean;
}
/**
* 什么时候被调用: 在Bean的init方法后被调用
* @param bean 传入的在IOC容器中创建/配置Bean
* @param beanName 传入的在IOC容器中创建/配置Bean的id
* @return 程序员对传入的bean 进行修改/处理【如果有需要的话】 ,返回
* @throws BeansException
*/
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("postProcessAfterInitialization().. bean=" + bean + " beanName=" + beanName);
return bean;
}
}
beans02.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">
<!--配置House对象-->
<bean class="com.lzw.spring.bean.House" id="house" init-method="init" destroy-method="destroy">
<property name="name" value="大豪宅"/>
</bean>
<bean class="com.lzw.spring.bean.House" id="house02" init-method="init" destroy-method="destroy">
<property name="name" value="香港豪宅"/>
</bean>
<!--配置后置处理器对象
1. 在beans02.xml 容器配置文件 配置了 MyBeanPostProcessor
2. 这时后置处理器对象,就会作用在该容器创建的Bean对象
3. 已经是针对所有对象编程->切面编程AOP
-->
<bean class="com.lzw.spring.bean.MyBeanPostProcessor" id="myBeanPostProcessor"/>
</beans>
@Test
public void testBeanPostProcessor() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans02.xml");
House house = ioc.getBean("house", House.class);
System.out.println("house=" + house);
House house02 = ioc.getBean("house02", House.class);
System.out.println("使用house02=" + house02);
((ConfigurableApplicationContext)ioc).close();
}


其他说明
1、怎么执行到这个方法?=> 使用 AOP(反射+动态代理+IO+容器+注解) 。
2、有什么用?=> 可以对 IOC 容器中所有的对象进行统一处理,比如 日志处理/权限的校 验/安全的验证/事务管理。
--初步体验案例:如果类型是 House 的统一改成 上海豪宅 。
3、针对容器的所有对象吗?是的=>切面编程特点 。
4、后面会手动实现这个底层机制。
通过属性文件给 bean 注入值
说明
在 spring 的 ioc 容器,通过属性文件给 bean 注入值。
代码演示
myproperties
monsterId=1000
name=jack
skill=hello
<?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 https://www.springframework.org/schema/context/spring-context.xsd">
<!--指定属性文件在哪里
location="classpath:my.properties" 指定文件位置需要带上 classpath
-->
<context:property-placeholder location="classpath:my.properties" />
<!--配置Monster对象
1.通过属性文件给monster对象的属性赋值
2.这时我们的属性值通过${属性名}
3.这里的属性名 就是 my.properties文件中的key
-->
<bean class="com.lzw.spring.bean.Monster" id="monster1000">
<property name="monsterID" value="${monsterId}"/>
<property name="skill" value="${skill}"/>
<property name="name" value="${name}"/>
</bean>
</beans>
//通过属性文件给bean属性赋值
@Test
public void setBeanByFile() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans03.xml");
Monster monster1000 = ioc.getBean("monster1000", Monster.class);
System.out.println("monster1000=" + monster1000);
}
如果properites配置文件中有中文,访问站长工具(https://tool.chinaz.com/)进行Unicode进行转码,将转后的编码复制到properties文件中。



基于 XML 的 bean 的自动装配
代码演示
package com.lzw.spring.dao;
/**
* @author LiAng
* DAO类
*/
public class OrderDao {
public void saveOrder() {
System.out.println("保存 一个订单...");
}
}
package com.lzw.spring.service;
import com.lzw.spring.dao.OrderDao;
/**
* @author LiAng
* OrderService
*/
public class OrderService {
private OrderDao orderDao;
public OrderDao getOrderDao() {
return orderDao;
}
public void setOrderDao(OrderDao orderDao) {
this.orderDao = orderDao;
}
}
package com.lzw.spring.web;
import com.lzw.spring.service.OrderService;
/**
* @author LiAng
* 这里的Action就是 Servlet->充当Controller
*/
public class OrderAction {
private OrderService orderService;
public OrderService getOrderService() {
return orderService;
}
public void setOrderService(OrderService orderService) {
this.orderService = orderService;
}
}
<!--配置OrderDao对象-->
<bean class="com.lzw.spring.dao.OrderDao" id="orderDao"/>
<!--配置OrderService对象
1. autowire="byType" 表示 在创建 orderService时
通过类型的方式 给对象属性 自动完成赋值/引用
2. 比如OrderService 对象有 private OrderDao orderDao
3. 就会在容器中去找有没有 OrderDao类型对象
4. 如果有,就会自动的装配, 如果是按照 byType 方式来装配, 这个容器中,不能有两个OrderDao类型对象
5. 如果对象没有属性, autowire就没有必要写
6. 其它类推..
7. 如果设置的是 autowire="byName" 表示通过名字完成自动装配
8. 比如下面的 autowire="byName" class="com.lzw.spring.service.OrderService"
1) 先看 OrderService 属性 private OrderDao orderDao(并不是根据这个"orderDao"为id进行装配)
2) 再根据这个属性的setXxx()方法的 xxx 来找对象id(是根据这里的"xxx"来装配)
3) public void setOrderDao() 就会找id=orderDao对象来进行自动装配(这里的"orderDao"就是2中的xxx)
4) 如果没有就装配失败
-->
<bean autowire="byType" class="com.lzw.spring.service.OrderService" id="orderService"/>
<!--<bean autowire="byName" class="com.lzw.spring.service.OrderService" id="orderService"/>-->
<!--配置OrderAction-->
<bean autowire="byType" class="com.lzw.spring.web.OrderAction" id="orderAction"/>
<!--<bean autowire="byName" class="com.lzw.spring.web.OrderAction" id="orderAction"/>-->
//通过自动装配来对属性赋值
@Test
public void setBeanByAutowire() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans03.xml");
OrderAction orderAction = ioc.getBean("orderAction", OrderAction.class);
//验证是否自动装配上OrderService
System.out.println(orderAction.getOrderService());
//验证是否自动装配上OrderDao
System.out.println(orderAction.getOrderService().getOrderDao());
}
Spring EL 表达式配置Bean(了解)
说明
Spring Expression Language,Spring 表达式语言,简称 SpEL。支持运行时查询并可以操作对象。
和 EL 表达式一样,SpEL 根据 JavaBean 风格的 getXxx()、setXxx()方法定义的属性访问对象。
SpEL 使用#{…}作为定界符,所有在大框号中的字符都将被认为是 SpEL 表达式。
不是重点,如果看到有人这样使用,能看懂即可。
代码演示
package com.lzw.spring.bean;
/**
* @author LiAng
* @time 2022/6/7 22:15
*/
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;
}
//cry 方法会返回字符串
public String cry(String sound) {
return "发出 " + sound + "叫声...";
}
//read 返回字符串
public static String read(String bookName) {
return "正在看 " + bookName;
}
@Override
public String toString() {
return "SpELBean{" +
"name='" + name + '\'' +
", monster=" + monster +
", monsterName='" + monsterName + '\'' +
", crySound='" + crySound + '\'' +
", bookName='" + bookName + '\'' +
", result=" + result +
'}';
}
}
<?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">
<!--配置一个monster对象-->
<bean id="monster01" class="com.lzw.spring.bean.Monster">
<property name="monsterID" value="100"/>
<property name="name" value="蜈蚣精~"/>
<property name="skill" value="蜇人~"/>
</bean>
<!-- spring el 表达式使用
通过spel给bean的属性赋值
-->
<bean id="spELBean" class="com.lzw.spring.bean.SpELBean">
<!-- sp el 给字面量 -->
<property name="name" value="#{'LiAng'}"/>
<!-- sp el 引用其它bean -->
<property name="monster" value="#{monster01}"/>
<!-- sp el 引用其它bean的属性值 -->
<property name="monsterName" value="#{monster01.name}"/>
<!-- sp el 调用普通方法(返回值) 赋值 -->
<property name="crySound" value="#{spELBean.cry('喵喵的..')}"/>
<!-- sp el 调用静态方法(返回值) 赋值 -->
<property name="bookName" value="#{T(com.lzw.spring.bean.SpELBean).read('天龙八部')}"/>
<!-- sp el 通过运算赋值 -->
<property name="result" value="#{89*1.2}"/>
</bean>
</beans>
//通过spring el 对属性赋值
@Test
public void setBeanBySpel() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans04.xml");
SpELBean spELBean = ioc.getBean("spELBean", SpELBean.class);
System.out.println("spELBean=" + spELBean);
}
*基于注解配置 bean(重点)
基本使用
说明
基本介绍
基于注解的方式配置 bean,主要是项目开发中的组件,比如 Controller、Service 和 Dao。
组件注解的形式:
- @Component 表示当前注解标识的是一个组件 。
- @Controller 表示当前注解标识的是一个控制器,通常用于 Servlet 。
- @Service 表示当前注解标识的是一个处理业务逻辑的类,通常用于 Service 类。
- @Repository 表示当前注解标识的是一个持久化层的类,通常用于 Dao 类。
快速入门
使用注解的方式来配置 Controller / Service / Respository / Component。
首先要引入 spring-aop-5.3.8.jar。

package com.lzw.spring.component;
import org.springframework.stereotype.Repository;
/**
* @author LiAng
*/
@Repository
public class UserDao {
}
package com.lzw.spring.component;
import org.springframework.stereotype.Service;
/**
* @author LiAng
* @Service 标识该类是一个Service类/对象
*/
@Service
public class UserService {
}
package com.lzw.spring.component;
import org.springframework.stereotype.Controller;
/**
* @author LiAng
* @Controller 标识该类是一个控制器Controller, 通常这个类是一个Servlet
*/
@Controller
public class UserAction {
}
package com.lzw.spring.component;
import org.springframework.stereotype.Component;
/**
* @author LiAng
* @Component 标识该类是一个组件, 是一个通用的注解
*/
@Component(value = "lzw1")
public class MyComponent {
}
<?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 https://www.springframework.org/schema/context/spring-context.xsd">
<!--配置容器要扫描的包
1. component-scan 要对指定包下的类进行扫描, 并创建对象到容器
2. base-package 指定要扫描的包
3. 含义是当spring容器创建/初始化时,就会扫描com.lzw.spring.component包
下的所有的 有注解 @Controller / @Service / @Repository / @Component类
将其实例化,生成对象,放入到ioc容器
-->
<context:component-scan base-package="com.lzw.spring.component"/>
</beans>
//通过注解来配置Bean
@Test
public void setBeanByAnnotation() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans05.xml");
UserDao userDao = ioc.getBean(UserDao.class);
UserService userService = ioc.getBean(UserService.class);
UserAction userAction = ioc.getBean(UserAction.class);
MyComponent myComponent = ioc.getBean(MyComponent.class);
System.out.println("userDao=" + userDao);
System.out.println("userService=" + userService);
System.out.println("userAction=" + userAction);
System.out.println("myComponent=" + myComponent);
}
注意事项和细节说明
- 需要导入 spring-aop-5.3.8.jar。
- 必须在 Spring 配置文件中指定"自动扫描的包",IOC 容器才能够检测到当前项目中哪些类被标识了注解, 注意导入 context 名称空间。
<context:component-scan base-package="com.lzw.spring.component"/>
<!--resource-pattern="User*.class" 表示只扫描com.lzw.spring.component 和它的子包下的User打头的类-->
可以使用通配符 * 来指定 ,比如 com.lzw.spring.* 表示。
Spring 的 IOC 容器不能检测一个使用了@Controller 注解的类到底是不是一个真正的控制器。注解的名称是用于程序员自己识别当前标识的是什么组件。其它的@Service @Repository 也是一样的道理 [也就是说 Spring 的 IOC 容器只要检查到注解就会生成对象, 但是这个注解的含义 Spring 不会识别,注解是给程序员编程方便看的]。
<context:component-scan base-package="com.lzw.spring.component" resource-pattern="User*.class"/>
resource-pattern="User*.class":表示只扫描满足要求的类[使用的少,不想扫描,不写注解就可以,知道这个知识点即可]。
排除哪些类,以 annotaion 注解为例。
<!-- 需求:如果我们希望排除某个包/子包下的某种类型的注解,可以通过exclude-filter来指定 1. context:exclude-filter 指定要排除哪些类 2. type 指定排除方式 annotation表示按照注解来排除 3. expression="org.springframework.stereotype.Service" 指定要排除的注解的全路径 --> <context:component-scan base-package="com.lzw.spring.component"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/> </context:component-scan>
指定自动扫描哪些注解类。
<!-- 需求:如果我们希望按照自己的规则,来扫描包/子包下的某些注解, 可以通过 include-filter 1. use-default-filters="false" 表示不使用默认的过滤机制/扫描机制 2. context:include-filter 表示要去扫描哪些类 3. type="annotation" 按照注解方式来扫描/过滤 4. expression="org.springframework.stereotype.Service" 指定要扫描的注解的全路径 --> <context:component-scan base-package="com.lzw.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:include-filter type="annotation" expression="org.springframework.stereotype.Repository"/> </context:component-scan>
默认情况:标记注解后,类名首字母小写作为 id 的值。也可以使用注解的 value 属性指定 id 值,并且 value 可以省略。
@Repository public class UserDao { }

/**
* @author LiAng
* 使用 @Repository 标识该类是一个Repository 即是一个持久化层的类/对象
* 1. 标记注解后,类名首字母小写作为id的值(默认)
* 2. value = "lzwUserDao" 使用指定的 lzwUserDao作为UserDao对象的id
*/
@Repository(value = "lzwUserDao")
public class UserDao {
}

- 扩展-@Controller 、@Service、@Component 区别 https://zhuanlan.zhihu.com/p/454638478
手动开发简单的 Spring 基于注解配置的程序
需求说明
自己写一个简单的Spring容器,通过读取类的注解 (@Component @Controller @Service @Reponsitory),将对象注入到 IOC 容器。
也就是说,不使用 Spring 原生框架,我们自己使用 IO+Annotaion+反射+集合 技术实现,打通 Spring 注解方式开发的技术痛点。
思路分析

1. 搭建基本结构并获取的扫描包
package com.lzw.spring.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author LiAng
* 1. @Target(ElementType.TYPE)指定我们的 ComponentScan注解 可以修饰 Type程序元素
* 2. @Retention(RetentionPolicy.RUNTIME) 指定 ComponentScan注解 保留范围
* 3. String value() default ""; 表示 ComponentScan 可以传入 value属性
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ComponentScan {
String value() default "";
}
package com.lzw.spring.annotation;
/**
* @author LiAng
* 这是一个配置类,作用类似于原生Spring的 beans.xml 容器配置文件
*/
@ComponentScan(value = "com.lzw.spring.component")
public class LzwSpringConfig {
}
package com.lzw.spring.annotation;
import java.lang.annotation.Annotation;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author LiAng
* LzwSpringApplicationContext 类的作用类似Spring原生ioc容器
*/
public class LzwSpringApplicationContext {
private Class configClass;
//ioc 存放的就是通过反射创建的对象(基于注解方式)
private final ConcurrentHashMap<String, Object> ioc = new ConcurrentHashMap<>();//singletonObjects
public LzwSpringApplicationContext(Class configClass){
this.configClass = configClass;
//System.out.println("this.configClass = " + this.configClass);
//获取要扫描的包
//1. 先得到 LzwSpringConfig 配置的 @ComponentScan(value = "com.lzw.spring.component")
ComponentScan componentScan = (ComponentScan)this.configClass.getDeclaredAnnotation(ComponentScan.class);
//2. 通过 componentScan的value => 即要扫描的包
String path = componentScan.value();
System.out.println("要扫描的包 = " + path);
}
}
2. 获取扫描包下所有.class 文件
package com.lzw.spring.annotation;
import com.lzw.spring.lzwapplicationcontext.LzwApplicationContext;
import java.io.File;
import java.lang.annotation.Annotation;
import java.net.URL;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author LiAng
* LzwSpringApplicationContext 类的作用类似Spring原生ioc容器
*/
public class LzwSpringApplicationContext {
private Class configClass;
//ioc 存放的就是通过反射创建的对象(基于注解方式)
private final ConcurrentHashMap<String, Object> ioc = new ConcurrentHashMap<>();//singletonObjects
public LzwSpringApplicationContext(Class configClass){
this.configClass = configClass;
//System.out.println("this.configClass = " + this.configClass);
//获取要扫描的包
//1. 先得到 LzwSpringConfig 配置的 @ComponentScan(value = "com.lzw.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 = LzwApplicationContext.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());
}
}
}
}
3. 获取全类名 反射对象 放入容器
package com.lzw.spring.annotation;
import com.lzw.spring.lzwapplicationcontext.LzwApplicationContext;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.io.File;
import java.net.URL;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author LiAng
* LzwSpringApplicationContext 类的作用类似Spring原生ioc容器
*/
public class LzwSpringApplicationContext {
private Class configClass;
//ioc 存放的就是通过反射创建的对象(基于注解方式)
private final ConcurrentHashMap<String, Object> ioc = new ConcurrentHashMap<>();//singletonObjects
public LzwSpringApplicationContext(Class configClass){
this.configClass = configClass;
//System.out.println("this.configClass = " + this.configClass);
//获取要扫描的包
//1. 先得到 LzwSpringConfig 配置的 @ComponentScan(value = "com.lzw.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 = LzwApplicationContext.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());
//E:\VIP\AllDemo\Spring5\spring\out\production\spring\com\lzw\spring\component\MyComponent.class
//获取到 com.lzw.spring.component.MyComponent
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. 获取类的完整路径(全类名)
String classFullName = path.replace("/", ".") + "." + className;
//System.out.println("classFullName = " + classFullName);
//3. 判断该类是不是需要注入容器, 就看该类是不是有注解 @Component @Service..
try {
//这时,我们就得到了该类的 Class对象
//Class.forName(classFullName)。forName()获得完整类的信息,loadClass()轻量级。
//1. Class clazz = Class.forName(classFullName) 可以反射加载类
//2. classLoader.loadClass(classFullName); 可以反射类的Class
//3. 区别是 : (1)方式会调用该类的静态方法, (2)方法不会
//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)){
//这时就可以反射对象,并放入到容器中
Class<?> clazz = Class.forName(classFullName);
Object instance = clazz.newInstance();
//放入到容器中,将类名的首字母小写作为id
ioc.put(StringUtils.uncapitalize(className),instance);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
//编写方法返回对容器中对象
public Object getBean(String name) {
return ioc.get(name);
}
}
package com.lzw.spring.annotation;
import com.lzw.spring.component.MyComponent;
import com.lzw.spring.component.UserAction;
import com.lzw.spring.component.UserDao;
import com.lzw.spring.component.UserService;
/**
* @author LiAng
*/
public class LzwSpringApplicationContextTest {
public static void main(String[] args) {
LzwSpringApplicationContext ioc = new LzwSpringApplicationContext(LzwSpringConfig.class);
UserAction userAction = (UserAction) ioc.getBean("userAction");
System.out.println("userAction" + userAction);
MyComponent myComponent = (MyComponent) ioc.getBean("myComponent");
System.out.println("myComponent" + myComponent);
UserService userService = (UserService) ioc.getBean("userService");
System.out.println("userService=" + userService);
UserDao userDao = (UserDao) ioc.getBean("userDao");
System.out.println("userDao=" + userDao);
System.out.println("ok");
}
}

注意事项和细节说明
还可以通过@Component(value = "xx") @Controller(value = "yy") @Service(value = "zz") 中指定的 value,给 bean 分配 id。
if(fileAbsolutePath.endsWith(".class")) {
//1. 获取到类名
String className = fileAbsolutePath.substring(fileAbsolutePath.lastIndexOf("\\") + 1, fileAbsolutePath.indexOf(".class"));
//System.out.println("className = " + className);
//2. 获取类的完整路径(全类名)
String classFullName = path.replace("/", ".") + "." + className;
//System.out.println("classFullName = " + classFullName);
//3. 判断该类是不是需要注入容器, 就看该类是不是有注解 @Component @Service..
try {
//这时,我们就得到了该类的 Class对象
//Class.forName(classFullName)。forName()获得完整类的信息,loadClass()轻量级。
//1. Class clazz = Class.forName(classFullName) 可以反射加载类
//2. classLoader.loadClass(classFullName); 可以反射类的Class
//3. 区别是 : (1)方式会调用该类的静态方法, (2)方法不会
//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
ioc.put(StringUtils.uncapitalize(className),instance);
}
} catch (Exception e) {
e.printStackTrace();
}
}
自动装配
@AutoWired
基于注解配置 bean,也可实现自动装配,使用的注解是:@AutoWired 或者 @Resource。
@AutoWired 的规则说明
(1)在 IOC 容器中查找待装配的组件的类型,如果有唯一的 bean 匹配,则使用该 bean 装配
(2)如果待装配的类型对应的 bean 在 IOC 容器中有多个,则使用待装配的属性的属性名作为 id 值再进行查找,找到就装配,找不到就抛异常。
package com.lzw.spring.component;
import org.springframework.stereotype.Service;
/**
* @author LiAng
* @Service 标识该类是一个Service类/对象
*/
@Service
public class UserService {
public void hi(){
System.out.println("UserService hi()~");
}
}
package com.lzw.spring.component;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
/**
* @author LiAng
* @Controller 标识该类是一个控制器Controller, 通常这个类是一个Servlet
*/
@Controller
public class UserAction {
@Autowired
private UserService userService200;
public void sayOk(){
System.out.println("UserAction 的sayOK()");
System.out.println("userAction 装配的 userService属性=" + userService200);
userService200.hi();
}
}
<?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 https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.lzw.spring.component"/>
<!--配置两个UserService对象-->
<bean class="com.lzw.spring.component.UserService" id="userService200"/>
<!--<bean class="com.lzw.spring.component.UserService" id="userService300"/>-->
</beans>
//通过注解来配置Bean
@Test
public void setProByAutowired() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans06.xml");
UserService userService = ioc.getBean("userService", UserService.class);
System.out.println("ioc容器中的userService=" + userService);
UserService userService200 = ioc.getBean("userService200", UserService.class);
System.out.println("ioc容器中的userService200=" + userService200);
UserAction userAction = ioc.getBean("userAction", UserAction.class);
//System.out.println("userAction=" + userAction);
userAction.sayOk();
}

@Resource
@Resource 的规则说明
(1)@Resource 有两个属性是比较重要的,分是 name 和 type,Spring 将@Resource 注解的 name 属性解析为 bean 的名字,而 type 属性则解析为 bean 的类型。所以如果使用 name 属性,则使用 byName 的自动注入策略,而使用 type 属性时则使用 byType 自动注入策略。
(2)如果@Resource 没有指定 name 和 type,则先使用byName注入策略,如果匹配不上,再使用 byType 策略,如果都不成功,就会报错。
(3)如果@Resource 指定type,并且对应的 bean 在 IOC 容器中有多个,则会继续使用待装配的属性的属性名作为 id 值进行查找,找到就装配,找不到就抛异常。
建议:不管是@Autowired 还是 @Resource 都保证属性名是规范的写法就可以注入。
package com.lzw.spring.component;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import javax.annotation.Resource;
/**
* @author LiAng
* @Controller 标识该类是一个控制器Controller, 通常这个类是一个Servlet
*/
@Controller
public class UserAction {
// @Resource(name = "userService") 表示装配id=userService的对象
//@Resource(name = "userService200")
// @Resource(type = UserService.class) 表示按照 UserService类型进行装配,这时要求容器中只能有一个这样类型的对象
//@Resource(type = UserService.class)
@Resource
private UserService userService200;
public void sayOk(){
System.out.println("UserAction 的sayOK()");
System.out.println("userAction 装配的 userService属性=" + userService200);
userService200.hi();
}
}
<?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 https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.lzw.spring.component"/>
<!--配置两个UserService对象-->
<bean class="com.lzw.spring.component.UserService" id="userService200"/>
<!--<bean class="com.lzw.spring.component.UserService" id="userService300"/>-->
</beans>
//通过注解来配置Bean
@Test
public void setProByAutowired() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans06.xml");
UserService userService = ioc.getBean("userService", UserService.class);
System.out.println("ioc容器中的userService=" + userService);
UserService userService200 = ioc.getBean("userService200", UserService.class);
System.out.println("ioc容器中的userService200=" + userService200);
UserAction userAction = ioc.getBean("userAction", UserAction.class);
//System.out.println("userAction=" + userAction);
userAction.sayOk();
}

@Autowired 和 @Qualifier组合
package com.lzw.spring.component;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
import javax.annotation.Resource;
/**
* @author LiAng
* @Controller 标识该类是一个控制器Controller, 通常这个类是一个Servlet
*/
@Controller
public class UserAction {
//指定id进行组装, 也可以使用@Autowired 和 @Qualifier(value = "userService200")
//这时,是装配的 id=userService200 , 需要两个注解都需要写上
//等价于 @Resource(name = "userService200")
@Autowired
@Qualifier(value = "userService200")
private UserService userService;
public void sayOk(){
System.out.println("UserAction 的sayOK()");
System.out.println("userAction 装配的 userService属性=" + userService);
userService.hi();
}
}

泛型依赖注入
介绍
- 为了更好的管理有继承和相互依赖的 bean 的自动装配,spring 还提供基于泛型依赖的注入机制 。
- 在继承关系复杂情况下,泛型依赖注入就会有很大的优越性。
应用实例
各个类关系图

传统方法是将 PhoneDao /BookDao 自动装配到 BookService/PhoneSerive 中,当这种继承关系多时,就比较麻烦,可以使用 spring 提供的泛型依赖注入。
package com.lzw.spring.depinjection;
/**
* @author LiAng
*/
public class Book {
}
package com.lzw.spring.depinjection;
/**
* @author LiAng
*/
public class Phone {
}
Dao层
package com.lzw.spring.depinjection;
/**
* @author LiAng
* 自定义泛型类
*/
public abstract class BaseDao<T> {
public abstract void save();
}
//=====================================================
package com.lzw.spring.depinjection;
import org.springframework.stereotype.Repository;
/**
* @author LiAng
*/
@Repository
public class BookDao extends BaseDao<Book> {
@Override
public void save() {
System.out.println("BookDao 的 save()..");
}
}
//=====================================================
package com.lzw.spring.depinjection;
import org.springframework.stereotype.Repository;
/**
* @author LiAng
*/
@Repository
public class PhoneDao extends BaseDao<Phone> {
@Override
public void save() {
System.out.println("PhoneDao 的 save()..");
}
}
Service层
package com.lzw.spring.depinjection;
import org.springframework.beans.factory.annotation.Autowired;
/**
* @author LiAng
* 自定义泛型类
*/
public abstract class BaseService<T> {
@Autowired
private BaseDao<T> baseDao;
public void save(){
baseDao.save();
}
}
//=====================================================
package com.lzw.spring.depinjection;
import org.springframework.stereotype.Service;
/**
* @author LiAng
*/
@Service
public class BookService extends BaseService<Book> {
//并没有写属性
}
//=====================================================
package com.lzw.spring.depinjection;
import org.springframework.stereotype.Service;
/**
* @author LiAng
*/
@Service
public class PhoneService extends BaseService<Phone> {
}
<?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 https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.lzw.spring.depinjection"/>
</beans>
//通过泛型依赖来配置Bean
@Test
public void setProByDependencyInjection() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans07.xml");
PhoneService phoneService = ioc.getBean("phoneService", PhoneService.class);
phoneService.save();
System.out.println("ok");
}
