Spring框架学习笔记(二):Spring IOC容器配置 Bean,分别基于XML配置bean 和 基于注解配置 bean

1 Spring配置/管理bean****介绍

Bean****管理包括两方面:创建 bean 对象;给 bean 注入属性

**Bean 配置方式:**基于 xml 文件配置方式;基于注解方式

2 基于XML配置****bean

2.1 通过类型来获取****bean

**方法:**给getBean传入一个Class对象

示例:

ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
Monster monster = ioc.getBean(Monster.class);

说明:
(1) 按类型来获取 bean, 要求 ioc 容器中的同一个类的 bean 只能有一个 , 否则会抛出异常
NoUniqueBeanDefinitionException
(2)这种方式的应用场景:比如 XxxAction/Servlet/Controller, 或 XxxService 在一个线程
中只需要一个对象实例 ( 单例 ) 的情况
(3)在容器配置文件 ( 比如 beans.xml) 中给属性赋值 , 底层是通过setter 方法完成的 , 这也是为什么我们需要提供 setter 方法的原因

2.2 通过构造器配置****bean

(1)constructor-arg标签可以指定使用构造器的参数

(2)index表示构造器的第几个参数 从0开始计算的

(3)除了可以通过index 还可以通过 name / type 来指定参数方式,name表示参数的名字,type表示参数的类型

(4)类的构造器,不能有完全相同类型和顺序的构造器,所以可以通过type来指定

<bean id="monster03" class="com.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.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.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>

说明:

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

2.3 通过p名称空间配置****bean

在 xml 文件配置, 增加命名空间配置

将光标放在p , 输入alt+enter , 就会自动的添加xmlns

示例:

<!--通过p名称空间来配置bean
    将光标放在p , 输入alt+enter , 就会自动的添加xmlns
-->
<bean id="monster06" class="com.spring.bean.Monster"
      p:monsterId="500"
      p:name="红孩儿"
      p:skill="吐火"
/>

2.4 引用**/注入外部bean****对象**

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

应用案例:
(1) 创建 MemberDAOImpl.java

public class MemberDAOImpl {
    public MemberDAOImpl() {
        System.out.println("MemberDAOImpl 构造器...");
    }
    public void add() {
        System.out.println("MemberDAOImpl add()方法");
    }
}

(2)创 建 MemberServiceImpl.java

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;
    }
}

(3)在 beans.xml 配置

<!-- bean 对象的相互引用
1. 其它含义和前面一样
2. ref 表示 memberDAO 这个属性将引用/指向 id = memberDAOImpl 对象
-->
<bean id="memberServiceImpl" class="com.spring.service.MemberServiceImpl">
    <property name="memberDAO" ref="memberDAOImpl"/>
</bean>
<bean id="memberDAOImpl" class="com.spring.dao.MemberDAOImpl"/>

(5)测试

ApplicationContext ioc = new ClassPathXmlApplicationContext("beans03.xml");
MemberServiceImpl bean = (MemberServiceImpl)ioc.getBean("memberServiceImpl", MemberServiceImpl.class);
System.out.println(bean);

细节说明:

  • 这里就体现出spring容器的依赖注入
  • 注意在spring容器中, xml是作为一个整体来执行的, 即如果你引用到一个bean对象, 对你配置的顺序没有要求,即bean的顺序没有要求
  • 建议还是按顺序,好处是阅读的时候,比较方便

2.5 引用**/注入内部bean****对象**

springioc容器中**,可以直接在 bean 内部配置内部 依赖bean****对象**
示例:

<!--配置MemberServiceImpl对象-使用内部bean-->
<bean class="com.spring.service.MemberServiceImpl" id="memberService2">
    <!--自己配置一个内部bean-->
    <property name="memberDAO">
        <bean class="com.spring.dao.MemberDAOImpl"/>
    </property>
</bean>

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

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

应用案例:

(1)创建 Master.java

import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

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;
    }
}

(2)配置 beans.xml

<!--配置Master对象
体会 spring 容器配置特点 依赖注入-非常灵活
-->
<bean class="com.spring.bean.Master" id="master">
    <property name="name" value="太上老君"/>
    <!--给list属性赋值-->
    <property name="monsterList">
        <list>
            <!--引用的方法-->
            <ref bean="monster01"/>
            <ref bean="monster02"/>
            <!--内部bean-->
            <bean class="com.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.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>

(3)测试

