美女邀我去歌舞厅娱乐,我拒绝了,我以为跟技术宅男们分享技术更为重要。
java
Spring方法注入的概念:一个由容器管理的singleton bean中,须要引入另一个由容器管理的prototype bean,因为singleton bean只会被建立一次,如何保证singleton bean每次调用时用到的prototype bean必定是prototype的呢(存在prototype被错误注入成singleton的风险)?因而,Spring提供了Method injection(方法注入)和Lookup method injection(查找方法注入),来解决此类问题。spring
public interface Command { public Object execute(); }
再定义一个实现类,prototype类型。数据库
public class AsyncCommand implements Command { @Override public Object execute() { System.out.println("Async command execute."); return "Execute result."; } }
在一个singleton bean中使用该prototype类。
编程
public class Manager { private AsyncCommand command; // inject public 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>
写一个方法来测试它。
app
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:ide
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(查找方法注入)两种解决方案。
测试
Method injection解决方案很简单,直接上代码。
this
public class Manager implements ApplicationContextAware { private ApplicationContext applicationContext; public void process() { Command command = applicationContext.getBean("command", Command.class); command.execute(); } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } }
Method injection是一种解决方案,另一种解决方案是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 { @Override public 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的具体源码实现。
org.springframework.beans.factory.support.CglibSubclassingInstantiationStrategy策略类的部分源码。
public Object instantiate(Constructor<?> ctor, Object... args) { // 建立Cglib的代理对象的Class Class<?> 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; } @Override public 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 all if (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源码的绊脚石或障碍。
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; } @Override public 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 optimization MethodReplacer 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)。(通过网络爬虫或转载的文章,常常丢失流程图、时序图,格式错乱等,仍是看原版的比较好)