spring之lookup-method、replaced-method

在开发中大部分使用到的Bean对象都是单例的,如果有一单例对象依赖一多实例对象时。由于Spring容器在启动后就初始化好了单实例对象,所以依赖的多实例对象也会进行创建好,但是这样会造成一个问题即:单实例对象有且仅有一次机会装配这个多实例对象

lookup-method 注入

lookup-method 注入底层是依赖了CGLIB 库提供的方法可以实现动态Bean的实现,下面是其简单示例需求:

有一电子工厂可以生产电子产品例如手机、电脑等,电子经销商希望每次进货的时候都可以拿到最新生产的产品。

public abstract class AbstractFactory {
     // 获取商品的抽象方法
    protected abstract Product getProduct();
}

//------------------------------------------
@Data
public class Product {

    private String productName;

    private String productPrice;

    private String productAddress;
}

//-----------------------------------------------
public class Phone extends Product {
    public Phone() {
        System.out.println("生产的是手机");
    }
}

//----------------------------------------------
public class Computer extends Product {

    public Computer() {
        System.out.println("生产的是电脑");
    }
}

xml的配置

    <bean id="phone" class="com.codegeek.ioc.day2.lookup.Phone" scope="prototype"/>
    <bean id="computer" class="com.codegeek.ioc.day2.lookup.Computer" scope="prototype"/>

    <bean class="com.codegeek.ioc.day2.lookup.AbstractFactory">
        <!--name属性指定抽象工厂的抽象方法名。而bean的值即bean的id值-->
        <lookup-method bean="phone" name="createProduct"></lookup-method>
    </bean>

测试:

    @Test
    public void testLookup() {
        // 获取工厂
        AbstractFactory bean = (AbstractFactory) applicationContext.getBean(AbstractFactory.class);
        // 调用生产产品的方法
        bean.createProduct();
    }
//输出
/**
生产的是手机
*/

上述使用的xml配置进行实现,也可以使用注解去实现如下所示:

  • 修改手机类与电脑类如下
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class Phone extends Product {
    public Phone() {
        System.out.println("生产的是手机");
    }
}

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class Computer extends Product {

    public Computer() {
        System.out.println("生产的是电脑");
    }
}

抽象工厂类

@Component
public abstract class AbstractFactory {
    // 在指定方法上指定创建Bean的名称即可
    @Lookup("computer")
    public abstract Product createProduct();
}

注意:

以上我们并没有实现此createProduct()抽象方法但是运行结果依然可以生产手机或者电脑,这是由于Spring底层使用CGLIB代理动态生成了此抽象工厂的子类以及重写实现了其抽象方法。这里需要注意的是代理的对象不能是final修饰,其方法也不能是final修饰。否则Spring无法使用CGLIB代理动态生成子类方法创建对象。所以一般我们将被代理的类设置为抽象类,被代理类的方法设置为抽象方法,而且除此之外需要注意的是一般注入的对象的scope 设置为多实例的,否则每次生成的都是同一对象。

replace-method
replace-method是Spring 动态借助CGLIB改变bean中的方法,通过改变方法逻辑注入对象,该方法的使用需要依赖Spring提供的MethodReplacer 接口实现。

定义一个打印输出用户名的方法然后使用replace-method 改变方法的输出值

定义接口以及原生实现

public interface UserService {

    void findUserNameById(String userId);
}

public class UserServiceImpl implements UserService {
    @Override
    public void findUserNameById(String userId) {
        String desc = userId == "1" ? "主角" : "路人";
        System.out.println(desc);
    }
}

//定义MethodReplacer实现
public class UserReplaceMethod implements MethodReplacer {

    /***
     *
     *   @Override
     *     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
     *
     *         return proxy;
     *     }
     */
    @Override
    public Object reimplement(Object obj, Method method, Object[] args) throws Throwable {
        System.out.println("执行的方法:" + method.getName());
        System.out.println("参数:"+ Arrays.stream(args).findFirst().get()) ;
        System.out.println("我是MethodReplacer......替换后的方法");
        return obj;
    }
}

xml配置

    <bean id="userService" class="com.codegeek.ioc.day2.replacemethod.UserServiceImpl">
        <replaced-method name="findUserNameById" replacer="replaceMethod">
            <arg-type>java.lang.String</arg-type>
        </replaced-method>
    </bean>

    <!-- ====================replace-method属性注入==================== -->
    <bean id="replaceMethod" class="com.codegeek.ioc.day2.replacemethod.UserReplaceMethod"/>

测试:

    @Test
    public void testReplaceMethod() {
        UserService bean = applicationContext.getBean("userService",UserServiceImpl.class);
        bean.findUserNameById("1");
    }
/**输出
...替换后的方法

*/

看到MethodReplacer 的实现是不是感觉和JDK的InvocationHandler接口非常类似呢?其实可以知道Spring的实现也是使用了反射以及底层CGLIB的实现完成方法替换。

lookup-method与@Autowired依赖注入的区别
Autowired用于给一个单例对象注入另一个单例对象。 但是无法注入另外一个多实例对象,这是由于单例的bean只会初始化一次,所以这个多实例bean实际上可以看成是一个“单例bean”。除了可以使用applicationContext.getBean去获取最新的实例对象,最完美的方式是使用lookup-method 完成注入,由于采用CGLIB底层动态实现类以及重写类方法可以完美做到零耦合,开发中建议使用此种方式完成方法注入。


————————————————
版权声明:本文为CSDN博主「codegeekgao」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/javaee_gao/article/details/106265081

原文地址:https://www.cnblogs.com/wangbin2188/p/15268140.html