Spring学习——IOC控制反转、DI依赖注入(下)
一、bean的高级配置
1.1 配置信息的继承
指定parent属性为要重用的bean的id值,不写的属性就沿用,也可以重写定义属性
<!-- abstract="true":这个bean的配置是一个抽象的,不能获取他的实例,只能被别人用来继承 -->
<bean id="person08" class="com.zb.bean.Person" abstract="true">
<property name="lastName" value="张三"></property>
<property name="age" value="18"></property>
<property name="gender" value="男"></property>
<property name="email" value="[email protected]"></property>
</bean>
<!--parent:指定当前bean的配置信息继承于哪个,继承后可以省略公共属性值的配置 -->
<bean id="person09" parent="person08">
<property name="lastName" value="李四"></property>
</bean>
还可以指定属性abstract=“true”,这样的bean只能被用来继承信息,不能获取实例。否则会报异常 BeanIsAbstractException
<bean id="person08" class="com.zb.bean.Person" abstract="true">
<property name="lastName" value="张三"></property>
<property name="age" value="18"></property>
<property name="gender" value="男"></property>
<property name="email" value="[email protected]"></property>
</bean>
1.2 bean之间的依赖
多个bean的默认创建顺序,是按照配置顺序创建的
<bean id="person" class="com.zb.bean.Person"></bean>
<bean id="car" class="com.zb.bean.Car"></bean>
<bean id="book" class="com.zb.bean.Book"></bean>
有的时候创建一个bean的时候需要保证另外一个bean也被创建,这时我们称前面的bean对后面的bean有依赖。例如:要求创建Person对象的时候必须创建Car和Book对象
<!-- 创建Person对象的时候必须创建Car和Book对象 -->
<bean id="person" class="com.zb.bean.Person" depends-on="book,car"></bean>
<bean id="car" class="com.zb.bean.Car"></bean>
<bean id="book" class="com.zb.bean.Book"></bean>
1.3 bean的作用域scope
可以在<bean>元素的scope属性里设置bean的作用域,以决定这个bean是单实例的还是多实例的
<bean id="" class="" scope="作用域"/>
singleton
: 单例模式,是默认模式。在容器启动完成之前就已经创建好对象保存在容器中了。
prototype
:多实例的。容器启动不会去创建实例bean,每次调用getBean()的时候会产生一个新的对象。
request:在web环境下,同一次请求创建一个bean实例(没用)
session:在web环境下,同一次会话创建一个bean实例(没用)
1.4 bean的生命周期
在配置bean时,通过init-method和destroy-method 属性为bean指定初始化和销毁方法
public class Book {
private String bookName;
private String author;
public void myInit(){
System.out.println("这是图书的初始化方法");
}
public void myDestory(){
System.out.println("这是图书的销毁方法。。。");
}
}
//在配置文件中设置
<bean id="book01" class="com.zb.bean.Book" destroy-method="myDestory" init-method="myInit"></bean>
IOC容器中注册的bean的生命周期:
单实例bean:容器启动的时候就会创建好,容器关闭也会销毁创建的bean
(容器启动)构造器 —> 初始化方法 —> (容器关闭)销毁方法
多实例bean:获取的时候才去创建
获取bean(构造器--->初始化方法)--->容器关闭不会调用bean的销毁方法
1.4.1 bean的后置处理器
bean后置处理器允许在调用初始化方法前后对bean进行额外的处理
定义一个类实现org.springframework.beans.factory.config.BeanPostProcessor接口,在初始化方法被调用前后
,Spring将把每个bean实例分别传递给上述接口的以下两个方法:
postProcessBeforeInitialization(Object, String)、postProcessAfterInitialization(Object, String)。需要注册这个实现类
/**
* 1)、编写后置处理器的实现类
* 2)、将后置处理器注册在配置文件中(感觉类似于javaweb过滤器)
* @author zb
*
*/
public class MyBeanPostProcessor implements BeanPostProcessor{
/**
* 初始化之前调用
* Object bean, 传递过来的,将要初始化的bean
* String beanName:bean在xml中配置的id
*/
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
System.out.println("postProcessBeforeInitialization...【"+beanName+"】bean将要调用初始化方法了....这个bean是这样:【"+bean+"】");
//返回传入的bean
return bean;
}
/**
* postProcessAfterInitialization:
* 初始化方法之后调用
*/
@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
System.out.println("postProcessAfterInitialization...【"+beanName+"】bean初始化方法调用完了...AfterInitialization..");
//初始化之后返回的bean;返回的是什么,容器中保存的就是什么
return bean;
}
}
//在配置文件中注册:
<bean id="beanPostProcessor" class="com.zb.bean.MyBeanPostProcessor">
</bean>
<bean id="book01" class="com.zb.bean.Book" destroy-method="myDestory" init-method="myInit">
</bean>
执行结果:
这是图书的构造方法
postProcessBeforeInitialization...【book01】bean将要调用初始化方法了....这个bean是这样:【Book [bookName=null, author=null]】
这是图书的初始化方法
postProcessAfterInitialization...【book01】bean初始化方法调用完了...AfterInitialization..
总结:添加bean后置处理器后bean的生命周期
[1]通过构造器或工厂方法创建bean实例
[2]为bean的属性设置值和对其他bean的引用
[3]将bean实例传递给bean后置处理器的postProcessBeforeInitialization()方法
[4]调用bean的初始化方法
[5]将bean实例传递给bean后置处理器的postProcessAfterInitialization()方法
[6]bean可以使用了
[7]当容器关闭时调用bean的销毁方法
1.5 引用外部属性文件(重点)
当bean的配置信息逐渐增多时,查找和修改一些bean的配置信息就变得愈加困难。这时可以将一部分信息提取到bean配置文件的外部,以properties格式的属性文件保存起来,同时在bean的配置文件中引用properties属性文件中的内容,从而实现一部分属性值在发生变化时仅修改properties属性文件即可。这种技术多用于连接数据库的基本信息的配置。
Spring管理连接池(以配置C3P0的数据库连接池为零)
-
直接配置
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="user" value="root"></property> <property name="password" value="root"></property> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"></property> <property name="driverClass" value="com.mysql.jdbc.Driver"></property> </bean>
-
创建properties属性文件(重点)
-
首先新建一个数据库连接池的配置文件db.properties:
jdbc.username=root jdbc.password=root jdbc.jdbcUrl=jdbc:mysql://localhost:3306/test jdbc.driverClass=com.mysql.jdbc.Driver
-
引入context命名空间:
-
使用context:property-placeholder location=" … "标签导入数据库配置文件db.properties,就可以用$取出对应的属性了:
<context:property-placeholder location="classpath:dbconfig.properties"/> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="user" value="${jdbc.username}"></property> <property name="password" value="${jdbc.password}"></property> <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property> <property name="driverClass" value="${jdbc.driverClass}"></property> </bean>
-
1.6 基于XML的自动装配(不推荐)
-
设置
<bean/>
元素的:autowire
属性 -
autowire属性: 让Spring按照一定的规则方式自己去找合适的对象,并完成DI注入操作
注意:- 如果按照
byName
自动注入,要求所有的属性名字和id的名字必须保证一种规范的命名 - 如果安装
byType
注入,如果Spring容器中同一个类型有多个实例,报 bean不是唯一类型错误;
- 如果按照
二、SpEL表达式(了解)
好的博客:spring之SpEL表达式
-
简介:
SpEL表达式语言是一种表达式语言,是一种可以与一个基于spring的应用程序中的运行时对象交互的东西。有点类似于ognl表达式。总得来说SpEL表达式是一种简化开发的表达式,通过使用表达式来简化开发,减少一些逻辑、配置的编写。 -
基本语法:
SpEL使用#{…}作为定界符,所有在大框号中的字符都将被认为是SpEL表达式。
代码示例:
<bean id="car01" class="com.zb.bean.Car">
<property name="carName" value="宝马"></property>
</bean>
<bean id="person04" class="com.zb.bean.Person">
<!-- 进行运算符操作 -->
<property name="salary" value="#{20*12}"></property>
<!-- 引用其他bean的属性值作为自己某个属性的值-->
<property name="lastName" value="#{car01.carName}"></property>
<!-- 引用其他的bean -->
<property name="car" value="#{car01}"></property>
<!--
调用静态方法: UUID.randomUUID().toString();
#{T(全类名).静态方法名(1,2)}
-->
<!-- 调用静态方法 -->
<property name="email" value="#{T(java.util.UUID).randomUUID().toString().substring(1,3)}"></property>
<!-- 调用非静态方法; 对象.方法名 -->
<property name="gender" value="#{car01.getCarName()}"></property>
</bean>
三、通过注解配置bean(重点)
相对于XML方式而言,通过注解的方式配置bean更加简洁和优雅,而且和MVC组件化开发的理念十分契合,是开发中常用的使用方式。
3.1 使用注解标识组件
-
通过给bean上添加注解,可以快速的将bean加入到IOC容器中。创建Dao、Service、Controller层所需要用到的注解
①普通组件:@Component
标识一个受Spring IOC容器管理的组件
②持久化层组件:@Respository
标识一个受Spring IOC容器管理的Dao持久化层组件
③业务逻辑层组件:@Service
标识一个受Spring IOC容器管理的Service业务逻辑层组件
④表述层控制器组件:@Controller
标识一个受Spring IOC容器管理的表述层控制器组件
-
给bean上添加注解之后,还需要告诉Spring,自动扫描加了注解的组件:依赖context名称空间,还需要有AOP包的依赖。
<context:component-scan base-package="com.zb"/>。
-
组件的id默认是类名首字母小写
组件的id。默认为_组件类名首字母小写 @Controller("bookServlethh")或(value="bookServlethh") public class BookServlet{ }
-
组件的作用域—默认为单例模式
可通过@Scope修改作用域模式@Scope(value="prototype")//修改成多例模式 @Repository public class BookDao {
总结:使用注解将组件快速的加入到容器中需要几步:
1)给需要添加的组件上标四个注解的任何一个
2)告诉Spring自动扫描加了注解的组件;依赖context名称空间
3)一定要导入aop包,才支持加注解模式
3.2 context扫描组件的配置
-
指定要扫描的包
<!-- context:component-scan:自动组件扫描 base-package:指定扫描的基础包 --> <context:component-scan base-package="com.zb" ></context:component-scan>
-
使用context:exclude-filter指定扫描包时不包含的类
<!-- 使用context:exclude-filter指定扫描包时不包含的类 annotation:按照注解进行排除,expression属性中指定要排除的注解的全类名 assignable:排除某个具体的类,expression属性中指定要排除的类的全类名 aspectj:后来aspectj表达式(了解) custom:自定义一个TypeFilter;自己写代码决定哪些使用(了解) regex:使用正则表达式(了解) --> <context:component-scan base-package="com.zb" > <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> <context:exclude-filter type="assignable" expression="com.zb.dao.BookDao"></context:exclude-filter> </context:component-scan>
-
使用context:include-filter指定扫描包时要包含的类,只扫描进入哪些组件
<!-- 使用context:include-filter指定扫描包时要包含的类,只扫描进入哪些组件,默认都是全部扫描进来 use-default-filters需要设置为false,关闭默认 --> <context:component-scan base-package="com.zb" use-default-filters="false"> <!-- 指定只扫描哪些组件 --> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"></context:include-filter> </context:component-scan>
3.3 Autowired自动装配
直接在成员上添加@Autowired完成自动装配。
@Service
public class BookService {
//使用@Autowired完成成员BookDao的自动装配
@Autowired
private BookDao bookDao;
public void read() {
this.bookDao.readBook();
}
}
Autowired的执行流程:
-
首先按照类型去容器中找对应的组件,如果找到一个就赋值,找不到就抛异常;
-
如果有多个类型匹配时,会使用要注入的对象变量名称作为bean的id,在spring容器查找,找到了也可以注入成功,找不到就报错。
-
结合注解@Qualifer,指定一个id:在自动按照类型注入的基础之上,再按照指定的bean的id去查找。
-
@Autowired标注的属性如果找不到就会报错,可以指定required属性,找不到就自动装配null
@Controller public class BookServlet { /** * 按照类型找,找到一个就赋值,找到多个按照变量名作为id继续匹配 * @Qualifier:指定一个名作为id,让spring别使用变量名作为id,如果变量名和该id都可以匹配到,则按照该id匹配 * 找不到的话:@Autowired(required=false),找不到赋值为null */ @Qualifier("bookServiceExt") @Autowired(required=false) private BookService bookService1; }
注解加在方法上
/**
* 在方法上@Autowired,ioc容器加载时就会执行该方法,同时也会为形参自动装配
*
*/
@Autowired(required=false)
public void hahaha(Person person,@Qualifier("bookServiceExt")BookService bookService1){
System.out.println("Spring运行了这个方法"+person+bookService1);
}
@Autowired、@Resource、@Inject都可以作为注入注解
@Autowired:最强大;Spring的注解
①扩展性差,依赖Spring容器框架
@Resource:j2ee;java的标准【jdk标准】
①扩展性强:切换成另一个容器框架,其还会可以使用
【@Inject:在EJB环境下使用()】
四、Spring的单元测试(了解)
1、导入:Spring单元测试包。spring-test-4.0.0.RELEASE.jar
2、@ContextConfiguration(locations = ""),指定Spring的配置文件位置
3、@RunWith(),指定用哪种驱动进行单元测试,默认junit
@RunWith(SpringJUnit4ClassRunner.class)
使用Spring的单元测试模块来执行@Test注解的测试方法;
之前@Test由Junit执行
好处:
不需要ioc.getBean()获取组件;直接AutoWired组件,Spring自动装配
/**
* 使用Spring的单元测试:
* 1、@ContextConfiguration(locations="")指定Spring配置文件的位置
* 2、@RunWith(),指定用哪种驱动进行单元测试
*/
@ContextConfiguration(locations="classpath:applicationContext.xml")
@RunWith(SpringJUnit4ClassRunner.class)
public class SpringTest {
ApplicationContext ioc = null;
@Autowired
BookService bookService;
@Autowired
BookServiceExt bookServiceExt;
@Test
public void test() {
System.out.println(bookService);
System.out.println(bookServiceExt);
}
}
五、泛型的依赖注入
BaseDao
public abstract class BaseDao<T> {
public abstract void save();
}
BookDao
@Repository
public class BookDao extends BaseDao<Book> {
@Override
public void save() {
System.out.println("向数据库中保存了book");
}
}
UserDao
@Repository
public class UserDao extends BaseDao<User> {
@Override
public void save() {
System.out.println("向数据库中保存了User");
}
}
BaseService
public class BaseService<T> {
@Autowired
private BaseDao<T> baseDao;
public void save(){
System.out.println("自动注入的dao:"+baseDao);
baseDao.save();
}
}
BookService
@Service
public class BookService extends BaseService<Book> {
}
UserService
@Service
public class UserService extends BaseService<User>{
}
测试:
public class IOCTest {
@Test
public void test01(){
ApplicationContext ioc = new ClassPathXmlApplicationContext("ApplicationContext.xml");
BookService bookService = ioc.getBean(BookService.class);
UserService userService = ioc.getBean(UserService.class);
bookService.save();//向数据库中保存了book
userService.save();//向数据库中保存了User
System.out.println(bookService.getClass().getGenericSuperclass());//com.zb.service.BaseService<com.zb.bean.Book>
}
}
分析执行结果: