Spring核心技术详解(二)

文章参考自:https://blog.csdn.net/c99463904/article/details/73003558

一、基于XML方式在Ioc容器装配Bean

为了让IoC容器帮我们创建和管理对象,我们必须在Spring IoC容器中装配好Bean,并建立好Bean和Bean之间的关联关系。Spring启动时读取应用程序提供的Bean配置信息,并在Spring容器中生成一份相应的Bean配置注册表,然后根据这张注册表实例化Bean,装配好Bean之间的依赖关系。


1、Bean的基本配置

<bean id=“userdaoid” class=“UserDao”/>

2、Bean的命名

  一般情况下,配置Bean时需要为其指定一个id属性作为Bean的名称。id在IoC容器中必须是唯一的。id的命名需要满足xml的命名规范:必须以字母开始,后面可以使字母数字、下划线、句号、冒号等符号,逗号和空格等字符是非法的。

  还有一个name属性,和id属性作用一样,但是name几乎可以使用任何字符。Spring配置文件中不允许出现相同的id,却允许出现相同的name。如果有相同的name,通过getBean(beanName)获取Bean时,将返回最后生命的Bean,原因是最后的Bean覆盖了前面同名的Bean。一般地,应该尽量使用id。


3、Bean的实例化方式
  • 使用默认的无参构造方法实例化
 // 创建一个类
public class User{
}  

 // 配置xml文件
"user" class="com.cad.domain.User" >
  • 使用默认的无参构造方法实例化
    工厂方法是非静态的,即必须实例化工厂类后才能调用工厂方法。
// 创建一个工厂类,提供一个普通的方法,返回指定的对象
public class UserFactory {
    public  User getUser(){
        return new User();
    }
}

//------------------------------- 配置xml文件 ---------------------------------

//先要实例工厂
"factory" class="com.cad.domain.UserFactory" > 
//通过实例化工厂调用工厂方法获取对象,factory-bean属性引用工厂实例,factory-method指定工厂方法
"user" factory-bean="factory" factory-method="getUser">
  • 使用静态工厂方法实例化
    工厂类方法是静态的,可以不用创建工厂类实例直接使用。
// 创建一个工厂类,提供一个静态方法,返回指定的对象
public class UserFactory {
    public static User getUser(){
        return new User();
    }
}

//------------------------------- 配置xml文件 ---------------------------------

//class指定工厂类,factory指定工厂方法
"user" class="com.cad.domain.UserFactory" factory-method="getUser">
4、Bean的属性注入

  Spring支持两种依赖注入方式,分别是属性注入和构造方法注入。用户不但可以将String,int等类型参数注入到Bean中,还可以将集合、Map类型注入到Bean中。

  • 属性注入(使用属性的set方法)
// 属性注入要求Bean提供一个默认的构造方法,并为需要注入的属性提供对应的set方法。
// Spring调Bean的默认构造方法创建对象,然后通过反射的方式调用set方法注入属性。 

// 创建类
public class User {
    private String username;
    public void setUsername(String username){
        this.username=username;
    }
    public void speak(){
        System.out.println("my name is"+username);
    }
}

//------------------------------- 配置xml文件 ---------------------------------

"user" class="com.cad.domain.User"> 

    "username" value="张三">
  • 使用有参数构造方法注入
// 使用构造方法注入前提是Bean必须提供带参的构造方法。 
//创建User类
public class User {
    private String username;
    public User(String username){
        this.username=username;
    }
    public void speak(){
        System.out.println("my name is"+username);
    }
}

//------------------------------- 配置xml文件 ---------------------------------
"user" class="com.cad.domain.User">
    
    "username" value="jack">
 
  • 注入null值
    如果需要为某个属性注入null值,必须使用专用的标签。
// 如果需要为某个属性注入null值,必须使用专用的<null/>标签。
<bean id="userdaoid" class="UserDao"> 
    <property name="username"><null/>property>
bean>
  • 注入集合类型
    java.util包中的集合类是最常用的数据结构类型。主要包括List、Set、map、Properties等,Spring为这些集合提供了专门的标签来配置。
// 我们创建一个类,包含了各种集合类型 
public class CollectionDemo {
    private List<String> list; 
    private Set<String> set; 
    private Map<String,String> map; 
    private Properties properties;

    public List<String> getList() {
        return list;
    }
    public void setList(List<String> list) {
        this.list = list;
    }
    public Set<String> getSet() {
        return set;
    }
    public void setSet(Set<String> set) {
        this.set = set;
    }
    public Map<String, String> getMap() {
        return map;
    }
    public void setMap(Map<String, String> map) {
        this.map = map;
    }
    public Properties getProperties() {
        return properties;
    }
    public void setProperties(Properties properties) {
        this.properties = properties;
    }

    public String toString() {
        return "CollectionDemo [list=" + list + ", set=" + set + ", map=" + map + ", properties=" + properties + "]";
    } 
}

//------------------------------- 配置xml文件 ---------------------------------

<bean id="collid" class="com.cad.domain.CollectionDemo"> 
    
    <property name="list">
        <list>
            <value>张三value>
            <value>李四value>
        list> 
    property> 

    
    <property name="set">
        <set>
            <value>javavalue>
            <value>c#value>
        set>
    property> 

    
    <property name="map">
        <map>
            <entry key="jack" value="杰克">entry>
            <entry key="tom"  value="汤姆">entry>
        map>
    property> 

    
    <property name="properties">
        <props>
            <prop key="prop1">prop1prop>
            <prop key="prop2">prop2prop>
        props>
    property>
bean>

5、使用p命名空间

  为了简化Xml的配置,Spring引入了一个P命名空间。

  • 未使用P命名空间之前:
// 创建一个User类
public class User {
    private String name;
    private int age;
    public void setName(String name) {
        this.name = name;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public void say(){
        System.out.println(name+":"+age);
    }
} 


<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="user" class="com.cad.domain.User">
        <property name="name" value="张三">property>
        <property name="age" value="18">property>
    bean>
beans>  
  • 使用P命名空间之后:


<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">

    
    <bean id="user" class="com.cad.domain.User" p:name="李四" p:age="88"/>
beans>  

6、Bean的作用域

  Bean的作用域会对Bean的生命周期和创建方式产生影响,其一共有五种作用域:
  Spring核心技术详解(二)_第1张图片
  当作用域设置成singleton是获得的两个对象是一样的

// 我们来演示一下,将User类的作用域设置为singleton
"user" class="com.cad.domain.User" p:name="李四" p:age="88" scope="singleton"/>

public class Test {
    @org.junit.Test
    public void test(){
        ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
        User user1=(User) ac.getBean("user");  //获得的两个对象是一样的
        User user2=(User) ac.getBean("user"); 
        System.out.println(user1);
        System.out.println(user2);
    }
}/**Output
        com.cad.domain.User@1c3a4799
        com.cad.domain.User@1c3a4799
**/

  作用域设置为多例

// 将User的作用域设置为多例
"user" class="com.cad.domain.User" p:name="李四" p:age="88" scope="prototype"/>

// 输出 
com.cad.domain.User@5d76b067
com.cad.domain.User@2a17b7b6

7、FactoryBean

  我们前面的都是普通bean,Spring利用反射机制通过bean的class属性来实例化Bean。如果有的Bean属性特别多,我们就需要编写大量的配置信息。Spring提供了一个FactoryBean接口。我们可以通过实现该接口来返回特定的Bean,该接口定义了三个方法:

  • T getObject():返回由FactoryBean创建的Bean实例。

  • boolean isSingleton():确定创建的Bean的作用域是singleton还是prototype

  • Class< ? > getObjectType():返回FactoryBean创建Bean的类型

// 我们创建一个类实现FactoryBean接口 
public class UserFactory implements FactoryBean<User> {

    //返回对象
    public User getObject() throws Exception {
        User user=new User();
        user.setAge(14);
        user.setName("tom");
        return user;
    }

    //返回bean的类型
    public Class getObjectType() {
        return User.class;
    }

    public boolean isSingleton() {
        // TODO Auto-generated method stub
        return false;
    }
}
// 配置 
"userfactory" class="com.cad.domain.UserFactory">bean>
//测试 
public class Test {
    @org.junit.Test
    public void test(){
        ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
        User user=(User) ac.getBean("userfactory"); 
        user.say();
    }
}/**Output
        tom:14 
**/

  当我们标签的class属性配置的类实现了FactoryBean接口时,通过getBean返回的就不是该类本身,而是getObject()方法所返回的对象,相当于getObject()方法代理了getBean()。



二、Bean的生命周期

Spring核心技术详解(二)_第2张图片
Spring核心技术详解(二)_第3张图片
步骤如下:

1)、实例化BeanFactoryPostProcessor,调用postProcessBeanFactory()方法对工厂定义信息进行后处理。

2)、调用者通过getBean向容器请求bean时,如果容器注册了InstantiationAwareBeanPostProcessor接口,在实例化Bean之前,会调用接口的postProcessBeforeInstantiation()方法。

3)、根据配置文件调用Bean的构造方法或者工厂方法实例化Bean。

4)、实例化Bean后,调用InstantiationAwareBeanPostProcessor接口的postProcessAfterInstantiation()方法,在这里可以对已经实例化的对象进行一些修饰。

5)、如果Bean配置了属性信息,在设置每个属性之前将先调用InstantiationAwareBeanPostProcessor的postProcessPropertyValues()方法。

6)、调用Bean的属性设置方法为Bean设置属性。

7)、如果Bean实现了BeanNameAware接口,会调用该接口的setBeanName()方法,将配置文件中该Bean对应的名称设置到Bean中。

8)、如果Bean实现了BeanFactoryAware接口,会调用该接口的setBeanFactory方法,将BeanFactory容器实例设置到Bean中。

9)、如果BeanFactory装配了BeanPostProcessor后处理器,将调用Object postProcessBeforeInitialization(Object bean,String beanName)方法对Bean进行加工操作,bean是当前处理的Bean,beanName是当前bean的配置名,返回的对象是加工处理后的Bean。用户可以使用该方法对Bean进行特殊处理。BeanPostProcessor在Spring框架中占有重要地位,AOP等都通过该BeanPostProcessor实施。

10)、如果Bean实现了InitializingBean接口,会调用该接口的afterPropertiesSet()方法。

11)、如果中配置了init-method属性,执行指定的初始化方法。

12)、调用BeanPostProcessor后处理器的Object postProcessAfterInitialization(Object bean,String beanName)方法再次获得对Bean的加工处理机会。

13)、将Bean返回给调用者。

14)、当容器关闭时, 如果Bean实现了DisposableBean接口,调用接口的destory方法。

15)、如果设置了destory-method属性,执行指定的销毁方法。


1、生命周期中方法的划分

  Bean的完整生命周期从Spring容器着手实例化bean开始,直到销毁Bean。其中有很多关键的方法。
这些方法大致分为三类:

  • Bean自身的方法:如Bean的构造方法,调用set方法设置属性,和init-method和destory-method指定的方法

  • Bean的生命周期接口方法:如BeanNameAwareBeanFactoryAwareInitializingBeanDisposableBean等接口的方法由Bean自己直接实现

  • 容器级生命周期接口方法:如InstantiationAwareBeanPostProcessorBeanPostProcessor接口,一般称为后处理器。这些接口的实现类与Bean无关,直接装配到Spring容器中。当Spring容器创建任何Bean时,这些后处理器都会起作用。


2、测试Bean生命周期的例子
//实现Bean级生命周期接口
public class User implements BeanFactoryAware,BeanNameAware,InitializingBean,DisposableBean{
    private String name;
    private int age;

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void myinit(){
        System.out.println("初始化..."); 
        name="王五";
        age=55;
    }  

    public void say(){
        System.out.println(name+":"+age);
    }

    public void mydestory(){
        System.out.println("销毁中...");
    }

    public void destroy() throws Exception {
        System.out.println("听说我是最后被调用");

    }

    public void afterPropertiesSet() throws Exception {
        System.out.println("听说我是第三个被调用");

    }

    public void setBeanName(String arg0) {
        System.out.println("听说我是第一个被调用");

    }

    public void setBeanFactory(BeanFactory arg0) throws BeansException {
        System.out.println("听说我是第二个被调用");

    }
}

<bean id="user" class="com.cad.domain.User" init-method="myinit" destroy-method="mydestory">bean> 
// 我们进行测试
public class Test {
    @org.junit.Test
    public void test(){
        ClassPathXmlApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
        User user=(User) ac.getBean("user");  
        user.say();
        //destory方法执行必须在容器关闭之后才能执行
        ac.close();
    }
} 

执行结果如下:

Spring核心技术详解(二)_第4张图片

3、装配后处理器
//提供BeanPostProcessor的实现类
public class MyBeanPostProcessor implements BeanPostProcessor {

    public Object postProcessAfterInitialization(Object arg0, String arg1) throws BeansException {
        System.out.println("后处理器:初始化方法之后执行");
        return arg0;
    }

    public Object postProcessBeforeInitialization(Object arg0, String arg1) throws BeansException {
        System.out.println("后处理器:初始化方法之前执行");
        return arg0;
    }

}

<bean id="myBeanPostProcessor" class="com.cad.domain.MyBeanPostProcessor">bean> 
// 我们进行测试
public class Test {
    @org.junit.Test
    public void test(){
        ClassPathXmlApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
        User user=(User) ac.getBean("user");  
        ac.close();
    }
} 

仔细观察输出结果,就会发现验证了我们说的Bean的生命周期过程:
Spring核心技术详解(二)_第5张图片


4、关于Bean生命周期接口的一些问题

  通过实现Bean的生命周期接口对Bean进行额外的一些控制,虽然具有一些优点,但是带来了一个很严重的问题,我们的类必须实现这些接口,Bean和Spring紧密的结合在了一起,这就带来了很大的麻烦,所以,我们一般不使用这些接口,而是通过的init-method和destory-method属性来达到我们的初始化和销毁效果,达到框架解耦的问题。

  此外,BeanPostProcessor接口是像插件一样注册到Spring容器中,使应用与框架解耦,同时可以为我们完成一些额外的功能。例如可以获取动态代理,还有实现AOP功能。

你可能感兴趣的:(Spring)