Spring5框架之lookup-method与replaced-method方法实现依赖注入(四)

每当出去找工作的时候关于Spring的依赖注入一般面试官都会或多或少问一下,一般同学们回答无非就是setter方法注入或者是构造器注入,此时如果你可以回答出lookup-methodreplaced-method 方法注入相关细节,或许可以让你在众多面试者中脱引而出。

需求

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

上述问题可以自定义一个接口实现Spring的ApplicationContextAware,调用其applicationContext的getBean方法即可如下所示:

@Component
public class SpringUtils implements ApplicationContextAware {

    public static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        if(SpringUtils.applicationContext == null) {
            SpringUtils.applicationContext = applicationContext;
        }
    }

    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }


    //通过name获取 Bean.
    public static Object getBean(String name){
        return getApplicationContext().getBean(name);
    }

    //通过class获取Bean.
    public static <T> T getBean(Class<T> clazz){
        return getApplicationContext().getBean(clazz);
    }

    //通过name,以及Clazz返回指定的Bean
    public static <T> T getBean(String name,Class<T> clazz){
        return getApplicationContext().getBean(name, clazz);
    }

}

但是这有一个问题就是深度依赖了Spring的接口,造成了一定程度的耦合。Spring框架为我们提供了两种特殊的方法lookup-method、replaced-method去解决这个问题。

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();
}

输出结果如下所示:
在这里插入图片描述

注意事项

在这里插入图片描述
以上我们并没有实现此抽象方法但是运行结果依然可以生产手机或者电脑,这是由于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底层动态实现类以及重写类方法可以完美做到零耦合,开发中建议使用此种方式完成方法注入。

猜你喜欢

转载自blog.csdn.net/javaee_gao/article/details/106265081