Spring系列第14篇:lookup-method和replaced-method比较陌生,怎么玩的?

star2017 1年前 ⋅ 561 阅读

lookup-method:方法查找

通常情况下,我们使用的bean都是单例的,如果一个bean需要依赖于另一个bean的时候,可以在当前bean中声明另外一个bean引用,然后注入依赖的bean,此时被依赖的bean在当前bean中自始至终都是同一个实例。

先来个案例回顾一下

  1. package com.javacode2018.lesson001.demo13.normal;
  2. public class ServiceA {
  3. }
  1. package com.javacode2018.lesson001.demo13.normal;
  2. public class ServiceB {
  3. private ServiceA serviceA;
  4. public ServiceA getServiceA() {
  5. return serviceA;
  6. }
  7. public void setServiceA(ServiceA serviceA) {
  8. this.serviceA = serviceA;
  9. }
  10. }

上面2个类,ServiceA和ServiceB,而ServiceB中需要用到ServiceA,可以通过setServiceA将serviceA注入到ServiceB中,spring配置如下:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans
  5. http://www.springframework.org/schema/beans/spring-beans-4.3.xsd">
  6. <bean id="serviceA" class="com.javacode2018.lesson001.demo13.normal.ServiceA" scope="prototype"/>
  7. <bean id="serviceB" class="com.javacode2018.lesson001.demo13.normal.ServiceB">
  8. <property name="serviceA" ref="serviceA"/>
  9. </bean>
  10. </beans>

上面serviceA的scope是prototype,表示serviceA是多例的,每次从容器中获取serviceA都会返回一个新的对象。

而serviceB的scope没有配置,默认是单例的,通过property元素将serviceA注入。

来个测试案例,如下:

  1. package com.javacode2018.lesson001.demo13;
  2. import com.javacode2018.lesson001.demo13.normal.ServiceA;
  3. import com.javacode2018.lesson001.demo13.normal.ServiceB;
  4. import org.junit.Test;
  5. import org.springframework.context.support.ClassPathXmlApplicationContext;
  6. /**
  7. * 公众号:路人甲Java,工作10年的前阿里P7分享Java、算法、数据库方面的技术干货!坚信用技术改变命运,让家人过上更体面的生活!
  8. * lookupMethod的使用
  9. */
  10. public class LookupMethodTest {
  11. @Test
  12. public void normalBean() {
  13. String beanXml = "classpath:/com/javacode2018/lesson001/demo13/normalBean.xml";
  14. ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(beanXml);
  15. System.out.println(context.getBean(ServiceA.class)); //@1
  16. System.out.println(context.getBean(ServiceA.class)); //@2
  17. System.out.println("serviceB中的serviceA");
  18. ServiceB serviceB = context.getBean(ServiceB.class); //@3
  19. System.out.println(serviceB.getServiceA()); //@4
  20. System.out.println(serviceB.getServiceA()); //@5
  21. }
  22. }

@1和@2从容器中按照类型查找ServiceA对应的bean。

@3:从容器中获取ServiceB

@4和@5:获取serviceB中的serviceA对象

运行normalBean()看一下效果:

  1. com.javacode2018.lesson001.demo13.normal.ServiceA@5bfa9431
  2. com.javacode2018.lesson001.demo13.normal.ServiceA@5db250b4
  3. serviceB中的serviceA
  4. com.javacode2018.lesson001.demo13.normal.ServiceA@223f3642
  5. com.javacode2018.lesson001.demo13.normal.ServiceA@223f3642

从输出中可以看出,@1和@2输出了不同的ServiceA,而@4和@5输出的是同一个serviceA,这是因为serviceB是单例的,serviceB中的serviceA会在容器创建serviceB的时候,从容器中获取一个serviceA将其注入到serviceB中,所以自始至终serviceB中的serviceA都是同一个对象。

如果我们希望beanB中每次使用beanA的时候beanA都是一个新的实例,我们怎么实现呢?

我们可以在serviceB中加个方法去获取serviceA,这个方法中我们主动去容器中获取serviceA,那么每次获取到的都是不同的serviceA实例。

