SSM框架之 Spring IOC

前言

推荐一个学习ssm 的视频 ssm框架一站式学习; 本文是其中 Spring IOC部分的笔记. Spring的基本术语就不赘述了.

本文的思想是将Spring 核心容器的配置以纯xml 配置, xml 和注解混合配置, 纯注解配置分别叙述, 读者可在三者的比较中, 了解IOC容器的细节.

总体来说, 三种配置解决的都是相同的问题: bean如何存入IOC容器? 如何在需要使用的时候, 从IOC 容器取出自动装配的 bean?

bean 基于xml

bean标签常用属性示例:

<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl" scope="singleton" init-method="init" destroy-method="destroy">
	<property name="" value=""| ref="">
    <property name="" value=""| ref="">
    <property name="" value=""| ref="">
</bean>

说明

标签用于属性注入, name-value 用于注入基本类型和String, name-ref 用于注入其他类型, 下面举例

<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd">
    <!-- 注入自定义类型的属性, 用 ref 属性指向容器中的其他bean, bean作用域为单例, 也是默认值-->
    <bean id="user1" class="com.zzy.entity.User" scope="singleton">
        <property name="id" value="12"></property>
        <property name="name" value="robbyzhan"></property>
        <property name="friend" ref="user2"></property>
    </bean>
	<!--注入简单类型的属性, 用 value属性-->
    <bean id="user2" class="com.zzy.entity.User">
        <property name="id" value="111"></property>
        <property name="name" value="xm"></property>
    </bean>
</beans>

测试 singleton 作用域:

public class Test1 {
    public static void main(String[] args){
        ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
        User user1 = context.getBean("user1", User.class);
        System.out.println(user1);
        // 将 singleton 作用域的 bean属性改变
        user1.setId(user1.getId() + 1);
        // 再次取出 singleton 作用域的bean
        User user2 = context.getBean("user1", User.class);
        System.out.println(user2);
    }
}

结果可看出, 当bean作用域为singleton, 两次取出的 bean是同一个
在这里插入图片描述

bean标签具体说明

<bean>作用:
    使得某个对象加入 IOC容器, 从而被 Spring管理。
    默认情况下它调用的是类中的无参构造函数。如果没有无参构造函数则不能创建成功。
<bean>的属性:
    id:给对象在容器中提供一个唯一标识。用于获取对象。
    class:指定类的全限定类名。用于反射创建对象。默认情况下调用无参构造函数。
    scope:指定对象的作用范围。
        * singleton :默认值,单例的.
        * prototype :多例的.
        * request :WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 request 域中.
        * session :WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 session 域中.
        * global session :WEB 项目中,应用在集群环境.如果没有集群环境那么 globalSession 相当于 	 	       session.	
    init-method:指定类中的初始化方法名称。
    destroy-method:指定类中销毁方法名称。

bean 单例和多例作用范围, 分别对应的生命周期

单例对象:scope="singleton"
	一个应用只有一个对象的实例。它的作用范围就是整个引用。
生命周期:
    对象出生:当应用加载,创建容器时,对象就被创建了。
    对象活着:只要容器在,对象一直活着。
    对象死亡:当应用卸载,销毁容器时,对象就被销毁了。
    
多例对象:scope="prototype"
	每次访问对象时,都会重新创建对象实例。
生命周期:
    与单例相反, 多例的 bean生命周期, IOC并不能管理
    对象出生:当使用对象时,创建新的对象实例, IOC无法决定。
    对象活着:只要对象在使用中,就一直活着。
    对象死亡:当对象长时间不用时,被 java 的垃圾回收器回收了, 所以在 IOC被人为销毁时, 多例的对象也不	     会因此销毁。

实例化bean的三种方式: 无参构造函数(默认), 静态工厂, 实例工厂

为bean 注入属性的两种方式: setter(默认), 构造函数注入;

使用xml 配置 bean及注入属性的实例:

<?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">
    <!-- 配置Service -->
    <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
        <!-- 注入dao -->
        <property name="accountDao" ref="accountDao"></property>
    </bean>

    <!--配置Dao对象-->
    <bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
        <!-- 注入QueryRunner -->
        <property name="runner" ref="runner"></property>
    </bean>

    <!--配置QueryRunner-->
    <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
        <!--注入数据源-->
        <constructor-arg name="ds" ref="dataSource"></constructor-arg>
    </bean>

    <!-- 配置数据源 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <!--连接数据库的必备信息-->
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/eesy"></property>
        <property name="user" value="root"></property>
        <property name="password" value="1234"></property>
    </bean>
</beans>
自定义 bean使用注解配置, 外部依赖 bean使用 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"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://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.zzy"></context:component-scan>
</beans>

将 bean存入IOC容器

@Component, @Repository, @Service, @Controller, 对应 bean标签的 id 和 class属性

class属性就由注解在哪个类上决定, id 属性默认是类名(非全限定)首字母小写, 如需指定, 按如下:

@Component("value"), 或 @Component(value="value")

bean 的注入(从IOC取出自动装配的 bean)

在IOC容器中, 有一系列bean, 每个都有id, class两个基本属性, 当我们需要取用的时候, 有以下方式

  • @Autowired, 按类注入

    假设 UserDaoImpl1 和 UserDaoImpl2都是 UserDao接口的实现类; @Autowired优先按照类型取, 也就是取出一个 UserDao类型(或其实现类型) 的bean, 如果bean有多个, 则按照引用名选取; 如果引用名 (userDaoImpl1)和 bean注册id还不匹配, 则编译错误

    @Autowired
    private UserDao userDaoImpl1;
    
  • @Qualifier, 按类查找的前提下, 再按 id查找注入, 比如上例等同于:

    @Autowired
    @Qualifier(value = "userDaoImpl1")
    private UserDao userDaoImpl;
    
  • @Resource, 仅按 id 注入, 上例等同于:

    @Resource
    private UserDao userDaoImpl1;
    

    @Resource(name = "userDaoImpl1")
    private UserDao userDaoImpl;
    
  • @Value, 注入属性(基本类型和 String)

    @Value("#{id.property}"}, 从IOC容器中注入某 bean的某属性
    @Value("${property}"}, 从 properties配置文件中注入某属性
    另有注入常量, 表达式, 可自行百度
    
完全使用注解 (@Configuration, @Bean, 及相关注解)

spring.xml 的演变

全xml 配置(例子在上面, 不推荐) -> 自己写的组件用注解, 依赖包中的组件(比如数据库连接池)用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"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://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.zzy"></context:component-scan>

    <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
        <constructor-arg name="ds" ref="dataSource"></constructor-arg>
    </bean>

    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/ee42"></property>
        <property name="user" value="root"></property>
        <property name="password" value="shsxt"></property>
    </bean>
</beans>

-> 全部用注解, 例子如下(和上面xml 的形式比较, 就知道各注解是什么意思了) :

@Configuration
@ComponentScan(basePackages = {"com.zzy"})
public class SpringConfig {
    @Bean("runner")
    @Scope("prototype")
    public QueryRunner runner(ComboPooledDataSource ds) {
        return new QueryRunner(ds);
    }

    @Bean("dataSource")
    public ComboPooledDataSource getDataSource() {
        String driverClass ="com.mysql.jdbc.Driver";
        String jdbcUrl ="jdbc:mysql://localhost:3306/ee42";
        String user ="root";
        String password = "shsxt";
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        try {
            dataSource.setDriverClass(driverClass);
            dataSource.setJdbcUrl(jdbcUrl);
            dataSource.setUser(user);
            dataSource.setPassword(password);
            return dataSource;
        } catch (PropertyVetoException e) {
            throw new RuntimeException(e);
        }
    }

}

上述例子存在的问题是, 把非公共的信息写在公共配置类里, 可以做如下优化:

@Configuration
@Import(JdbcConfig.class)
@ComponentScan(basePackages = {"com.zzy"})
@PropertySource("classpath:jdbc.properties")
public class SpringConfig {

}
public class JdbcConfig {
    @Value("${jdbc.driver}")
    private String driverClass;
    @Value("${jdbc.url}")
    private String jdbcUrl;
    @Value("${jdbc.username}")
    private String user;
    @Value("${jdbc.password}")
    private String password;

    @Bean("runner")
    @Scope("prototype")
    public QueryRunner runner(ComboPooledDataSource ds) {
        return new QueryRunner(ds);
    }

    @Bean("dataSource")
    public ComboPooledDataSource getDataSource() {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        try {
            dataSource.setDriverClass(driverClass);
            dataSource.setJdbcUrl(jdbcUrl);
            dataSource.setUser(user);
            dataSource.setPassword(password);
            return dataSource;
        } catch (PropertyVetoException e) {
            throw new RuntimeException(e);
        }

    }
}

jdbc.properties 资源文件, 放在 resources目录下:

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/ee42
jdbc.username=root
jdbc.password=shsxt

扩展: 自动装配时遇到多个同类型 bean, 如何用 @Qualifier 区分的方法

自定义 bean注册到 Spring容器中, 如果同类型的 bean有多个, 则可以通过@Autowired 先确定类, 再用 @Qualifier确定bean的 id

如果再@Bean 标签中需要注入其他bean, 但是其他 需要注入的bean属于同类型下有多个 bean, 那也可以用 @Qualifier指定某一个bean, 典型的有: 为QueryRunner 注入多数据源中的一个

public class JdbcConfig {
    @Value("${jdbc.driver}")
    private String driverClass;
    @Value("${jdbc.url}")
    private String jdbcUrl;
    @Value("${jdbc.username}")
    private String user;
    @Value("${jdbc.password}")
    private String password;

    @Bean("runner")
    @Scope("prototype")
    public QueryRunner runner(@Qualifier("ds1")DataSource ds) {
        return new QueryRunner(ds);
    }

    @Bean("ds1")
    public ComboPooledDataSource getDataSource1() {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        try {
            dataSource.setDriverClass(driverClass);
            dataSource.setJdbcUrl(jdbcUrl);
            dataSource.setUser(user);
            dataSource.setPassword(password);
            return dataSource;
        } catch (PropertyVetoException e) {
            throw new RuntimeException(e);
        }
    }

    @Bean("ds2")
    public ComboPooledDataSource getDataSource2() {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        try {
            dataSource.setDriverClass(driverClass);
            dataSource.setJdbcUrl(jdbcUrl);
            dataSource.setUser(user);
            dataSource.setPassword(password);
            return dataSource;
        } catch (PropertyVetoException e) {
            throw new RuntimeException(e);
        }
    }
}

按上述 配置, IOC中有两个 ComboPooledDataSource类型的bean, id 分别是 ds1和 ds2, 自动装配 QueryRunner对象并注入 DataSource 对象时, 需要 @Qualifier指定具体数据源

总结

  • @Configuration, 指定某类为Spring容器配置类, 相关概念:

    ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
    

    用作读取注解配置文件, 并实现容器初始化; 用这段代码显式指定容器的配置类后, 可缺省 @Configuration; 不过不建议缺省

  • @Bean(name= " “), 相当于<bean id=” " , 用于注解方法, 把方法的返回值作为 bean放入 IOC容器

    此注解的 name属性缺省时, bean的id 就默认为 @Bean 所标记的方法名

  • @Qualifier(" "): 从IOC 中取bean, 自动装配时, 可以先按 class取, 再按 id取, 此时@Qualifier规定了 id

  • @Scope(" "), 对应 bean标签的 scope属性

  • @ComponentScan (basePaceages= {"", “”, … }), 指定 bean扫描的包, 相当于 <context:component-scan

  • @Import (Class<?> … ), 引入其他配置文件作为子配置文件

  • @PropertySource(“classpath: “), 指定资源文件, 对照 @Value(”${ }”), 从资源文件取值

Spring 的 IOC和 DI, 归纳起来就是存 bean和取 bean的问题, 试对容器存取相关注解作分类:

  • 存bean的: @Component为代表, 还有@Controller, @Service, @Repository, @Bean(一般用于外部依赖类), @ComponentScan (扫描注解, 存入容器)
  • 取bean的: @Autowird 为代表, 还有@Resource, @Qualifier(在实现类和@Bean标签中作依赖注入, 需要指定bean id 时使用), @Value
  • 配置类相关注解: @Configuration, @Import, @PropertySource
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值