public static void main(String[] args) {
    ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
    Master master01 = ioc.getBean("master", 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("username");
    String property2 = pros.getProperty("password");

    String property3 = pros.getProperty("ip");
    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);
    }
}

运行效果:

细节说明:
(1)主要掌握 List/Map/Properties 三种集合的使用 .
(2)Properties 集合的特点

  • 这个 Properties 是 Hashtable 的子类 , 是 key-value 的形式
  • key 是 string 而 value 也是 string

2.7 通过util名称空间创建****list

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

<!--
    通过 util 名称空间来创建 list 集合,可以当做创建 bean 对象的工具来使用
-->
<util:list id="myListBook">
    <value>三国演义</value>
    <value>西游记</value>
    <value>红楼梦</value>
    <value>水浒传</value>
</util:list>
<bean id="bookStore" class="com.spring.bean.BookStore">
    <property name="bookList" ref="myListBook"/>
</bean>

集合

应用案例:

(1)创建 BookStore.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;
    }
}

(2)修改 beans.xml , 增加配置

<!--
    通过 util 名称空间来创建 list 集合,可以当做创建 bean 对象的工具来使用
-->
<util:list id="myListBook">
    <value>三国演义</value>
    <value>西游记</value>
    <value>红楼梦</value>
    <value>水浒传</value>
</util:list>
<bean id="bookStore" class="com.spring.bean.BookStore">
    <property name="bookList" ref="myListBook"/>
</bean>

(3)测试

@Test
public void bookStoreTest() {
    ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
    BookStore bookStore = ioc.getBean("bookStore", BookStore.class);
    System.out.println(bookStore.getBookList());
}

2.8 级联属性赋值

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

应用案例:

(1)创建 Dept.java

public class Dept {
    private String name;
    public Dept() {
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

(2)创建 Emp.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;
    }
}

(3)修改 beans.xml , 增加配置

<!--配置Dept对象-->
<bean class="com.spring.bean.Dept" id="dept"/>
<!--配置Emp对象-->
<bean class="com.spring.bean.Emp" id="emp">
    <property name="name" value="jack"/>
    <property name="dept" ref="dept"/>
    <!--给dept的name属性指定值[级联属性赋值]-->
    <property name="dept.name" value="Java开发部门"/>
</bean>

2.9 通过静态工厂获取对象

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

应用案例:

(1)创建 MyStaticFactory.java

import java.util.HashMap;
import java.util.Map;

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);
    }
}

(2)修改 beans.xml , 增加配置

<!-- 通过静态工厂来获取 bean 对象 -->
<bean id="my_monster" class="com.spring.factory.MyStaticFactory"
      factory-method="getMonster">
    <!-- constructor-arg 标签提供 key -->
    <constructor-arg value="monster_01"/>
</bean>

(3)测试

@Test
public void test02() {
    ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
    Monster monster = ioc.getBean("my_monster", Monster.class);
    System.out.println(monster);
}

2.10 通过实例工厂获取对象

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

应用案例:

(1)创建 MyInstanceFactory.java

import java.util.HashMap;
import java.util.Map;

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);
    }
}

(2)配置 beans.xml

<!-- 通过实例工厂来获取 bean 对象 -->
<bean id="myInstanceFactory" class="com.spring.factory.MyInstanceFactory"/>
<bean id="my_monster2" factory-bean="myInstanceFactory"
      factory-method="getMonster">
    <constructor-arg value="monster_02"/>
</bean>

(3)测试代码

@Test
public void getBeanByInstanceFactory() {
    ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
    Monster my_monster = ioc.getBean("my_monster2", Monster.class);
    System.out.println(my_monster);
}

(4)结果

2.11 通过FactoryBean获取对象

在 spring 的 ioc 容器,通过 FactoryBean 获取 bean 对象

应用案例:

(1)创建MyFactoryBean.java

说明:

  • 该类实现了 FactoryBean 接口,并指定了泛型Monster

  • 创建了一个map集合,用于存放创建好的对象

  • 重写了getObject方法,返回keyVal对应的对象,即this.monster_map.get(keyVal)

  • 重写了getObjectType方法,返回对象的运行类型

  • 重写isSingleton方法,表示对象是否为单例,true为是

    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;
    }
    }

(2)配置 beans.xml

<!--
    1. 通过 FactoryBean 来获取 bean 对象
    2. name="keyVal" 就是 MyFactoryBean 定义的 setKeyVal 方法
    3. value="monster_01" ,就是给 keyVal 的值
