美女邀我去歌舞厅娱乐,我拒绝了,我觉得跟技术宅男们分享技术更为重要。
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 {
@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>
写一个方法来测试它。
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();
}
@Override
public 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 {
@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的具体源码实现。
2.Spring查找方法注入(Lookup method injection)的源码解析
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源码的绊脚石或障碍。
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;
}
@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)。(经过网络爬虫或转载的文章,经常丢失流程图、时序图,格式错乱等,还是看原版的比较好)
来源:oschina
链接:https://my.oschina.net/u/2727738/blog/664659