上一章节我们讲解了Spring IOC中 Bean的装配过程,本章节我们主要讲解Spring的依赖注入方式。spring的花式DI,配置,看这个就够了。
猿思考是一个原创系列文章,帮助你从一个小白快速掌握基础知识,很多基础知识,在于思考的变通,更多精彩内容,敬请大家关注公主号猿人工厂,点击猿人养成获取!
setter方式:Spring IOC容器通过调用对象的setter方法将依赖的对象注入,这种方式由于使用起来非常简单,所以使用的频率是最高的。我们用一个例子来说明下怎么去使用:
编写一个TravelRouteDao接口和其实现类:
package com.pz.study.frame.spring.dao;
import com.pz.study.frame.spring.domain.TravelRoute;
/**
* 线路Service
*/
public interface TravelRouteDao {
/**
* 根据id查询
* @param rid
* @return
*/
public TravelRoutefindTravelRouteById(String travelRouteId);
}
package com.pz.study.frame.spring.dao.impl;
import com.pz.study.frame.spring.dao.TravelRouteDao;
import com.pz.study.frame.spring.domain.TravelRoute;
public class TravelRouteDaoImpl implements TravelRouteDao {
@Override
public TravelRoute findTravelRouteById(StringtravelRouteId) {
System.out.println("哈哈哈,是Dao我使用"+ travelRouteId+"操作数据");
return new TravelRoute();
}
}
编写TravelRouteManager接口和其实现类:
package com.pz.study.frame.spring.manager;
import com.pz.study.frame.spring.domain.TravelRoute;
/**
* 线路Service
*/
publicinterface TravelRouteManager {
/**
* 根据id查询
* @param rid
* @return
*/
public TravelRoutefindTravelRouteById(String travelRouteId);
}
注意:在实现类中,使用TravelRouteDao做为一个成员变量,并创建setter方法:
package com.pz.study.frame.spring.manager.impl;
import com.pz.study.frame.spring.dao.TravelRouteDao;
import com.pz.study.frame.spring.domain.TravelRoute;
import com.pz.study.frame.spring.manager.TravelRouteManager;
public class TravelRouteManagerImpl implements TravelRouteManager{
private TravelRouteDao travelRouteDao;
@Override
public TravelRoutefindTravelRouteById(String travelRouteId) {
System.out.println("哈哈哈,我在使用"+ travelRouteId+"的方式在调用程序噢");
returntravelRouteDao.findTravelRouteById(travelRouteId);
}
public void setTravelRouteDao(TravelRouteDao travelRouteDao) {
this.travelRouteDao = travelRouteDao;
}
}
在配置文件ApplicationContext.xml中增加配置:
注意:在xsd上增加配置:default-autowire="byName">
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd" default-autowire="byName">
创建测试方法:
@Test
publicvoid testManager(){
ApplicationContext applicationContext= new ClassPathXmlApplicationContext("applicationContext.xml");
TravelRouteManagertravelRouteManager=(TravelRouteManager) applicationContext.getBean("travelRouteManager");
travelRouteManager.findTravelRouteById("3333");
}
在没有Spring或者其他IOC容器时,我们需要使用new关键字手去创建TravelRouteDaoImpl对象,使用SpringIOC容器后,容器替我们去创建了TravelRouteDaoImpl的对象它通过setter方法注入到TravelRouteManagerImpl中的travelRouteDao属性上面。
Constructure:构造注入是容器通过构造方法将实例化的对象进行注入。使用一个类嘛,不用过构造方法,怎么创建对象嘛。有些类没有无参构造方法,没它还真不行了。
修改之前的TravelRouteManagerImpl,添加构造方法
package com.pz.study.frame.spring.manager.impl;
import com.pz.study.frame.spring.dao.TravelRouteDao;
import com.pz.study.frame.spring.domain.TravelRoute;
import com.pz.study.frame.spring.manager.TravelRouteManager;
public class TravelRouteManagerImpl implements TravelRouteManager{
private TravelRouteDao travelRouteDao;
public TravelRouteManagerImpl(TravelRouteDaotravelRouteDao) {
this.travelRouteDao = travelRouteDao;
}
@Override
public TravelRoutefindTravelRouteById(String travelRouteId) {
System.out.println("哈哈哈,我在使用"+ travelRouteId+"的方式在调用程序噢");
returntravelRouteDao.findTravelRouteById(travelRouteId);
}
publicvoid setTravelRouteDao(TravelRouteDao travelRouteDao) {
this.travelRouteDao = travelRouteDao;
}
}
修改applicationContext.xml文件,使用constructor-arg子标签,如果构造方法存在多个参数,就需要配置多个constructor-arg子标签,并且需要保证constructor-arg子标签中name属性和构造方法中的参数顺序一致。
注意:注释掉之前配置的travelRouteManager噢
随着annotation的流行,Spring也支持使用annotation的方式来完成依赖注入。下面主要讲解以下几个annotation的用法:
@Component、@Repository、@Service、@Controller
使用annotation的方式来实现依赖注入,就不需要在xml中配置bean了,不过在这之前需要增加一个扫描器:
如果仅仅是使用依赖注入的功能,以上几个annotation在作用上是没什么差别的,但是如果Web 应用程序采用了经典的三层分层结构的话,最好在持久层、业务层和控制层分别采用@Repository、@Service 和 @Controller 对分层中的类进行注释,而用 @Component 对那些比较中立的类进行注释,Spring会把使用了以上注释的类纳入特有的容器管理方式。在后续的AOP方面,我们会讲到。
下面我们来使用下@Component、@Repository、@Service、@Controller这几个注解:
编写一个Controller类
package com.pz.study.frame.spring.controller;
import org.springframework.stereotype.Controller;
import com.pz.study.frame.spring.domain.TravelRoute;
import com.pz.study.frame.spring.service.TravelRouteService;
@Controller("travelRouteController")
public class TravelRouteController {
private TravelRouteService travelRouteService;
public TravelRoute findTravelRouteById(StringtravelRouteId){
returntravelRouteService.findTravelRouteById(travelRouteId);
}
publicvoid setTravelRouteService(TravelRouteServicetravelRouteService) {
this.travelRouteService = travelRouteService;
}
}
修改之前的TravelRouteService TravelRouteManager TravelRouteDao和其实现类
package com.pz.study.frame.spring.service.impl;
import org.springframework.stereotype.Service;
import com.pz.study.frame.spring.domain.TravelRoute;
import com.pz.study.frame.spring.manager.TravelRouteManager;
import com.pz.study.frame.spring.service.TravelRouteService;
@Service(value="TravelRouteService")
publicclass TravelRouteServiceImpl implements TravelRouteService {
private TravelRouteManager travelRouteManager;
public TravelRouteServiceImpl(){
System.out.println("TravelRouteServiceImpl被实例化了");
}
@Override
public TravelRoutefindTravelRouteById(String travelRouteId) {
returntravelRouteManager.findTravelRouteById(travelRouteId);
}
publicvoid init() {
System.out.println("我是初始方法init我被执行了");
}
publicvoid destroy() {
System.out.println("我是销毁方法destroy我被执行了");
}
publicvoid setTravelRouteManager(TravelRouteManagertravelRouteManager) {
this.travelRouteManager = travelRouteManager;
}
}
package com.pz.study.frame.spring.manager.impl;
import org.springframework.stereotype.Component;
import com.pz.study.frame.spring.dao.TravelRouteDao;
import com.pz.study.frame.spring.domain.TravelRoute;
import com.pz.study.frame.spring.manager.TravelRouteManager;
@Component("travelRouteManager")
public class TravelRouteManagerImpl implements TravelRouteManager{
private TravelRouteDao travelRouteDao;
public TravelRouteManagerImpl(TravelRouteDao travelRouteDao) {
this.travelRouteDao = travelRouteDao;
}
@Override
public TravelRoutefindTravelRouteById(String travelRouteId) {
System.out.println("哈哈哈,我在使用"+ travelRouteId+"的方式在调用程序噢");
returntravelRouteDao.findTravelRouteById(travelRouteId);
}
public void setTravelRouteDao(TravelRouteDao travelRouteDao){
this.travelRouteDao = travelRouteDao;
}
}
package com.pz.study.frame.spring.dao.impl;
import org.springframework.stereotype.Repository;
import com.pz.study.frame.spring.dao.TravelRouteDao;
import com.pz.study.frame.spring.domain.TravelRoute;
@Repository("travelRouteDao")
publicclass TravelRouteDaoImpl implements TravelRouteDao {
@Override
public TravelRoutefindTravelRouteById(StringtravelRouteId) {
System.out.println("哈哈哈,是Dao我使用"+ travelRouteId+"操作数据");
return new TravelRoute();
}
}
编写测试用例:
@Test
publicvoid testController(){
ApplicationContext applicationContext= new ClassPathXmlApplicationContext("applicationContext.xml");
TravelRouteController travelRouteController=(TravelRouteController)applicationContext.getBean("travelRouteController");
travelRouteController.findTravelRouteById("3333");
}
运行测试用例看看效果
@Autowired和@Qualifier注解:
上面的例子中使用到的annotation都是用于注解类的。我们对类的属性的依赖注入还是通过setter方法实现的。Spring IOC 也提供了annotation用于注解属性,代替属性的setter方法,完成依赖注入。我们可以使用annotation——@Autowired来代替属性的setter方法,@Autowired默认情况下先按类型到容器中查找被注释标记的bean完成自动装配,如果发现找不到匹配的类型,就会按照名称去查找。如果还是无法找到与被注释的bean,程序会抛出异常,也可以设置Autowired的required为false,这样即使找不到需要被注入的bean,程序也不会抛出异常,但是也会导致一个新问题,由于属性没有被注入,又使用了属性的方法,从而导致程序运行时的空指针问题,这就需要大家自己权衡了。如果同一个类型的bean被注入多次,那么就需要使用@Qualifier来标记到底使用哪一个bean了。被@Qualifier注释的属性,会按照bean 的id来获取。
我们看下例子:
修改下Controller和TravelRouteServiceImpl以及TravelRouteManagerImpl感受下效果。
package com.pz.study.frame.spring.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
import com.pz.study.frame.spring.domain.TravelRoute;
import com.pz.study.frame.spring.service.TravelRouteService;
@Controller("travelRouteController")
publicclass TravelRouteController {
@Autowired
@Qualifier("travelRouteService")
private TravelRouteService travelRouteService;
public TravelRoute findTravelRouteById(StringtravelRouteId){
returntravelRouteService.findTravelRouteById(travelRouteId);
}
public void setTravelRouteService(TravelRouteServicetravelRouteService) {
this.travelRouteService = travelRouteService;
}
}
package com.pz.study.frame.spring.service.impl;
import org.springframework.stereotype.Service;
import com.pz.study.frame.spring.domain.TravelRoute;
import com.pz.study.frame.spring.manager.TravelRouteManager;
import com.pz.study.frame.spring.service.TravelRouteService;
@Service(value="TravelRouteService")
public class TravelRouteServiceImpl implements TravelRouteService {
private TravelRouteManager travelRouteManager;
public TravelRouteServiceImpl(){
System.out.println("TravelRouteServiceImpl被实例化了");
}
@Override
public TravelRoutefindTravelRouteById(String travelRouteId) {
returntravelRouteManager.findTravelRouteById(travelRouteId);
}
public void init() {
System.out.println("我是初始方法init我被执行了");
}
public void destroy() {
System.out.println("我是销毁方法destroy我被执行了");
}
public void setTravelRouteManager(TravelRouteManagertravelRouteManager) {
this.travelRouteManager = travelRouteManager;
}
}
package com.pz.study.frame.spring.manager.impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.pz.study.frame.spring.dao.TravelRouteDao;
import com.pz.study.frame.spring.domain.TravelRoute;
import com.pz.study.frame.spring.manager.TravelRouteManager;
@Component("travelRouteManager")
public class TravelRouteManagerImpl implements TravelRouteManager{
@Autowired(required=false)
private TravelRouteDao travelRouteDao;
public TravelRouteManagerImpl(TravelRouteDao travelRouteDao) {
this.travelRouteDao = travelRouteDao;
}
@Override
public TravelRoutefindTravelRouteById(String travelRouteId) {
System.out.println("哈哈哈,我在使用"+ travelRouteId+"的方式在调用程序噢");
returntravelRouteDao.findTravelRouteById(travelRouteId);
}
public void setTravelRouteDao(TravelRouteDao travelRouteDao) {
this.travelRouteDao = travelRouteDao;
}
}
注意:例子为了保持兼容之前使用setter的方式,并没有去掉setter方法。大家可以注释掉setter方法,然后运行测试用例看效果。
@Resource不是Spring IOC提供的annotation,它是由javax.annotation包下提供的,属于JAVAEE规范的一部分,Spring为了支持JAVAEE也做了对应的实现。使用@Resource可以替代@Autowired完成依赖注入。@Resource默认按照名称进行装配,可以通过name属性来指定需要装配的属性,如果没有指定name属性,默认按属性名称进行查找完成装配,当查找不到与属性名称匹配的bean时,才按照类型进行装配。需要注意的是,一但指定了@Resource的那么属性,就只会按照属性名称完成装配。我们看个小例子:
package com.pz.study.frame.spring.service.impl;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;
import com.pz.study.frame.spring.domain.TravelRoute;
importcom.pz.study.frame.spring.manager.TravelRouteManager;
importcom.pz.study.frame.spring.service.TravelRouteService;
@Service(value="TravelRouteService")
publicclass TravelRouteServiceImpl implements TravelRouteService {
@Resource(name="travelRouteManager")
private TravelRouteManager travelRouteManager;
publicTravelRouteServiceImpl(){
System.out.println("TravelRouteServiceImpl被实例化了");
}
@Override
public TravelRoute findTravelRouteById(StringtravelRouteId) {
returntravelRouteManager.findTravelRouteById(travelRouteId);
}
public void init() {
System.out.println("我是初始方法init我被执行了");
}
public void destroy() {
System.out.println("我是销毁方法destroy我被执行了");
}
public void setTravelRouteManager(TravelRouteManagertravelRouteManager) {
this.travelRouteManager = travelRouteManager;
}
}
运行测试用例,程序正常运行,说明已经完成装配。
使用注解实现初始化和销毁操作
@PostConstruct用于注解对象被实例化前被调用的方法。
@PreDestroy 用于注解对象被销毁前被调用的方法。
我们看下例子:
package com.pz.study.frame.spring.controller;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
import com.pz.study.frame.spring.domain.TravelRoute;
import com.pz.study.frame.spring.service.TravelRouteService;
@Controller("travelRouteController")
public class TravelRouteController {
public TravelRouteController(){
System.out.println("====TravelRouteController====被实例化了");
}
@Autowired
@Qualifier("travelRouteService")
private TravelRouteService travelRouteService;
public TravelRoutefindTravelRouteById(String travelRouteId){
returntravelRouteService.findTravelRouteById(travelRouteId);
}
public void setTravelRouteService(TravelRouteServicetravelRouteService) {
this.travelRouteService = travelRouteService;
}
@PostConstruct
public void before(){
System.out.println("====TravelRouteController==before==被实例化之后才调用我");
}
@PreDestroy
public void after(){
System.out.println("====TravelRouteController==after==被销毁前才调用我");
}
}
运行测试用例看下效果就知道了
@Scope注解
@Scope使用在类上用于指定bean的作用域,默认为singleton。
packagecom.pz.study.frame.spring.dao.impl;
importorg.springframework.context.annotation.Scope;
importorg.springframework.stereotype.Repository;
importcom.pz.study.frame.spring.dao.TravelRouteDao;
import com.pz.study.frame.spring.domain.TravelRoute;
@Repository("travelRouteDao")
@Scope("singleton")
publicclass TravelRouteDaoImpl implements TravelRouteDao {
@Override
public TravelRoutefindTravelRouteById(String travelRouteId) {
System.out.println("哈哈哈,是Dao我使用"+ travelRouteId+"操作数据");
return new TravelRoute();
}
}
关于使用注解还是XML做程序配置,其实都是为了配置程序运行时的信息,关于到底使用哪个更好,一直以来争论不断。注解的好处是,配置和代码在一起,一目了然,简单方便,注释是一种硬编码,修改后需要重新编译代码才能运行。
使用XML做程序配置,最大的好处是,配置发生变化后,无需编译代码,直接重启应用就可以了。这个好处在线上出现极端情况下,说实话比annotation好太多了。但是xml编写起来不如annotation直观,简洁。
如果同时使用了xml和annotation做配置,xml的配置的优先级高于注解,这样做的好处是,做到程序兼容,如果修改配置,只需要修改配置文件即可。但是程序看上去比较臃肿。
不过对于一个大型的有强大生命周期的项目而言,配置尽量清楚一些,可调整一些,打包编译上线需要你boss签字的,等打包签字的时间,事故等级都可能上升N个,不是大厂里混的,你是理解不了annotation其中的痛。
大家可能发现了,如果使用xml的方式配置文件,每增加一个bean就会编写一次配置文件。随着bean的增多,配置文件也会变得庞大无比,没有什么可读性,难以维护。Spring为了解决这个问题,是支持多个配置文件的,我们来看一个例子:
编写一个新的配置文件ApplicationContext2.xml:
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd" default-autowire="byName">
修改ApplicationContext.xml引入新的配置文件即可。
我建了一个群,群里有很多高手,欢迎大家入群探讨。