【Spring】IOC的实现——Bean标签与依赖注入


Bean标签基本配置

一句话概括就是实现唯一标识id向完整类名class的映射。

也就是说,对象的构造交由Spring来完成(工厂模式设计);这种构造显然是通过反射完成的,因此要求类必须拥有无参构造函数。

注意三点:id唯一,class必须完整,多个id可能映射同一个class。

 

Bean标签范围配置

取值说明
singleton单例的,每次获取的是同一个Bean对象(默认)
prototype多例的,每次获取的是不同的Bean对象
requestWEB项目中,创建的Bean对象立即放到request域中
sessionWEB项目中,创建的Bean对象立即放到session域中
global sessionWEB项目中,需要有Portlet环境,否则相当于普通的session域

配置

<bean id="userDao" class="dao.impl.UserDaoImpl" scope="singleton"></bean>
<bean id="userDao" class="dao.impl.UserDaoImpl" scope="prototype"></bean>

测试

@Test
// 测试scope属性
public void test1() {
    ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
    UserDao userDao1 = (UserDao) app.getBean("userDao");
    UserDao userDao2 = (UserDao) app.getBean("userDao");
    System.out.println(userDao1);
    System.out.println(userDao2);
}
>>> dao.impl.UserDaoImpl@5a1c0542
	dao.impl.UserDaoImpl@5a1c0542
>>> dao.impl.UserDaoImpl@23faf8f2
	dao.impl.UserDaoImpl@2d6eabae

 
 

Bean生命周期配置

关于生命周期我们已经见怪不怪,无非就是 初始化-使用-销毁

生命周期是指Bean对象的生命周期,id与class绑定,因此配置就在applicationContext.xml中的那一行进行;初始化/销毁的参数名为init-method/destroy-method,参数值为该类的某两个方法(方法名没有限制):

public class UserDaoImpl implements UserDao {

    public void init() {
        System.out.println("初始化...");
    }

    public void destroy() {
        System.out.println("销毁...");
    }
    
}
<bean id="userDao" class="dao.impl.UserDaoImpl" init-method="init" destroy-method="destroy"></bean>

补充(🌟):

  Bean对象的范围配置(单例/多例)与其生命周期息息相关——singleton只在初始化容器(app)时创建一次对象,因此该对象的生命周期与整个容器绑定;prototype在初始化容器时并不创建对象而是在每次调用(getBean)时创建一个对象,因此这些对象的生命周期彼此独立,就像普通对象在失去最后一个指向它引用时才被GC机制标记回收。

 
 

Bean实例化的三种方法

无参构造方法

最常用、最重要、最简单的方式,默认调用了目的类的无参构造函数

<bean id="userDao" class="dao.impl.UserDaoImpl"></bean>

工厂静态方法

不直接构造目的类的对象,而是通过一个工厂的静态方法返回

public class StaticFactory {

    public static UserDao getUserDao() {
        return new UserDaoImpl();
    }
    
}
<bean id="userDao" class="StaticFactory" factory-method="getUserDao"></bean>

工厂实例方法

与上一种方法的区别在于工厂内返回目的类对象的方法是否是静态方法(static)。

如果工厂内的方法是静态的,就不用做出工厂的对象,使用工厂类名即可使用该方法;如果工厂内的方法并非静态,那么就需要先做出工厂的一个对象,再用该对象返回目的类对象。这个过程体现在了配置文件中:

public class DynamicFactory {

    public UserDao getUserDao() {
        return new UserDaoImpl();
    }
    
}
<bean id="factory" class="DynamicFactory"></bean>
<bean id="userDao" factory-bean="factory" factory-method="getUserDao"></bean>

 
 
 

Bean依赖注入(概念)

依赖注入(Dependency Injection)是Spring框架核心IOC的具体实现。

在编写程序时,通过控制反转,对象的创建权反转给了Spring;但在代码中存在依赖现象,比如service层需要调用dao层,IOC解耦只是降低它们的依赖关系,但并未消除。(下面有代码演示>_<)

Spring通过一种巧妙的设计优化来这个问题,将这种依赖也全权交给了Spring(如下图):

在这里插入图片描述

 
 

Bean依赖注入(一)

这是之前学习的经典代码,dao层作为service层的一个成员变量,且使用new获取对象:

public class UserServiceImpl implements UserService {

    private UserDao userDao = new UserDaoImpl();

    public void sayHi() {
        userDao.sayHi();
    }
}

下面使用Spring获取对象,但未使用依赖注入,UserService的代码仍旧依赖UserDao,它们俩并未完全解耦

public class UserServiceImpl implements UserService {

    public void sayHi() {
        ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserDao userDao = (UserDao) app.getBean("userDao");
        userDao.sayHi();
    }
}

实现service和dao的完全解耦,即在spring容器中,将dao注♂入到service体♂内。下面用两种方法予以实现:
 
set方法

public class UserServiceImpl implements UserService {

