方法注入:
我们不完全按照官网顺序进行学习,先看这一小节,对应官网上的位置如下图
为什么需要方法注入:
首先我们思考一个问题,在有了依赖注入的情况下,为什么还需要方法注入这种方式呢?换而言之,方法注入解决了什么问题?
我们来看下面这种场景:
@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的时候都去重新获取就可以了,那么这个时候我们可以通过方法注入来解决。
通过注入上下文(applicationContext对象)
又分为以下两种方式:
实现org.springframework.context.ApplicationContextAware接口
@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); } }
通过@LookUp的方式(也分为注解跟XML两种方式,这里只演示注解的)
@Component public class MyService{ public void test(int a) { LuBanService luBanService = lookUp(); luBanService.addAndPrint(a); } // @Lookup public LuBanService lookUp(){ return null; } }
方法注入 之 replace-method
方法注入还有一种方式,即通过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那样更多的用于在方法的执行前后等时机完成某些逻辑。
依赖注入跟方法注入的总结:
- 我们首先要明确一点,什么是依赖(Dependencies)?来看官网中的一段话:
可以说,一个对象的依赖就是它自身的属性,Spring中的依赖注入就是属性注入。
- 我们知道一个对象由两部分组成:属性+行为(方法),可以说Spring通过属性注入+方法注入的方式掌控的整个bean。
- 属性注入跟方法注入都是Spring提供给我们用来处理Bean之间协作关系的手段
- 属性注入有两种方式:构造函数,Setter方法。
- 方法注入(LookUp Method跟Replace Method)需要依赖动态代理完成
- 方法注入对属性注入进行了一定程度上的补充,因为属性注入的情况下,原型对象可能会失去原型的意义,见:为什么需要方法注入
画图如下: