2019独角兽企业重金招聘Python工程师标准>>>

美女邀我去歌舞厅娱乐,我拒绝了,我觉得跟技术宅男们分享技术更为重要。

Spring方法注入的概念:一个由容器管理的singleton bean中,需要引入另外一个由容器管理的prototype bean,由于singleton bean只会被创建一次,如何保证singleton bean每次调用时用到的prototype bean一定是prototype的呢(存在prototype被错误注入成singleton的风险)?于是,Spring提供了Method injection(方法注入)和Lookup method injection(查找方法注入),来解决此类问题。

1.错误使用prototype的例子

public interface Command {public Object execute();}

再定义一个实现类,prototype类型。

public class AsyncCommand implements Command {@Overridepublic Object execute() {System.out.println("Async command execute.");return "Execute result.";}
}

在一个singleton bean中使用该prototype类。

public class Manager {private AsyncCommand command;// injectpublic void setCommand(AsyncCommand command) {this.command = command;}public void process() {command.execute();System.out.println(command); }
}

Spring的Xml配置文件如下:

<bean id="command" class="x.y.AsyncCommand" scope="prototype" /><bean id="manager" class="x.y.Manager" scope="singleton"><property name="command" ref="command" />
</bean>

写一个方法来测试它。

public static void main(String[] args) {FileSystemXmlApplicationContext context = new FileSystemXmlApplicationContext("D:/workspace/Spring4.2.5/bin/context.xml");Manager m = context.getBean("manager", Manager.class);for (int i = 0; i < 5; i++) {m.process();}context.close();
}

output:

x.y.AsyncCommand@5891e32e
Async command execute.
x.y.AsyncCommand@5891e32e
Async command execute.
x.y.AsyncCommand@5891e32e
Async command execute.
x.y.AsyncCommand@5891e32e
Async command execute.
x.y.AsyncCommand@5891e32e
Async command execute.

五次全是x.y.AsyncCommand@5891e32e,说明Command根本不是prototype,而变成了singleton了。这和XML中定义的scope="prototype"相违背。


为了解决上述问题,Spring提供了Method injection(方法注入)和Lookup method injection(查找方法注入)两种解决方案。

2.Method injection(方法注入)

Method injection解决方案很简单,直接上代码。

public class Manager implements ApplicationContextAware {private ApplicationContext applicationContext;public void process() {Command command = applicationContext.getBean("command", Command.class);command.execute();}@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {this.applicationContext = applicationContext;}
}

Method injection是一种解决方案,另外一种解决方案是Lookup method injection(查找方法注入),这才是我们探究的重点。

3.Lookup method injection(查找方法注入)(本文讨论的重点)

1.使用Cglib自己编程实现Lookup method injection(查找方法注入)

首先,我们通过代码来看看,我们要实现的功能。首先定义一个抽象类,提供Command对象的createCommand()方法是一个abstract抽象方法。

public abstract class CommandManager {public Object process() {Command command = createCommand();System.out.println(command);return command.execute();}public abstract Command createCommand();}

要求创建一个CommandManager实例,并准确提供prototype类型的Command对象。注意那个createCommand()方法。

我们来编写一个自定义的LookupOverrideMethodInterceptor拦截器,来完成此功能。

import java.lang.reflect.Method;import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;public class LookupOverrideMethodInterceptor implements MethodInterceptor {@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {// 检测是否是需要override的方法(写的很简单,能说明问题即可,要那么复杂干嘛呢)if ("createCommand".equals(method.getName())) {return new AsyncCommand();}return methodProxy.invokeSuper(obj, args);}
}

再编写一个测试类。

public static void main(String[] args) {Enhancer en = new Enhancer();en.setSuperclass(CommandManager.class);en.setCallback(new LookupOverrideMethodInterceptor());CommandManager cm = (CommandManager) en.create();for (int i = 0; i < 5; i++) {cm.process();}
}

output:

x.y.AsyncCommand@736e9adb
Async command execute.
x.y.AsyncCommand@6d21714c
Async command execute.
x.y.AsyncCommand@108c4c35
Async command execute.
x.y.AsyncCommand@4ccabbaa
Async command execute.
x.y.AsyncCommand@4bf558aa
Async command execute.

我们自定义的LookupOverrideMethodInterceptor拦截器,就轻松的完成了查找方法注入,这就是大名鼎鼎的Spring其Lookup method injection(查找方法注入)的底层实现原理。

以上是通过cglib编程方式,自己实现的Lookup method injection(查找方法注入)。


在Spring中使用则比较简单了,其Xml文件配置如下:

<bean id="command" class="x.y.AsyncCommand" scope="prototype" /><bean id="commandManager" class="x.y.CommandManager"><lookup-method name="createCommand" bean="command" />
</bean>

明白了原理之后,那就让我们来看看Spring的具体源码实现。

2.Spring查找方法注入(Lookup method injection)的源码解析

org.springframework.beans.factory.support.CglibSubclassingInstantiationStrategy策略类的部分源码。

    public Object instantiate(Constructor<?> ctor, Object... args) {// 创建Cglib的代理对象的ClassClass<?> subclass = createEnhancedSubclass(this.beanDefinition);Object instance;if (ctor == null) {// 通过代理对象Class反射创建Cglib代理对象instance = BeanUtils.instantiate(subclass);}else {try {Constructor<?> enhancedSubclassConstructor = subclass.getConstructor(ctor.getParameterTypes());instance = enhancedSubclassConstructor.newInstance(args);}catch (Exception ex) {throw new BeanInstantiationException(this.beanDefinition.getBeanClass(),"Failed to invoke constructor for CGLIB enhanced subclass [" + subclass.getName() + "]", ex);}}Factory factory = (Factory) instance;// 注册LookupOverrideMethodInterceptor和ReplaceOverrideMethodInterceptor(注册了2个,是不是2个都回调呢?非也,MethodOverrideCallbackFilter会正确选择其中1个来回调,二选一)factory.setCallbacks(new Callback[] {NoOp.INSTANCE,new LookupOverrideMethodInterceptor(this.beanDefinition, this.owner),new ReplaceOverrideMethodInterceptor(this.beanDefinition, this.owner)});return instance;}/*** Create an enhanced subclass of the bean class for the provided bean* definition, using CGLIB.*/private Class<?> createEnhancedSubclass(RootBeanDefinition beanDefinition) {Enhancer enhancer = new Enhancer();enhancer.setSuperclass(beanDefinition.getBeanClass());enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);if (this.owner instanceof ConfigurableBeanFactory) {ClassLoader cl = ((ConfigurableBeanFactory) this.owner).getBeanClassLoader();enhancer.setStrategy(new ClassLoaderAwareGeneratorStrategy(cl));}// 注册Callback过滤器(选择使用LookupOverrideMethodInterceptor,还是ReplaceOverrideMethodInterceptor,就是靠该过滤器控制的)enhancer.setCallbackFilter(new MethodOverrideCallbackFilter(beanDefinition));enhancer.setCallbackTypes(CALLBACK_TYPES);return enhancer.createClass();}

Spring中LookupOverrideMethodInterceptor的源码。

private static class LookupOverrideMethodInterceptor extends CglibIdentitySupport implements MethodInterceptor {// DefaultListableBeanFactory容器对象private final BeanFactory owner;public LookupOverrideMethodInterceptor(RootBeanDefinition beanDefinition, BeanFactory owner) {super(beanDefinition);this.owner = owner;}@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy mp) throws Throwable {// Cast is safe, as CallbackFilter filters are used selectively.LookupOverride lo = (LookupOverride) getBeanDefinition().getMethodOverrides().getOverride(method);Object[] argsToUse = (args.length > 0 ? args : null);  // if no-arg, don't insist on args at allif (StringUtils.hasText(lo.getBeanName())) {// 到容器中,通过xml配置的bean name去查找依赖的prototype对象return this.owner.getBean(lo.getBeanName(), argsToUse);}else {// 到容器中,通过方法的返回类型去查找依赖的prototype对象return this.owner.getBean(method.getReturnType(), argsToUse);}}}

分析到此,是不是对Lookup method injection(查找方法注入)的底层实现原理彻底清楚了呢?这些知识点其实并不重要,分析它,是避免它成为我们理解、阅读Spring源码的绊脚石或障碍。

3.Arbitrary method replacement(强行替换注入)

Arbitrary method replacement(强行替换注入)和Lookup method injection(查找方法注入)的原理是一模一样的,就是叫法和用途不同而已。

强行替换注入的含义是:我不要原来的bean提供方式了,我要新的提供方式来替换原来的提供方式。

private static class ReplaceOverrideMethodInterceptor extends CglibIdentitySupport implements MethodInterceptor {private final BeanFactory owner;public ReplaceOverrideMethodInterceptor(RootBeanDefinition beanDefinition, BeanFactory owner) {super(beanDefinition);this.owner = owner;}@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy mp) throws Throwable {ReplaceOverride ro = (ReplaceOverride) getBeanDefinition().getMethodOverrides().getOverride(method);// TODO could cache if a singleton for minor performance optimizationMethodReplacer mr = this.owner.getBean(ro.getMethodReplacerBeanName(), MethodReplacer.class);return mr.reimplement(obj, method, args);}}

LookupOverrideMethodInterceptor和ReplaceOverrideMethodInterceptor两兄弟的实现原理,真的就是一模一样,靠的就是Cglib拦截器。

总结:我们不仅分析了三种特殊注入方式的来历,还亲自使用Cglib手工实现了Lookup method injection,明白了其底层原理。最后,还分析了Spring的源码。这些知识点确实是几乎不使用的,但是,理解其原理,有助于我们快速跳过这些障碍,去学习和理解Spring其他重要且实用的功能。

但是,也不要小看这些技术思想,在Mybatis中,我们定义一个interface的接口UserMapper.java,没有任何实现类的情况下,Mybatis居然可以提供一个接口UserMapper的实例,并可以调用实例里面的方法返回真实的数据库数据给我们使用,你不觉得很奇怪吗?我想,读懂了本篇文章,自然就能对Mybatis的实现原理猜出一二,敬请期待后续博文,对Mybatis的底层原理探究吧。

注:Spring4已经完全内置了Cglib的功能,已经不再需要额外的Cglib的jar包了,本人使用的是Spring4.2.5版本。因此,请用您明亮的慧眼,去区分是cglib包中的类,还是Spring中的cglib相关类。

版权提示:文章出自开源中国社区,若对文章感兴趣,可关注我的开源中国社区博客(http://my.oschina.net/zudajun)。(经过网络爬虫或转载的文章,经常丢失流程图、时序图,格式错乱等,还是看原版的比较好)

转载于:https://my.oschina.net/zudajun/blog/664659

Spring查找方法注入(Lookup method injection)的底层实现原理相关推荐

  1. Spring方法注入 @Lookup注解使用

    情景分析 在Spring的诸多应用场景中bean都是单例形式,当一个单利bean需要和一个非单利bean组合使用或者一个非单利bean和另一个非单利bean组合使用时,我们通常都是将依赖以属性的方式放 ...

  2. Spring查找方法示例

    当一个bean依赖于另一个bean时,我们使用setter属性或通过构造函数注入bean. getter方法将向我们返回已设置的引用,但是假设您每次调用getter方法时都想要一个依赖bean的新实例 ...

  3. Spring Setter方法注入

    Setter方法输入 实体类Rumenz.java/Holder.java package com.rumenz;public class Rumenz {private Integer id;pri ...

  4. Spring的setter方法注入和构造器注入的对比

    我们知道,Spring的依赖注入,有setter方法注入,实例变量注入,构造器注入等. Spring官方文档里,提到: 依赖注入存在两种主要形式: 构造器注入 setter方法注入 注:其实对于Spr ...

  5. Spring--依赖注入 or 方法注入 ?

    依赖注入 我们在 Spring - 循环依赖 中谈到 Spring 的两种依赖注入方式 构造器注入 属性注入(setter注入也归属于此) @Service public class HelloSer ...

  6. java的lookup方法_深入理解Spring中的Lookup(方法注入)

    前言 本文主要给大家介绍了关于Spring中Lookup(方法注入)的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍: 在使用Spring时,可能会遇到这种情况:一个单例的Be ...

  7. Spring-方法注入lookup、方法替换MethodReplacer接口

    问题 lookup方法注入 概述 实例 方法一 通过在配置文件中配置的方式实现 方法二 通过实现接口代码的方式实现 小结 方法替换MethodReplacer接口 概述 实例 小结 总结 问题 无状态 ...

  8. Spring官网阅读(二)(依赖注入及方法注入)

    上篇文章我们学习了官网中的1.2,1.3两小节,主要是涉及了容器,以及Spring实例化对象的一些知识.这篇文章我们继续学习Spring官网,主要是针对1.4小节,主要涉及到Spring的依赖注入.虽 ...

  9. spring中的依赖注入——构造函数注入、set方法注入( 更常用的方式)、复杂类型的注入/集合类型的注入

    spring中的依赖注入 依赖注入: Dependency Injection IOC的作用:降低程序间的耦合(依赖关系) 依赖关系的管理:以后都交给spring来维护.在当前类需要用到其他类的对象, ...

最新文章

  1. Teddy's Knowledge Base--基于.Net 2.0 (C# 2.0, ASP.NET 2.0)的快速开发框架设计NBear V2.0.0 [开源]...
  2. 解决ubuntu未安装无线网卡驱动的问题
  3. TCP重复ACK与乱序
  4. ECMAScript 6中的Set和Map数据结构
  5. VS2008和VS2010水晶报表版本冲突的问题解决
  6. @loj - 2483@「CEOI2017」Building Bridges
  7. 使用Java语言开发微信公众平台(五)——获取access_token
  8. tensorflow 基础: static shape VS Dynamic shape, get_shape VS tf.shape() , reshape VS set_shape
  9. nodeJS之TCP模块net
  10. canbus是什么意思_canbus.是什么意思
  11. 面向AMD64的文件xxx与项目的目标平台x86不兼容
  12. win7休眠设置在哪里_win7怎么开启休眠模式
  13. 8月App Store交友软件下载量TOP10,陌陌、觅伊、soul上榜
  14. 热烈祝贺“UTONMOS第一届公会会长圆桌会议”圆满举行
  15. 《SpringBoot框架开发技术整合》笔记(一)
  16. 用户价值VS商业价值
  17. 直连的不同网段的两台主机如何通信
  18. SAP MM 事务代码VL10B对于有多个Delivery Schedule的STO item的处理
  19. 取消浏览器打开默认为百度搜索引擎
  20. 华为:证实已开发出自主操作系统

热门文章

  1. linuxrpm命令卸载python_Linux RPM包安装、卸载、升级命令讲解
  2. 对抗搜索之Alpha-Beta剪枝算法
  3. 继戴尔670亿收购EMC后,科技界钱多的让人窒息的五大收购案
  4. python教程-数据分析-matplotlib绘制折线图2 +总结(搭建网格,加上图例,修改图片的颜色和线条,脑图总结)
  5. 使用Assembly来开发c#程序
  6. se linux影响性能,性能 | SELinux+
  7. 双11蓝牙耳机选购小知识,2020哪款蓝牙耳机值得入手?
  8. win10 硬盘100% 解决
  9. python关键字定义_python使用什么关键字定义类
  10. LINUX中atd和crond的区别以及运用