那么问题来了,我们如何在serviceB中获取到spring容器呢?

spring中有个接口ApplicationContextAware

  1. org.springframework.context.ApplicationContextAware
  2. public interface ApplicationContextAware extends Aware {
  3. void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
  4. }

上面这个接口有一个方法setApplicationContext,这个接口给了自定义的bean中获取applicationContext的能力,当我们的类实现这个接口之后,spring容器创建bean对象的时候,如果bean实现了这个接口,那么容器会自动调用setApplicationContext方法,将容器对象applicationContext传入,此时在我们的bean对象中就可以使用容器的任何方法了。

下面我们就通过ApplicationContextAware接口来实现单例bean中使用多例bean的案例。

单例bean中使用多例bean:ApplicationContext接口的方式

ServiceA.java

  1. package com.javacode2018.lesson001.demo13.applicationcontextaware;
  2. public class ServiceA {
  3. }

ServiceB.java

  1. package com.javacode2018.lesson001.demo13.applicationcontextaware;
  2. import org.springframework.beans.BeansException;
  3. import org.springframework.context.ApplicationContext;
  4. import org.springframework.context.ApplicationContextAware;
  5. public class ServiceB implements ApplicationContextAware { //@1
  6. public void say(){
  7. ServiceA serviceA = this.getServiceA();//@2
  8. System.out.println("this:"+this+",serviceA:"+ serviceA);
  9. }
  10. public ServiceA getServiceA() {
  11. return this.context.getBean(ServiceA.class);//@3
  12. }
  13. private ApplicationContext context;
  14. @Override
  15. public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
  16. this.context = applicationContext;
  17. }
  18. }

注意上面代码,ServiceB实现了ApplicationContextAware接口,然后实现了这个接口中的setApplicationContext方法,spring容器在创建ServiceB的时候会自动调用setApplicationContext方法。

@3:从容器中主动去获取ServiceA,这样每次获取到的ServiceA都是一个新的实例。

@2:say方法中调用getServiceA方法获取ServiceA对象,然后将其输出。

alicationcontextaware.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans
  5. http://www.springframework.org/schema/beans/spring-beans-4.3.xsd">
  6. <bean id="serviceA" class="com.javacode2018.lesson001.demo13.applicationcontextaware.ServiceA" scope="prototype"/>
  7. <bean id="serviceB" class="com.javacode2018.lesson001.demo13.applicationcontextaware.ServiceB"/>
  8. </beans>

上面定义了2个bean,第一个是多例的。

测试用例

  1. @Test
  2. public void alicationcontextaware() {
  3. String beanXml = "classpath:/com/javacode2018/lesson001/demo13/alicationcontextaware.xml";
  4. ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(beanXml);
  5. System.out.println(context.getBean(com.javacode2018.lesson001.demo13.applicationcontextaware.ServiceA.class)); //@1
  6. System.out.println(context.getBean(com.javacode2018.lesson001.demo13.applicationcontextaware.ServiceA.class)); //@2
  7. System.out.println("serviceB中的serviceA");
  8. com.javacode2018.lesson001.demo13.applicationcontextaware.ServiceB serviceB = context.getBean(com.javacode2018.lesson001.demo13.applicationcontextaware.ServiceB.class); //@3
  9. serviceB.say();
  10. serviceB.say();
  11. }

运行输出:

  1. com.javacode2018.lesson001.demo13.applicationcontextaware.ServiceA@78047b92
  2. com.javacode2018.lesson001.demo13.applicationcontextaware.ServiceA@8909f18
  3. serviceB中的serviceA
  4. this:com.javacode2018.lesson001.demo13.applicationcontextaware.ServiceB@79ca92b9,serviceA:com.javacode2018.lesson001.demo13.applicationcontextaware.ServiceA@1460a8c0
  5. this:com.javacode2018.lesson001.demo13.applicationcontextaware.ServiceB@79ca92b9,serviceA:com.javacode2018.lesson001.demo13.applicationcontextaware.ServiceA@4f638935

最后2行是是serviceB中的say方法输出的,可以看出serviceB是一个对象,而serviceA是不同的对象。

单例bean中使用多例bean:lookup-method方式实现

上面这种方式实现了单例bean中使用多例bean的需求,但是用到spring中的接口ApplicationContextAware,此时对spring的api有耦合的作用,我们一直推行高内聚低耦合,所以我们得寻求更好的办法。

能不能有这样的功能,当serviceB中调用getServiceA的时候,系统自动将这个方法拦截,然后去spring容器中查找对应的serviceA对象然后返回,spring中的lookup-method就可以实现这样的功能。

下面我们使用lookup-method来实现一下。

ServiceA.java

  1. package com.javacode2018.lesson001.demo13.lookupmethod;
  2. public class ServiceA {
  3. }

ServiceB.java

  1. package com.javacode2018.lesson001.demo13.lookupmethod;
  2. public class ServiceB {
  3. public void say() {
  4. ServiceA serviceA = this.getServiceA();
  5. System.out.println("this:" + this + ",serviceA:" + serviceA);
  6. }
  7. public ServiceA getServiceA() { //@1
  8. return null;
  9. }
  10. }

注意上面的@1,这个方法中返回了一个null对象,下面我们通过spring来创建上面2个bean对象,然后让spring对上面的getServiceA方法进行拦截,返回指定的bean,如下:

lookupmethod.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans
  5. http://www.springframework.org/schema/beans/spring-beans-4.3.xsd">
  6. <bean id="serviceA" class="com.javacode2018.lesson001.demo13.lookupmethod.ServiceA" scope="prototype"/>
  7. <bean id="serviceB" class="com.javacode2018.lesson001.demo13.lookupmethod.ServiceB">
  8. <lookup-method name="getServiceA" bean="serviceA"/>
  9. </bean>
  10. </beans>

注意上面的配置,重点在于这行配置:

  1. <lookup-method name="getServiceA" bean="serviceA"/>

当我们调用serviceB中的getServiceA方法的时候,这个方法会拦截,然后会按照lookup-method元素中bean属性的值作为bean的名称去容器中查找对应bean,然后作为getServiceA的返回值返回,即调用getServiceA方法的时候,会从spring容器中查找id为serviceA的bean然后返回。

测试用例

LookupMethodTest中加个方法,如下:

  1. @Test
  2. public void lookupmethod() {
  3. String beanXml = "classpath:/com/javacode2018/lesson001/demo13/lookupmethod.xml";
  4. ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(beanXml);
  5. System.out.println(context.getBean(com.javacode2018.lesson001.demo13.lookupmethod.ServiceA.class)); //@1
  6. System.out.println(context.getBean(com.javacode2018.lesson001.demo13.lookupmethod.ServiceA.class)); //@2
  7. System.out.println("serviceB中的serviceA");
  8. com.javacode2018.lesson001.demo13.lookupmethod.ServiceB serviceB = context.getBean(com.javacode2018.lesson001.demo13.lookupmethod.ServiceB.class); //@3
  9. serviceB.say();
  10. serviceB.say();
  11. }

运行看看效果:

  1. com.javacode2018.lesson001.demo13.lookupmethod.ServiceA@619713e5
  2. com.javacode2018.lesson001.demo13.lookupmethod.ServiceA@708f5957
  3. serviceB中的serviceA
  4. this:com.javacode2018.lesson001.demo13.lookupmethod.ServiceB$$EnhancerBySpringCGLIB$$aca8be5a@68999068,serviceA:com.javacode2018.lesson001.demo13.lookupmethod.ServiceA@7722c3c3
  5. this:com.javacode2018.lesson001.demo13.lookupmethod.ServiceB$$EnhancerBySpringCGLIB$$aca8be5a@68999068,serviceA:com.javacode2018.lesson001.demo13.lookupmethod.ServiceA@2ef3eef9

注意最后2行的输出,serviceA是调用this.getServiceA()方法获取 ,源码中这个方法返回的是null,但是spring内部对这个方法进行了拦截,每次调用这个方法的时候,都会去容器中查找serviceA,然后返回,所以上面最后2行的输出中serviceA是有值的,并且是不同的serviceA实例。

lookup-method:看其名字,就知道意思:方法查找,调用name属性指定的方法的时候,spring会对这个方法进行拦截,然后去容器中查找lookup-method元素中bean属性指定的bean,然后将找到的bean作为方法的返回值返回。

这个地方底层是使用cglib代理实现的,后面有篇文章会详细介绍代理的2种实现,到时候大家注意下,spring中很多牛逼的功能都是靠代理实现的。

spring提供的还有一个功能,同样可以可以解决上面单例bean中用到多例bean的问题,也就是下面我们要说的replaced-method。

replaced-method:方法替换

replaced-method:方法替换,比如我们要调用serviceB中的getServiceA的时候,我们可以对serviceB这个bean中的getServiceA方法进行拦截,把这个调用请求转发到一个替换者处理。这就是replaced-method可以实现的功能,比lookup-method更强大更灵活。

replaced-method的使用3个步骤

步骤一:定义替换者

自定义一个替换者,替换者需要实现spring中的MethodReplacer接口,看一下这个接口的定义:

  1. package org.springframework.beans.factory.support;
  2. import java.lang.reflect.Method;
  3. public interface MethodReplacer {
  4. /**
  5. * @param obj 被替换方法的目标对象
  6. * @param method 目标对象的方法
  7. * @param args 方法的参数
  8. * @return return value for the method
  9. */
  10. Object reimplement(Object obj, Method method, Object[] args) throws Throwable;
  11. }

当调用目标对象需要被替换的方法的时候,这个调用请求会被转发到上面的替换者的reimplement方法进行处理。

如:

  1. package com.javacode2018.lesson001.demo14;
  2. import org.springframework.beans.BeansException;
  3. import org.springframework.beans.factory.support.MethodReplacer;
  4. import org.springframework.context.ApplicationContext;
  5. import org.springframework.context.ApplicationContextAware;
  6. import java.lang.reflect.Method;
  7. import java.util.Map;
  8. /**
  9. * servieB的方法替换者
  10. */
  11. public class ServiceBMethodReplacer implements MethodReplacer, ApplicationContextAware {
  12. @Override
  13. public Object reimplement(Object obj, Method method, Object[] args) throws Throwable {
  14. return this.context.getBean(ServiceA.class);
  15. }
  16. private ApplicationContext context;
  17. @Override
  18. public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
  19. this.context = applicationContext;
  20. }
  21. }
步骤二:定义替换者bean
  1. <!-- 定义替换者bean -->
  2. <bean id="serviceBMethodReplacer" class="com.javacode2018.lesson001.demo14.ServiceBMethodReplacer" />
步骤二:通过replaced-method元素配置目标bean需要被替换的方法
  1. <bean id="serviceB" class="com.javacode2018.lesson001.demo14.ServiceB">
  2. <replaced-method name="getServiceA" replacer="serviceAMethodReplacer"/>
  3. </bean>

注意上面的replaced-method元素的2个属性:

name:用于指定当前bean需要被替换的方法

replacer:替换者,即实现了MethodReplacer接口的类对应的bean

上面配置中当调用serviceB的getServiceA的时候,会自动调用serviceAMethodReplacer这个bean中的reimplement方法进行处理。

案例

ServiceA.java
  1. package com.javacode2018.lesson001.demo14;
  2. public class ServiceA {
  3. }
ServiceB.java
  1. package com.javacode2018.lesson001.demo14;
  2. public class ServiceB {
  3. public void say() {
  4. ServiceA serviceA = this.getServiceA();
  5. System.out.println("this:" + this + ",serviceA:" + serviceA);
  6. }
  7. public ServiceA getServiceA() { //@1
  8. return null;
  9. }
  10. }

上面getServiceA需要返回一个ServiceA对象,此处返回的是null,下面我们通过spring对这个方法进行替换,然后从容器中获取ServiceA然后返回,下面我们来看看替换者的代码。

替换者ServiceBMethodReplacer.java

这个替换者会替换ServiceB中的getServiceA方法

  1. package com.javacode2018.lesson001.demo14;
  2. import org.springframework.beans.BeansException;
  3. import org.springframework.beans.factory.support.MethodReplacer;
  4. import org.springframework.context.ApplicationContext;
  5. import org.springframework.context.ApplicationContextAware;
  6. import java.lang.reflect.Method;
  7. import java.util.Map;
  8. /**
  9. * servieB的方法替换者
  10. */
  11. public class ServiceBMethodReplacer implements MethodReplacer, ApplicationContextAware {
  12. @Override
  13. public Object reimplement(Object obj, Method method, Object[] args) throws Throwable {
  14. return this.context.getBean(ServiceA.class);
  15. }
  16. private ApplicationContext context;
  17. @Override
  18. public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
  19. this.context = applicationContext;
  20. }
  21. }
spring中bean配置文件:replacedmethod.xml
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans
  5. http://www.springframework.org/schema/beans/spring-beans-4.3.xsd">
  6. <!-- 定义替换者bean -->
  7. <bean id="serviceBMethodReplacer" class="com.javacode2018.lesson001.demo14.ServiceBMethodReplacer" />
  8. <bean id="serviceA" class="com.javacode2018.lesson001.demo14.ServiceA" scope="prototype"/>
  9. <bean id="serviceB" class="com.javacode2018.lesson001.demo14.ServiceB">
  10. <replaced-method name="getServiceA" replacer="serviceBMethodReplacer"/>
  11. </bean>
  12. </beans>
测试用例ReplacedMethodTest
  1. package com.javacode2018.lesson001.demo14;
  2. import org.junit.Test;
  3. import org.springframework.context.support.ClassPathXmlApplicationContext;
  4. /**
  5. * 公众号:路人甲Java,工作10年的前阿里P7分享Java、算法、数据库方面的技术干货!坚信用技术改变命运,让家人过上更体面的生活!
  6. * replaced-method:方法替换
  7. */
  8. public class ReplacedMethodTest {
  9. @Test
  10. public void replacedmethod() {
  11. String beanXml = "classpath:/com/javacode2018/lesson001/demo14/replacedmethod.xml";
  12. ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(beanXml);
  13. System.out.println(context.getBean(ServiceA.class)); //@1
  14. System.out.println(context.getBean(ServiceA.class)); //@2
  15. System.out.println("serviceB中的serviceA");
  16. ServiceB serviceB = context.getBean(ServiceB.class); //@3
  17. serviceB.say();
  18. serviceB.say();
  19. }
  20. }
运行输出
  1. com.javacode2018.lesson001.demo14.ServiceA@7722c3c3
  2. com.javacode2018.lesson001.demo14.ServiceA@2ef3eef9
  3. serviceB中的serviceA
  4. this:com.javacode2018.lesson001.demo14.ServiceB$$EnhancerBySpringCGLIB$$21bb8912@243c4f91,serviceA:com.javacode2018.lesson001.demo14.ServiceA@291ae
  5. this:com.javacode2018.lesson001.demo14.ServiceB$$EnhancerBySpringCGLIB$$21bb8912@243c4f91,serviceA:com.javacode2018.lesson001.demo14.ServiceA@61df66b6

从输出中可以看出结果和lookup-method案例效果差不多,实现了单例bean中使用多例bean的案例。

输出中都有CGLIB这样的字样,说明这玩意也是通过cglib实现的。

总结

  1. lookup-method:方法查找,可以对指定的bean的方法进行拦截,然后从容器中查找指定的bean作为被拦截方法的返回值
  2. replaced-method:方法替换,可以实现bean方法替换的效果,整体来说比lookup-method更灵活一些
  3. 单例bean中使用多例bean,本文中列出了3种方式,大家消化一下。

案例源码

  1. 链接:https://pan.baidu.com/s/1p6rcfKOeWQIVkuhVybzZmQ
  2. 提取码:zr99

最新资料

更多内容请访问:IT源点

相关文章推荐

全部评论: 0

    我有话说: