上篇文章我们学习了官网中的1.2,1.3两小节,主要是涉及了容器,以及Spring实例化对象的一些知识。这篇文章我们继续学习Spring官网,主要是针对1.4小节,主要涉及到Spring的依赖注入。虽然只有一节,但是涉及的东西确不少。话不多说,开始正文。
[TOC]
根据官网介绍,依赖注入主要分为两种方式
我们分别对以上两种方式进行测试,官网上用的是XML的方式,我这边就采用注解的方式了:
测试代码如下,我们通过在Service中注入LuBanService这个过程来
public class Main02 {
public static void main(String[] args) {
AnnotationConfigApplicationContext ac = new
// config类主要完成对类的扫描
AnnotationConfigApplicationContext(Config.class);
Service service = (Service) ac.getBean("service");
service.test();
}
}
@Component
public class LuBanService {
LuBanService(){
System.out.println("luBan create ");
}
}
@Component
public class Service {
private LuBanService luBanService;
public Service() {
System.out.println("service create");
}
public void test(){
System.out.println(luBanService);
}
// 通过autowired指定使用set方法完成注入
@Autowired
public void setLuBanService(LuBanService luBanService) {
System.out.println("注入luBanService by setter");
this.luBanService = luBanService;
}
}
输出如下:
luBan create
service create
注入luBanService by setter // 验证了确实是通过setter注入的
com.dmz.official.service.LuBanService@5a01ccaa
@Component
public class Service {
private LuBanService luBanService;
public Service() {
System.out.println("service create by no args constructor");
}
// 通过Autowired指定使用这个构造函数,否则默认会使用无参
@Autowired
public Service(LuBanService luBanService) {
System.out.println("注入luBanService by constructor with arg");
this.luBanService = luBanService;
System.out.println("service create by constructor with arg");
}
public void test(){
System.out.println(luBanService);
}
}
输出如下:
luBan create
注入luBanService by constructor // 验证了确实是通过constructor注入的
service create by constructor
com.dmz.official.service.LuBanService@1b40d5f0
在上面的验证中,大家可能会有以下几个疑问:
根据上图中官网所说,我们可以得出如下结论:
我们不完全按照官网顺序进行学习,先看这一小节,对应官网上的位置如下图:
首先我们思考一个问题,在有了依赖注入的情况下,为什么还需要方法注入这种方式呢?换而言之,方法注入解决了什么问题?
我们来看下面这种场景:
@Component
public class MyService {
@Autowired
private LuBanService luBanService;
public void test(int a){
luBanService.addAndPrint(a);
}
}
@Component
// 原型对象
@Scope("prototype")
public class LuBanService {
int i;
LuBanService() {
System.out.println("luBan create ");
}
// 每次将当前对象的属性i+a然后打印
public void addAndPrint(int a) {
i+=a;
System.out.println(i);
}
}
public class Main02 {
public static void main(String[] args) {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(Config.class);
MyService service = (MyService) ac.getBean("myService");
service.test(1);
service.test(2);
service.test(3);
}
}
在上面的代码中,我们有两个Bean,MyService为单例的Bean,LuBanService为原型的Bean。我们的本意可能是希望每次都能获取到不同的LuBanService,预期的结果应该打印出:
1,2,3
实际输出:
1 3 6
这个结果说明我们每次调用到的LuBanService是同一个对象。当然,这也很好理解,因为在依赖注入阶段我们就完成了LuBanService的注入,之后我们在调用测试方法时,不会再去进行注入,所以我们一直使用的是同一个对象。
我们可以这么说,原型对象在这种情况下,失去了原型的意义,因为每次都使用的是同一个对象。那么如何解决这个问题呢?只要我每次在使用这个Bean的时候都去重新获取就可以了,那么这个时候我们可以通过方法注入来解决。
又分为以下两种方式:
@Component
public class MyService implements ApplicationContextAware {
private ApplicationContext applicationContext;
public void test(int a) {
LuBanService luBanService = ((LuBanService) applicationContext.getBean("luBanService"));
luBanService.addAndPrint(a);
}
@Override
public void setApplicationContext(@Nullable ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
@Component
public class MyService{
@Autowired
private ApplicationContext applicationContext;
public void test(int a) {
LuBanService luBanService = ((LuBanService) applicationContext.getBean("luBanService"));
luBanService.addAndPrint(a);
}
}
@Component
public class MyService{
public void test(int a) {
LuBanService luBanService = lookUp();
luBanService.addAndPrint(a);
}
//
@Lookup
public LuBanService lookUp(){
return null;
}
}
方法注入还有一种方式,即通过replace-method这种形式,没有找到对应的注解,所以这里我们也就用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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="myService" class="com.dmz.official.service.MyService">
<replaced-method replacer="replacer" name="test"/>
</bean>
<bean id="replacer" class="com.dmz.official.service.MyReplacer"/>
</beans>
public class MyReplacer implements MethodReplacer {
@Override
public Object reimplement(Object obj, Method method, Object[] args) throws Throwable {
System.out.println("替代"+obj+"中的方法,方法名称:"+method.getName());
System.out.println("执行新方法中的逻辑");
return null;
}
}
public class MyService{
public void test(int a) {
System.out.println(a);
}
}
public class Main {
public static void main(String[] args) {
ClassPathXmlApplicationContext cc =
new ClassPathXmlApplicationContext("application.xml");
MyService myService = ((MyService) cc.getBean("myService"));
myService.test(1);
}
}
执行结果:
替代com.dmz.official.service.MyService$$EnhancerBySpringCGLIB$$61c14242@63e31ee中的方法,方法名称:test
执行新方法中的逻辑
这里需要注意一点:
我在测试replace-method这种方法注入的方式时,受动态代理的影响,一直想将执行我们被替代的方法。用代码体现如下:
public class MyReplacer implements MethodReplacer {
@Override
public Object reimplement(Object obj, Method method, Object[] args) throws Throwable {
// System.out.println("替代"+obj+"中的方法,方法名称:"+method.getName());
// System.out.println("执行新方法中的逻辑");
method.invoke(obj,args);
return null;
}
}
但是,这段代码是无法执行的,会报栈内存溢出。因为obj是我们的代理对象,method.invoke(obj,args)执行时会进入方法调用的死循环。最终我也没有找到一种合适的方式来执行被替代的方法。目前看来这可能也是Spring的设计,所以我们使用replace-method的场景应该是想完全替代某种方法的执行逻辑,而不是像AOP那样更多的用于在方法的执行前后等时机完成某些逻辑。
可以说,一个对象的依赖就是它自身的属性,Spring中的依赖注入就是属性注入。
画图如下:
这篇文章到这里就结束了,看完记得点个关注+分享,我们下篇文章再见!