前言
推荐一个学习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