-->
<bean id="myFactoryBean" class="com.spring.factory.MyFactoryBean">
    <property name="keyVal" value="monster_01"/>
</bean>

(3)测试代码

@Test
public void getBeanByFactoryBean() {
    ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
    Monster monster = ioc.getBean("myFactoryBean", Monster.class);
    System.out.println(monster);
}

2.12 bean 配置信息重用(继承)

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

应用案例:

(1)配置beans.xml

<!-- 继承的方式来实现 bean 配置信息的重用 -->
<bean id="monster10" class="com.spring.bean.Monster">
    <property name="monsterId" value="10"/>
    <property name="name" value="蜈蚣精"/>
    <property name="skill" value="蜇人"/>
</bean>
<!-- parent="monster10" 就是继承使用了 monster10 的配置信息 -->
<bean id="monster11" class="com.spring.bean.Monster" parent="monster10"/>
<!-- 当我们把某个bean设置为 abstract="true" 这个bean只能被继承,而不能实例化了 -->
<bean id="monster12" class="com.spring.bean.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.spring.bean.Monster" parent="monster12"/>

说明:

  • 可通过parent="monster10"方式继承其他bean,实现bean 配置信息的重用
  • 把某个bean设置为 abstract="true" ,这个bean只能被继承,而不能实例化了

测试代码:

@Test
public void getBeanByExtends() {
    ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
    Monster monster1 = ioc.getBean("monster11", Monster.class);
    System.out.println(monster1);
    Monster monster2 = ioc.getBean("monster13", Monster.class);
    System.out.println(monster2);
}

2.13 bean 创建顺序

说明:

(1)在 spring 的 ioc 容器, 默认是按照配置的顺序创建 bean 对象

<bean id="student01" class="com.hspedu.bean.Student" />
<bean id="department01" class="com.hspedu.bean.Department" />

会先创建 student01 这个 bean 对象,然后创建 department01 这个 bean 对象

(2)如果这样配置,表示 student01 对象依赖于 department01 对象

<bean id="student01" class="com.hspedu.bean.Student" depends-on="department01"/>
<bean id="department01" class="com.hspedu.bean.Department" />

就会先创建 department01 对象,再创建 student01 对象.

(3)如果使用ref关联两个bean,无需关注配置顺序

说明:spring会先把有关联的对象都创建好,再处理引用关系

案例:

  1. 先看下面的配置 , 两个 bean 创建的顺序如下
  • 先创建 id=memberDAOImpl
  • 再创建 id = memberServiceImpl
  • 调用 memberServiceImpl.setMemberDAO() 完成引用


2. 先看下面的配置 , 请问两个 bean 创建的顺序如下

  • 先创建 id = memberServiceImpl
  • 再创建 id=memberDAOImpl
  • 最后调用 memberServiceImpl.setMemberDAO() 完成引用

2.14 bean****对象的单例和多例

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

如果,我们希望 ioc 容器配置的某个 bean 对象,是以多个实例形式创建的则可以通过配置 scope="prototype" 来指定,这样spring在获取对象时才会临时创建该对象,而不是像单例对象一样提前创建好

应用案例:

(1)创建 Car.java

public class cat {
    public cat() {
        System.out.println("cat 构造器");
    }
}

(2)配置 beans.xml

<!-- 
如果希望 ioc 容器配置的某个 bean 对象,
是以多个实例形式创建可以通过配置 scope="prototype" 来指定
-->
<bean name="cat" scope="prototype" class="com.spring.bean.Cat"/>

(3)测试代码

@Test
public void getBeanByPrototype() {
    ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
    for (int i = 0; i < 3; i++) {
        Cat cat = ioc.getBean("cat", Cat.class);
        System.out.println(cat);
    }
}

细节说明:

  • 在默认情况下 scope属性是 singleton,在启动容器时, 默认就会创建 , 并放入到 singletonObjects 集合
  • 在ioc容器中, 只有一个这个bean对象
  • 当执行getBean时, 返回的的是同一个对象
  • 如果我们希望每次getBean返回一个新的Bean对象,则可以scope="prototype"
    • 如 果 是 单 例 singleton, 同 时 希 望 在 getBean 时 才 创 建 , 可 以 指 定 懒 加 载
      lazy-init="true" (注意默认是 false)
    • 通常情况下, lazy-init 就使用默认值 false , 在开发看来, 用空间换时间是值得的, 除非
      有特殊的要求.
    • 如果 scope="prototype" 这时你的 lazy-init 属性的值不管是 ture, 还是 false 都是在
      getBean 时候,才创建对象.

2.15 bean****的生命周期

:bean 对象创建是由 JVM 完成的,依次执行如下方法:
(1)执行构造器
(2)执行 set 相关方法
(3)调用 bean 的初始化的方法(需要配置)
(4)使用 bean
(5)当容器关闭时候,调用 bean 的销毁方法(需要配置)
应用实例

(1)创建 House.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()..");
    }
}

(2)配置beans.xml,配置 bean 的初始化方法和销毁方法

<!-- 配置 bean 的初始化方法和销毁方法 -->
<bean id="house" class="com.spring.bean.House"
      init-method="init" destroy-method="destory">
    <property name="name" value="北京豪宅"/>
</bean>

(3)测试代码

@Test
public void beanLife() {
    ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
    House house = ioc.getBean("house", House.class);
    System.out.println(house);
    //关闭容器
    ((ConfigurableApplicationContext) ioc).close();
}

细节说明:

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

2.16 配置bean的后置处理器

说明:

(1)在 spring 的 ioc 容器,可以配置 bean 的后置处理器

(2)该处理器/对象会在 bean 初始化方法调用前和初始化方法调用后被调用

(3)程序员可以在后置处理器中编写自己的代码

应用案例:

(1)创 建 后 置处 理 器 MyBeanPostProcessor.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;
    }
}

0(2)配置beans03.xml

当我们在xml 容器配置文件 配置了 MyBeanPostProcessor,这时后置处理器对象,就会作用在该容器创建的所有Bean对象上

<!-- 配置 bean 的初始化方法和销毁方法 -->
<bean id="house" class="com.spring.bean.House"
      init-method="init" destroy-method="destory">
    <property name="name" value="北京豪宅"/>
</bean>
<!-- bean 后置处理器的配置 -->
<bean id="myBeanPostProcessor" class="com.spring.bean.MyBeanPostProcessor" />

(3)测试代码

@Test
public void testBeanPostProcessor() {
    ApplicationContext ioc = new ClassPathXmlApplicationContext("beans03.xml");
    House house = ioc.getBean("house", House.class);
    System.out.println(house);
    //关闭容器
    ((ConfigurableApplicationContext) ioc).close();
}

疑惑点说明:

  • 怎么执行到这个方法:使用 AOP(反射+动态代理+IO+容器+注解)
  • 有什么用:可以对 IOC 容器中所有的对象进行统一处理 ,比如 日志处理/权限的校验/安全的验证/事务管理.
  • 针对容器的所有对象吗:是的=>切面编程特点

2.17 通过属性文件给bean注入值

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

应用案例:

(1)resources/ 下创建my.properties

可以在Unicode编码转换 - 站长工具 (chinaz.com)网站将中文转成unicode编码

monsterId=1000
name=\u4e4c\u9f9f\u7cbe
skill=\u7f29\u8116\u5b50

(2)修改 src\beans.xml , 继续完成配置

location="classpath:my.properties" 表示在类路径下的my.properties读取

这时我们的属性值通过${属性名}获取,这里说的 属性名 就是 my.properties文件中的 k=v 的k

<!-- 
    1. 通过属性文件给 bean 注入值, 
    2. 需要导入: xmlns:context 名字空间,并指定属性文件路径
-->
<context:property-placeholder location="classpath:my.properties"/>
<bean id="monster100" class="com.spring.bean.Monster">
    <property name="monsterId" value="${monsterId}"/>
    <property name="name" value="${name}"/>
    <property name="skill" value="${skill}"/>
</bean>

(3)测试代码

@Test
public void setProByProFile() {
    ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
    Monster monster100 = ioc.getBean("monster100", Monster.class);
    System.out.println(monster100);
}

2.18 基于XMLbean的自动装配

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

应用案例:
(1)创建 OrderDao.java

public class OrderDao {
    public void saveOrder() {
        System.out.println("保存 一个订单...");
    }
}

创建 OrderService.java

public class OrderService {
    private OrderDao orderDao;
    public OrderDao getOrderDao() {
        return orderDao;
    }
    public void setOrderDao(OrderDao orderDao) {
        this.orderDao = orderDao;
    }
}

创建 OrderAction.java

public class OrderAction {
    private OrderService orderService;
    public OrderService getOrderService() {
        return orderService;
    }
    public void setOrderService(OrderService orderService) {
        this.orderService = orderService;
    }
}

(2)配置 beans.xml

根据类型进行自动组装

<!--
    autowire="byType" 表示根据类型进行自动组装.
-->
 <bean id="orderAction" autowire="byType" class="com.spring.action.OrderAction" />
 <bean id="orderService" autowire="byType" class="com.spring.service.OrderService"/>
 <bean id="orderDao" class="com.spring.dao.OrderDao"/>

根据名称进行自动组装

<!--
1. 说明: autowire = "byName" 会自动去找 id 为 setXxxx 后面 Xxxx 的 bean 自动组装.
,如果找到就装配,如果找不到就报错, 比如这里的
2. <bean id="orderAction" autowire="byName" class="com.spring.bean.OrderAction" />
就 会 去 找 OrderAction 类 中 定 义 的 setOrderService 的 id 为 orderService 的
OrderService bean 组装,找到就阻装,找不到就组装失败
-->
<bean id="orderAction" autowire="byName" class="com.spring.action.OrderAction"/>
<bean id="orderService" autowire="byName" class="com.spring.service.OrderService"/>
<bean id="orderDao" class="com.spring.dao.OrderDao"/>

说明:

(1)autowire="byType" 表示 在创建 orderService时通过类型的方式 给对象属性 自动完成赋值/引用

(2)比如OrderService 对象有 private OrderDao orderDao

(3)就会在容器中去找有没有 OrderDao类型对象

(4)如果有,就会自动的装配, 如果是按照 byType 方式来装配, 这个容器中,不能有两个OrderDao类型对象

(5)如果对象没有属性, autowire就没有必要写

(6)如果设置的是 autowire="byName" 表示通过名字完成自动装配

(7)比如下面的 autowire="byName" class="com.hspedu.spring.service.OrderService"

  • 先看 OrderService 属性 private OrderDao orderDao
  • 再根据这个属性的setXxx()方法的 xxx 来找对象id(而不是根据属性名)
  • public void setOrderDao() 就会找id=orderDao对象来进行自动装配
  • 如果没有就装配失败

2.19 spring el****表达式

(1)Spring Expression Language,Spring 表达式语言,简称 SpEL。支持运行时查询并可以操 作对象。

(2)和 EL 表达式一样,SpEL 根据 JavaBean 风格的 getXxx()、setXxx()方法定义的属性访问对象

(3)SpEL 使用#{...}作为定界符,所有在大框号中的字符都将被认为是 SpEL 表达式。

应用案例:

(1)创建 SpELBean.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;
    }
}

(2)配置 beans.xml

<!-- spring el 表达式 -->
<bean id="spELBean" class="com.spring.bean.SpELBean">
    <!-- sp el 给字面量 -->
    <property name="name" value="#{'小苏'}"/>
    <!-- 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.spring.bean.SpELBean).read(' 天龙八部')}"/>
    <!-- sp el 通过运算赋值 -->
    <property name="result" value="#{89*1.2}"/>
</bean>

<!--配置一个monster对象-->
<bean id="monster01" class="com.spring.bean.Monster">
    <property name="monsterId" value="100"/>
    <property name="name" value="蜈蚣精~"/>
    <property name="skill" value="蜇人~"/>
</bean>

(3)测试代码