    private UserDao userDao;

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    public void sayHi() {
        userDao.sayHi();
    }
}
<bean id="userDao" class="dao.impl.UserDaoImpl"></bean>
<bean id="userService" class="service.impl.UserServiceImpl">
    <property name="userDao" ref="userDao"></property>
    <!-- 第一个usrDao是成员变量,第二个userDao是引用 -->
    <!-- 多说一句,说"成员变量"是不准确的,这个名称实际上是由setter函数还原回去的(setUserDao -> userDao) -->
</bean>

补充:代码不变,另一种配置方法(p命名空间)

<!-- 先在最上面的beans中加上xmlns:p="http://www.springframework.org/schema/p"> -->
<bean id="userDao" class="dao.impl.UserDaoImpl"></bean>
<bean id="userService" class="service.impl.UserServiceImpl" p:userDao-ref="userDao" />

 
有参构造方法

public class UserServiceImpl implements UserService {

    private UserDao userDao;

    public UserServiceImpl() {
    }

    public UserServiceImpl(UserDao userDao) {
        this.userDao = userDao;
    }

    public void sayHi() {
        userDao.sayHi();
    }
}
<bean id="userDao" class="dao.impl.UserDaoImpl"></bean>
<bean id="userService" class="service.impl.UserServiceImpl">
    <constructor-arg name="userDao" ref="userDao"></constructor-arg>
    <!-- 第一个usrDao是成员变量,第二个userDao是引用 -->
</bean>

 
 

Bean依赖注入(二)

依赖注入常用set方法。因此下面在配置时,默认用的setter而非有参构造。

简单数据类型的注入

<property name="userName" value="Alice"></property>
<property name="age" value="12"></property>

集合(List)的注入

<property name="list">
     <list>
     	<!-- 这是一个List<String> -->
         <value>aaaaaa</value>
         <value>bbbbbb</value>
         <value>cccccc</value>
     </list>
</property>

集合(Map)的注入

<property name="map">
    <map>
    	<!-- 这是一个Map<String, User> -->
        <entry key="no1" value-ref="user1"></entry>
        <entry key="no2" value-ref="user2"></entry>
    </map>
</property>

集合(Properties)的注入

<property name="properties">
    <props>
        <prop key="no1">aaaaaa</prop>
        <prop key="no2">bbbbbb</prop>
    </props>
</property>

 
总结(重点>_<)

容器中是一个个bean对象,依赖注入是指给这些对象(bean)注入属性(property)。

所注入的类型无非有两种,primitive主数据类型和引用。前者想到value,后者想到ref。

另外,集合类型(List,Map…)的注入也在上面做了演示。

明白了上面几点,写配置文件就是个简单的套娃过程。比如spring容器中有一个Loli对象,她的属性有姓名(String name)以及朋友们(List friends),所注入的内容你自己决定,试一试:

<bean id="loli" class="domain.Loli">
    <property name="name" value="Alice"></property>
    <property name="friends">
        <list>
            <ref bean="loli22"></ref>
            <ref bean="loli33"></ref>
        </list>
    </property>
</bean>

<bean id="loli22" class="domain.Loli">
    <property name="name" value="22娘"></property>
</bean>
<bean id="loli33" class="domain.Loli">
    <property name="name" value="33娘"></property>
</bean>
// 把被我们注入过的Loli拿出来瞧瞧
public class LoliTest {

    @Test
    public void test() {
        ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
        Loli loli = (Loli) app.getBean("loli");
        System.out.println(loli);
    }

}

在这里插入图片描述

 
 
 
 

要点补充

分模块开发(配置文件之间的import)

在spring主配置文件(applicationContext.xml)引入其他的配置文件,使逻辑更加清晰且防止配置文件过大:

<!-- applicationContext.xml中引入其他配置文件 -->
<import resource="applicationContext-student.xml"/>
<import resource="applicationContext-teacher.xml"/>

ApplicationContext的实现类

ApplicationContext用来接住spring容器(app),因此它显然是一个接口。后面常见的实现类有三种:

1)ClassPathXmlApplicationContext 从相对路径加载配置文件(推荐)

2)FileSystemXmlApplicationContext 从磁盘的绝对路径加载配置文件(可移植性差,不推荐)

3)AnnotationConfigApplicationContext 用注解配置时就用它
 
getBean()方法

在源码中找到它的两种最常用的重载:

public Object getBean(String name) throws BeansException {  
	assertBeanFactoryActive();   
	return getBeanFactory().getBean(name);
}

public <T> T getBean(Class<T> requiredType) throws BeansException {
	assertBeanFactoryActive();
	return getBeanFactory().getBean(requiredType);
}
ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");

Loli loli = (Loli) app.getBean("loli");

Loli loli = app.getBean(Loli.class);

1)第一种的参数是字符串(对象的id),返回的Bean实例的类型是Object,因此需要强转一下

2)第二种的参数是Class类型(类名.class),返回的Bean实例的类型是对应类型,不用强转直接接住

注意:第一种是“根据对象名获取对象”,所以不会出什么差错;第二种是“根据类名获取对象”,但容器中可能有多个对象(Bean)是同一个类(Class)的实例,因此当出现歧义时,会报错

 
 
 
 

 
 
 
 

 
 
 
 

More >_<

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值