zoukankan      html  css  js  c++  java
  • lookup-method和replace-method注入

    官方文档说明:

    在Spring5核心的1.4.6章节

    在大多数应用程序方案中,容器中的大多数bean都是 singletons 。当单例bean需要与另一个单例bean协作或非单例bean需要与另一个非单例bean协作时,通常通过将一个bean定义为另一个bean的属性来处理依赖关系。当bean生命周期不同时会出现问题。假设单例bean A需要使用非单例(原型)bean B,可能是在A上的每个方法调用上。容器只创建一次单例bean A,因此只有一次机会来设置属性。每次需要时,容器都不能为bean A提供bean B的新实例。

    解决方案是放弃一些控制反转。您可以通过实现 ApplicationContextAware 接口 make bean A aware of the container ,并通过 making a getBean("B") call to the container 在每次Bean A需要时请求(通常是新的)bean B实例。以下示例显示了此方法:

    // a class that uses a stateful Command-style class to perform some processing
    package fiona.apple;
    ​
    // Spring-API imports
    import org.springframework.beans.BeansException;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.ApplicationContextAware;
    ​
    public class CommandManager implements ApplicationContextAware {
    ​
        private ApplicationContext applicationContext;
    ​
        public Object process(Map commandState) {
            // grab a new instance of the appropriate Command
            Command command = createCommand();
            // set the state on the (hopefully brand new) Command instance
            command.setState(commandState);
            return command.execute();
        }
    ​
        protected Command createCommand() {
            // notice the Spring API dependency!
            return this.applicationContext.getBean("command", Command.class);
        }
    ​
        public void setApplicationContext(
                ApplicationContext applicationContext) throws BeansException {
            this.applicationContext = applicationContext;
        }
    }

    前面的内容是不可取的,因为业务代码知道并耦合到Spring Framework。方法注入是Spring IoC容器的一个高级功能,可以让您干净地处理这个用例。

    查找方法注入

    Lookup方法注入是容器覆盖容器管理bean上的方法并返回容器中另一个命名bean的查找结果的能力。查找通常涉及原型bean,如 the preceding section 中描述的场景。 Spring Framework通过使用CGLIB库中的字节码生成来动态生成覆盖该方法的子类来实现此方法注入。

    • 为了使这个动态子类工作,Spring bean容器子类不能 final 的类,以及要重写的方法也不能 final

    • 对具有 abstract 方法的类进行单元测试要求您自己对该类进行子类化,并提供 abstract 方法的存根实现。

    • 组件扫描也需要具体方法,这需要具体的类来获取。

    • 另一个关键限制是查找方法不适用于工厂方法,特别是配置类中没有 @Bean 方法,因为在这种情况下,容器不负责创建实例,因此无法动态创建运行时生成的子类。

    对于前面代码片段中的 CommandManager 类,Spring容器动态地覆盖 createCommand() 方法的实现。 CommandManager 类没有任何Spring依赖项,因为重写的示例显示:

    package fiona.apple;
    ​
    // no more Spring imports!
    public abstract class CommandManager {
    ​
        public Object process(Object commandState) {
            // grab a new instance of the appropriate Command interface
            Command command = createCommand();
            // set the state on the (hopefully brand new) Command instance
            command.setState(commandState);
            return command.execute();
        }
    ​
        // okay... but where is the implementation of this method?
        protected abstract Command createCommand();
    }

    在包含要注入的方法的客户端类(在本例中为 CommandManager )中,要注入的方法需要以下形式的签名:

    <public|protected> [abstract] <return-type> theMethodName(no-arguments);

    如果方法是 abstract ,则动态生成的子类实现该方法。否则,动态生成的子类将覆盖原始类中定义的具体方法。请考虑以下示例:

    <!-- a stateful bean deployed as a prototype (non-singleton) -->
    <bean id="myCommand" class="fiona.apple.AsyncCommand" scope="prototype">
        <!-- inject dependencies here as required -->
    </bean><!-- commandProcessor uses statefulCommandHelper -->
    <bean id="commandManager" class="fiona.apple.CommandManager">
        <lookup-method name="createCommand" bean="myCommand"/>
    </bean>

    标识为 commandManager 的bean在需要 myCommand bean的新实例时调用自己的 createCommand() 方法。如果实际需要的话,您必须小心将 myCommandbean部署为原型。如果是 singleton ,则每次都返回 myCommand bean的相同实例。

    或者,在基于注释的组件模型中,您可以通过 @Lookup 注释声明查找方法,如以下示例所示:

    public abstract class CommandManager {
    ​
        public Object process(Object commandState) {
            Command command = createCommand();
            command.setState(commandState);
            return command.execute();
        }
    ​
        @Lookup("myCommand")
        protected abstract Command createCommand();
    }

    或者,更具惯用性,您可以依赖于针对查找方法的声明返回类型解析目标bean:

    public abstract class CommandManager {
    ​
        public Object process(Object commandState) {
            MyCommand command = createCommand();
            command.setState(commandState);
            return command.execute();
        }
    ​
        @Lookup
        protected abstract MyCommand createCommand();
    }

    请注意,您通常应该使用具体的存根实现来声明这种带注释的查找方法,以使它们与Spring的组件扫描规则兼容,其中默认情况下抽象类被忽略。此限制不适用于显式注册或显式导入的bean类。

    任意方法更换

    与查找方法注入相比,一种不太有用的方法注入形式是能够使用另一个方法实现替换托管bean中的任意方法。您可以安全地跳过本节的其余部分,直到您确实需要此功能。

    使用基于XML的配置元数据,您可以使用 replaced-method 元素将已存在的方法实现替换为已部署的bean。考虑以下类,它有一个我们想要覆盖的名为computeValue 的方法:

    public class MyValueCalculator {
    ​
        public String computeValue(String input) {
            // some real code...
        }
    ​
        // some other methods...
    }

    实现 org.springframework.beans.factory.support.MethodReplacer 接口的类提供新的方法定义,如以下示例所示:

    /**
     * meant to be used to override the existing computeValue(String)
     * implementation in MyValueCalculator
     */
    public class ReplacementComputeValue implements MethodReplacer {
    ​
        public Object reimplement(Object o, Method m, Object[] args) throws Throwable {
            // get the input value, work with it, and return a computed result
            String input = (String) args[0];
            ...
            return ...;
        }
    }

    部署原始类并指定方法覆盖的bean定义类似于以下示例:

    <bean id="myValueCalculator" class="x.y.z.MyValueCalculator">
        <!-- arbitrary method replacement -->
        <replaced-method name="computeValue" replacer="replacementComputeValue">
            <arg-type>String</arg-type>
        </replaced-method>
    </bean><bean id="replacementComputeValue" class="a.b.c.ReplacementComputeValue"/>

    您可以在 <replaced-method/> 元素中使用一个或多个 <arg-type/> 元素来指示被覆盖的方法的方法签名。仅当方法重载且类中存在多个变体时,才需要参数的签名。为方便起见,参数的类型字符串可以是完全限定类型名称的子字符串。例如,以下全部匹配 java.lang.String

    java.lang.String
    String
    Str

    代码演示:

    look-up:

    public abstract class Car {
        
    
        //用于lookup-method注入
        @Lookup("taxi")
        public abstract Taxi createTaxi();
    
        private Taxi taxi;
    
        public Taxi getTaxi() {
            return taxi;
        }
    
        //setter注入
        public void setTaxi(Taxi taxi) {
            this.taxi = taxi;
        }
    
    }
    public class Taxi {
        public void say() {
            System.out.println("I am a Taxi...");
        }
    }

    xml文件

    <!-- ====================lookup-method属性注入==================== -->
        <bean id="car" class="spring2.Car">
            <!--注意:下面这句配置和lookup-method注入没有关系,我们只是为了出于演示和说明配置该bean -->
                <property name="taxi" ref="taxi" /> 
            <!--lookup-method注入 -->
                <lookup-method name="createTaxi" bean="taxi" />  
        </bean>
        <bean id="taxi"
            class="spring2.Taxi" scope="prototype" />

    测试类:

    public class Test1 {
        
           ClassPathXmlApplicationContext xmlBeanFactory = null;
    
            @Before
            public void initXmlBeanFactory() {
                System.out.println("
    ========测试方法开始=======
    ");
                xmlBeanFactory = new ClassPathXmlApplicationContext("spring2.xml");
            }
    
            @After
            public void after() {
                System.out.println("
    ========测试方法结束=======
    ");
            }
    
            @Test
            public void test8() {
                // 测试lookup-method注入
                Car car1 = xmlBeanFactory.getBean("car", Car.class);
                Car car2 = xmlBeanFactory.getBean("car", Car.class);
    
                System.out.println("Car:singleton,所以animal1==animal2应该为" + (car1 == car2));
    
                Taxi dog1 = car1.getTaxi();
                Taxi dog2 = car2.getTaxi();
                System.out.println("Taxi:prototype,Car:singleton,未使用lookup-method注入所以dog1==dog2应该为" + (dog1 == dog2));
    
                //注意:这里是通过createDog()方法获取
                Taxi taxi3 = car1.createTaxi();
                Taxi taxi4 = car2.createTaxi();
                System.out.println("Taxi:prototype,Car:singleton,使用了lookup-method注入所以dog3==dog4应该为" + (taxi3 == taxi4));
    
            }
    
    }

    结果:

    ========测试方法开始=======
    Car:singleton,所以animal1==animal2应该为true
    Taxi:prototype,Car:singleton,未使用lookup-method注入所以dog1==dog2应该为true
    Taxi:prototype,Car:singleton,使用了lookup-method注入所以dog3==dog4应该为false
    
    ========测试方法结束=======

    replaced-method

    public class ReplaceDog implements MethodReplacer {
        @Override
        public Object reimplement(Object obj, Method method, Object[] args) throws Throwable {
            System.out.println("Hello, I am a white dog...");
    
            Arrays.stream(args).forEach(str -> System.out.println("参数:" + str));
            return obj;
        }
    }
    public class OriginalDog {
        public void sayHello() {
            System.out.println("Hello,I am a black dog...");
        }
    
        public void sayHello(String name) {
            System.out.println("Hello,I am a black dog, my name is " + name);
        }
    }

    xml配置

    <!-- ====================replace-method属性注入==================== -->
    <bean id="dogReplaceMethod" class="com.lyc.cn.v2.day01.method.replaceMethod.ReplaceDog"/>
    <bean id="originalDogReplaceMethod" class="com.lyc.cn.v2.day01.method.replaceMethod.OriginalDog">
        <replaced-method name="sayHello" replacer="dogReplaceMethod">
            <arg-type match="java.lang.String"></arg-type>
        </replaced-method>
    </bean>

    测试类:

    @Test
    public void test9() {
        //测试replace-method注入
        OriginalDog originalDog = xmlBeanFactory.getBean("originalDogReplaceMethod", OriginalDog.class);
        originalDog.sayHello("输出结果已经被替换了。。。");
    }

    结果:

    ========测试方法开始=======
    
    十二月 09, 2019 3:59:33 下午 org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh
    信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@3caeaf62: startup date [Mon Dec 09 15:59:33 CST 2019]; root of context hierarchy
    十二月 09, 2019 3:59:33 下午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
    信息: Loading XML bean definitions from class path resource [spring2.xml]
    Hello, I am a white dog...
    参数:输出结果已经被替换了。。。
    
    ========测试方法结束=======
  • 相关阅读:
    LeetCode Path Sum II
    LeetCode Longest Palindromic Substring
    LeetCode Populating Next Right Pointers in Each Node II
    LeetCode Best Time to Buy and Sell Stock III
    LeetCode Binary Tree Maximum Path Sum
    LeetCode Find Peak Element
    LeetCode Maximum Product Subarray
    LeetCode Intersection of Two Linked Lists
    一天一个设计模式(1)——工厂模式
    PHP迭代器 Iterator
  • 原文地址:https://www.cnblogs.com/dalianpai/p/12009977.html
Copyright © 2011-2022 走看看