在开发中大部分使用到的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