@Test
public void setProBySpel() {
    ApplicationContext ioc = new ClassPathXmlApplicationContext("beans04.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());
}

3 基于注解配置 bean

3.1 基本介绍

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

组件注解的形式有:

(1)@Component 表示当前注解标识的是一个组件

(2)@Controller 表示当前注解标识的是一个控制器,通常用于 Servlet

(3)@Service 表示当前注解标识的是一个处理业务逻辑的类,通常用于 Service 类

(4)@Repository 表示当前注解标识的是一个持久化层的类,通常用于 Dao 类

3.2 快速入门案例

使用注解的方式来配置****Controller / Service / Respository / Component

(1)创建 UserAction.java UserService.java, UserDao.java MyComponent.java

/**
 * @Controller 标识该类是一个控制器Controller, 通常这个类是一个Servlet
 */
@Controller
public class UserAction {}

/**
 * @Service 标识该类是一个Service类/对象
 */
@Service
public class UserService {
}

/**
 * 使用 @Repository 标识该类是一个Repository是一个持久化层的类/对象
 */
@Repository
public class UserDao {
}

/**
 * @Component 标识该类是一个组件, 是一个通用的注解
 */
@Component
public class MyComponent {
}

(2)在xml文件中配置要扫描的包

<!--配置容器要扫描的包
1. component-scan 要对指定包下的类进行扫描, 并创建对象到容器
2. base-package 指定要扫描的包
3. 含义是当spring容器创建/初始化时,就会扫描com.spring.component包
   下的所有的 有注解 @Controller / @Service / @Respository / @Component类
   将其实例化,生成对象,放入到ioc容器
-->
<context:component-scan base-package="com.spring.component"/>

(3)测试代码

@Test
public void getBeanByAnnotation() {
    ApplicationContext ioc = new ClassPathXmlApplicationContext("beans05.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);
}

运行结果:

(5)细节说明:

  • 必须在 Spring 配置文件中指定"自动扫描的包",IOC 容器才能够检测到当前项目中哪些类被标识了注解, 注意到导入 context 名称空间。可以使用通配符 * 来指定 ,比如 com.spring.* 表示。

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

    • Spring 的 IOC 容器不能检测一个使用了 @Controller 注解的类到底是不是一个真正的控
      制器。注解的名称是用于程序员自己识别当前标识的是什么组件。其它的 @Service
      @Repository 也是一样的道理( 也就是说 spring 的 IOC 容器只要检查到注解就会生成对象,
      但是这个注解的含义 spring 不会识别,注解是给程序员编程方便看的)
  • <context:component-scan base-package="com.hspedu.spring.component" resource-pattern="User*.class" />,resource-pattern="User*.class": 表示只扫描com.spring.component 和它的子包下的User打头的类

  • 排除指定类

如果我们希望排除某个包/子包下的某种类型的注解,可以通过exclude-filter来指定

(1)context:exclude-filter 指定要排除哪些类

(2)type 指定排除方式 ,annotation表示按照注解来排除

(3)expression="org.springframework.stereotype.Service" 指定要排除的注解的全路径

<context:component-scan base-package="com.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>

以上配置表示不扫描带Service和Repository注解的类

  • 扫描指定类

如果我们希望按照自己的规则,来扫描包/子包下的某些注解, 可以通过 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.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>

以上配置表示只扫描com.spring.component包下,带Service、Repository、Controller注解的类

  • 标记注解后,默认类名首字母小写作为 id 的值。也可以使用注解的 value 属性****指定 id 值,并且 value 可以省略。

    @Controller(value="userAction01")

    //value可以省略
    @Controller("userAction01")

3.3 实现简单的Spring基于注解配置的程序

3.3.1 实现思路

3.3.2 代码实现

说明:以下实现代码基于上方入门案例中的四个类

(1)创建ComponentScan.java

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ComponentScan {

    //表示ComponentScan注解可以传入一个value属性
    String value() default "";
}

(2)创建WwjSpringConfig.java

/**
 * 这是一个配置类,作用类似于原生Spring的 beans.xml 容器配置文件
 */
@ComponentScan(value = "com.spring.component")
public class WwjSpringConfig {
}

(3)创建WwjSpringApplicationCo ntext.java

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.lang.annotation.Annotation;
import java.net.URL;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 该类作用类似于Spring原生ioc容器
 */
public class WwjSpringApplicationContext {
    //拿到配置类.class文件
    private Class configClass;
    //ioc存放的就是通过反射创建的对象
    private final ConcurrentHashMap<String,Object> ioc = new ConcurrentHashMap<>();

    //构造器
    public WwjSpringApplicationContext(Class configClass){
        this.configClass = configClass;
//        System.out.println("this.configClass=" + this.configClass);
        //获取要扫描的包
        //1.先获取到ComponentScan注解
        ComponentScan componentScan = (ComponentScan)this.configClass.getDeclaredAnnotation(ComponentScan.class);
        //2.获取到注解的value值
        String path = componentScan.value();
//        System.out.println("要扫描的包= " + path);

        //得到要扫描的包下的所有class文件(在target目录下)
        //1.得到类的加载器
        ClassLoader classLoader = WwjSpringApplicationContext.class.getClassLoader();
        //2.通过类的加载器获取到要扫描的包的资源路径
        path = path.replace(".","/");//一定要把.替换成/
        URL resource = classLoader.getResource(path);
//        System.out.println(resource);
        //3.将要加载的资源(.class) 路径下的文件进行遍历
        File file = new File(resource.getFile());
        if (file.isDirectory()){
            File[] files = file.listFiles();
            for (File f : files) {
//                System.out.println(f.getAbsolutePath());
                //获取到.class文件的绝对路径
                String fileAbsolutePath = f.getAbsolutePath();
                //这里只处理.class文件
                if (fileAbsolutePath.endsWith(".class")) {
                    //获取到类全类名
                    //1.获取到类名
                    String className = fileAbsolutePath.substring
                            (fileAbsolutePath.lastIndexOf("\\") + 1, fileAbsolutePath.indexOf(".class"));
//                System.out.println(className);
                    //2.拼接成全类名
                    String classFullName = path.replace("/",".") + "." + className;
//                    System.out.println(classFullName);
                    //3.判断该类是不是需要注入容器,看该类是不是有注解 @Component @Service @Repository @Controller
                    try {
                        //这时,就得到了该类的Class对象
                        //1. Class clazz = Class.forName(classFullName) 可以反射加载类
                        //2. classLoader.loadClass(classFullName); 也可以反射类的Class
                        //3. 区别是 : 上面方式会调用该类的静态方法, 下面方法不会
                        //4. aClass.isAnnotationPresent(Component.class) 判断该类是否有 @Component
                        Class<?> aClass = classLoader.loadClass(classFullName);
                        //判断该类是否有 @Component @Service @Repository @Controller
                        if (aClass.isAnnotationPresent(Component.class) ||
                                aClass.isAnnotationPresent(Service.class) ||
                                aClass.isAnnotationPresent(Repository.class) ||
                                aClass.isAnnotationPresent(Controller.class)){
                            String keyVal = StringUtils.uncapitalize(className);
                            //获取到value值,替换指定id
                            if (aClass.isAnnotationPresent(Component.class)){
                                Component annotation = aClass.getDeclaredAnnotation(Component.class);
                                String value = annotation.value();
                                if (!value.equals("")){
                                    keyVal = value;
                                }
                            } else if (aClass.isAnnotationPresent(Service.class)) {
                                Service annotation = aClass.getDeclaredAnnotation(Service.class);
                                String value = annotation.value();
                                if (!value.equals("")){
                                    keyVal = value;
                                }
                            } else if (aClass.isAnnotationPresent(Repository.class)) {
                                Repository annotation = aClass.getDeclaredAnnotation(Repository.class);
                                String value = annotation.value();
                                if (!value.equals("")){
                                    keyVal = value;
                                }
                            } else if (aClass.isAnnotationPresent(Controller.class)) {
                                Controller annotation = aClass.getDeclaredAnnotation(Controller.class);
                                String value = annotation.value();
                                if (!value.equals("")){
                                    keyVal = value;
                                }
                            }

                            //这时候就可以创建该类的对象,并放入到ioc容器中
                            Class<?> clazz = Class.forName(classFullName);
                            Object o = clazz.newInstance();
//                            String defaultID = className.substring(0,1).toLowerCase() + className.substring(1);
//                            System.out.println(defaultID);
                            //org.springframework.util.StringUtils包中的静态方法可以将字符串首字母小写
                            ioc.put(keyVal,o);
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }

        }
    }

    public Object getBean(String id){
        return ioc.get(id);
    }
}

(4)创建测试主程序WwjSpringApplicationContextTest.java

public class WwjSpringApplicationContextTest {
    public static void main(String[] args) {
        WwjSpringApplicationContext ioc = new WwjSpringApplicationContext(WwjSpringConfig.class);
        System.out.println(ioc.getBean("yss1"));
        System.out.println(ioc.getBean("yss2"));
        System.out.println(ioc.getBean("yss3"));
    }

(5)运行结果

3.4 自动装配

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

3.4.1 @AutoWired****的规则说明

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

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

3.4.2 @Resource****的规则说明

(1)@Resource 有两个属性是比较重要的 , 分别是 name 和 **type,**Spring 将 @Resource 注解的
name 属性解析为 bean 的名字 , 而 type 属性则解析为 bean 的类型 . 所以如果使用 name 属
性 , 则使用 byName 的自动注入策略 , 而使用 type 属性时则使用 byType 自动注入策略
比如@Resource(name = "userService") 表示装配 id=userService的对象
比如@Resource(type = UserService.class) 表示按照UserService.class类型进行装配, 这时要求容器中,只能有一个这样类型的对象
(2)如果 @Resource 没有指定 name 和 type , 则先使用 byName(name的值为属性名) 注入策略 , 如果匹配不上 ,
再使用 byType 策略 , 如果都不成功,就会报错
说明: @Autowired + @Qualifier(value = "userService02") 组合也可以完成指定 name/id 来进行自动装配,这时,是装配的 id=userService02 , 需要两个注解都需要写上

3.4.3 应用案例

(1)实现 UserAction 和 UserService 的两级自动组装

@Service
public class UserService {
    //方法..
    public void hi(){
        System.out.println("UserService hi()~");
    }
}

/**
 * @Controller 标识该类是一个控制器Controller, 通常这个类是一个Servlet
 */
@Controller
public class UserAction {
    //@Autowired 自动装配 UserService, 这时是以 UserService,class 类型对对象进行组装
    //如待装配的类型对应的 bean 在 IOC 容器中有多个,则是以 id=userService 的 UserService 对象进行组装
    @Autowired
    private UserService userService;

    public void sayOk() {
        System.out.println("UserAction 的sayOk()");
        System.out.println("userAction 装配的 userService属性=" + userService);
        userService.hi();
    }

    //不写这个方法,也可以完成组装
    public void setUserService(UserService userService) {
        this.userService = userService;
    }
}

(2)测试代码

@Test
public void setProByAnnotationAutowired() {
    ApplicationContext ioc = new ClassPathXmlApplicationContext("beans05.xml");
    UserAction userAction01 = ioc.getBean(UserAction.class);
    userAction01.sayOk();
}

3.5 泛型依赖注入

基本说明:

(1)为了更好的管理有继承和相互依赖的 bean 的自动装配,spring 还提供基于泛型依赖的注入机制

(2)在继承关系复杂情况下,泛型依赖注入就会有很大的优越性

应用实例:

(1)各个类关系图

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

(3)创建 Book.java,Phone.java等类

package com.spring.depinjection;
public class Book {
}

package com.spring.depinjection;

public class Phone {
}

package com.spring.depinjection;

public abstract class BaseDao<T> {
    public abstract void save();
}

package com.spring.depinjection;
import org.springframework.stereotype.Repository;

@Repository
public class BookDao extends BaseDao<Book> {
    @Override
    public void save() {
        System.out.println("BookDao 的 save()");
    }
}

package com.spring.depinjection;
import org.springframework.stereotype.Repository;

@Repository
public class PhoneDao extends BaseDao<Phone> {
    @Override
    public void save() {
        System.out.println("PhoneDao 的 save()");
    }
}

package com.spring.depinjection;
import org.springframework.beans.factory.annotation.Autowired;

public class BaseService<T> {
    @Autowired
    private BaseDao<T> baseDao;
    public void save() {
        baseDao.save();
    }
}

package com.spring.depinjection;
import org.springframework.stereotype.Service;

@Service
public class BookService extends BaseService<Book> {
}

package com.spring.depinjection;
import org.springframework.stereotype.Service;

@Service
public class PhoneService extends BaseService<Phone> {
}

(4)修改 beans06.xml , 增加配置

<context:component-scan base-package="com.hspedu.spring.depinjection"/>

(5)测试代码

@Test
public void setProByDepinjectionAutowired() {
    ApplicationContext ioc = new ClassPathXmlApplicationContext("beans06.xml");
    BookService bookService = ioc.getBean(BookService.class);
    bookService.save();
    PhoneService phoneService = ioc.getBean(PhoneService.class);
    phoneService.save();
}

(6)测试结果

相关推荐
懒惰的bit2 小时前
基础网络安全知识
学习·web安全·1024程序员节
2401_858286113 小时前
L7.【LeetCode笔记】相交链表
笔记·leetcode·链表
Natural_yz5 小时前
大数据学习09之Hive基础
大数据·hive·学习
龙中舞王5 小时前
Unity学习笔记(2):场景绘制
笔记·学习·unity
Natural_yz5 小时前
大数据学习10之Hive高级
大数据·hive·学习
海波东5 小时前
某m大厂面经1
java·spring
love_and_hope5 小时前
Pytorch学习--神经网络--完整的模型训练套路
人工智能·pytorch·python·深度学习·神经网络·学习
青椒大仙KI116 小时前
24/11/7 算法笔记 PCA主成分分析
笔记·算法·信息可视化
夜雨星辰4876 小时前
Android Studio 学习——整体框架和概念
android·学习·android studio
奔跑的花短裤6 小时前
少儿编程启蒙学习
学习·青少年编程·机器人·ai编程