Controller接口调用链路浅析

项目初始化,创建代理类

1
2
3
4
5
AbstractClassGenerator#generate() 358:{strategy.generate(this)} ->
DefaultGeneratorStrategy#generate() 25:{transform(cg).generateClass(cw)} ->
Enhancer#generateClass() 670:{getMethods(...)} ->
Enhancer#generateClass() 651:{CollectionUtils.filter(methods, new VisibilityPredicate(superclass, true));} ->
AbstractClassGenerator#generate() 363:{gen = ReflectUtils.defineClass(...)}

主要是在AbstractClassGenerator这个类完成代理类的生成,其中调用了cglib相关逻辑过滤原生类的方法, DefaultGeneratorStrategyCollectionUtils需要导入cglib源码进行查看,而private方法则被VisibilityPredicate这个策略过滤了,源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// VisibilityPredicate 过滤方法 很明显private方法false
public boolean evaluate(Object arg) {
Member member = (Member)arg;
int mod = member.getModifiers();
if (Modifier.isPrivate(mod)) {
return false;
} else if (Modifier.isPublic(mod)) {
return true;
} else if (Modifier.isProtected(mod) && protectedOk) {
// protected is fine if 'protectedOk' is true (for subclasses)
return true;
} else {
// protected/package private if the member is in the same package as the source class
// and we are generating into the same classloader.
return samePackageOk
&& pkg.equals(TypeUtils.getPackageName(Type.getType(member.getDeclaringClass())));
}
}

同一个代理类生成流程会被多次触发,这个原因还未深究,不知道出于什么目的,可以标记一下这个问题点。

tomcat开启线程,走到过滤器链之前的一个定向逻辑链路调用

tomcat这部分调用链绕弯很多,我没有细看这部分的映射逻辑,和我们的问题核心也没有太大关系,有兴趣可以看看,

1
2
3
4
5
6
7
8
9
10
11
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:143)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:374)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1590)

过滤器链路

过滤器链路是在项目启动时提前扫描好注入上下文中的,servlet原生过滤链,ApplicationFilterChain,以下是主要调用链的逻辑:

1
2
3
4
5
6
ApplicationFilterChain#doFilter() 调用链路 ->
ApplicationFilterChain#internalDoFilter() 193 索引移动,调用过滤器 ->
Filter#doFilter() 相应过滤器逻辑 ->
chain.doFilter(request, response) 过滤器完成后继续调用链路 ->
重复上述动作直至遍历完链路 ApplicationFilterChain#internalDoFilter() 231 servlet.service(request, response) ->
开始servlet调用接口...

完成上述tomcat的定向逻辑调用后就会开启过滤器链路

1
2
StandardWrapperValve#invoke() 174
ApplicationFilterChain filterChain = ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);

调用controller接口

过滤器链路走完后,继续servlet请求处理,执行doService、doDispatch相关操作。然后便是调用接口方法,这里将针对方法属性进行是否代理类拦截增强操作。我们先生成代理类。

Method invoke

c源码下载:https://hg.openjdk.org/jdk8u,想看虚拟机源码点击hotspot,加载页面后点击左侧栏zip、gc下载源码压缩包,我们需要jdk和hotspot的源码。

我们知道源码都是使用反射进行动态代理的,这一步骤就是调用Method#invoke()方法,一层层往下看,最终调用的native方法:

1
2
NativeMethodAccessorImpl.class
private static native Object invoke0(Method m, Object obj, Object[] args);

jdk源码进一步调用jvm

1
2
3
4
5
6
7
jdk-7fcf35286d52\src\share\native\sun\reflect\NativeAccessors.c

JNIEXPORT jobject JNICALL Java_sun_reflect_NativeMethodAccessorImpl_invoke0
(JNIEnv *env, jclass unused, jobject m, jobject obj, jobjectArray args)
{
return JVM_InvokeMethod(env, m, obj, args);
}

关键反射调用 Reflection::invoke_method

实力有限,这块c源码确实啃不动,单纯抛砖引玉一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
hotspot-69087d08d473\src\share\vm\prims\jvm.cpp

JVM_ENTRY(jobject, JVM_InvokeMethod(JNIEnv *env, jobject method, jobject obj, jobjectArray args0))
JVMWrapper("JVM_InvokeMethod");
Handle method_handle;
if (thread->stack_available((address) &method_handle) >= JVMInvokeMethodSlack) {
method_handle = Handle(THREAD, JNIHandles::resolve(method));
Handle receiver(THREAD, JNIHandles::resolve(obj));
objArrayHandle args(THREAD, objArrayOop(JNIHandles::resolve(args0)));
oop result = Reflection::invoke_method(method_handle(), receiver, args, CHECK_NULL);
jobject res = JNIHandles::make_local(env, result);
if (JvmtiExport::should_post_vm_object_alloc()) {
oop ret_type = java_lang_reflect_Method::return_type(method_handle());
assert(ret_type != NULL, "sanity check: ret_type oop must not be NULL!");
if (java_lang_Class::is_primitive(ret_type)) {
// Only for primitive type vm allocates memory for java object.
// See box() method.
JvmtiExport::post_vm_object_alloc(JavaThread::current(), result);
}
}
return res;
} else {
THROW_0(vmSymbols::java_lang_StackOverflowError());
}
JVM_END
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
oop Reflection::invoke_method(oop method_mirror, Handle receiver, objArrayHandle args, TRAPS) {
oop mirror = java_lang_reflect_Method::clazz(method_mirror);
int slot = java_lang_reflect_Method::slot(method_mirror);
bool override = java_lang_reflect_Method::override(method_mirror) != 0;
objArrayHandle ptypes(THREAD, objArrayOop(java_lang_reflect_Method::parameter_types(method_mirror)));

oop return_type_mirror = java_lang_reflect_Method::return_type(method_mirror);
BasicType rtype;
if (java_lang_Class::is_primitive(return_type_mirror)) {
rtype = basic_type_mirror_to_basic_type(return_type_mirror, CHECK_NULL);
} else {
rtype = T_OBJECT;
}

instanceKlassHandle klass(THREAD, java_lang_Class::as_Klass(mirror));
Method* m = klass->method_with_idnum(slot);
if (m == NULL) {
THROW_MSG_0(vmSymbols::java_lang_InternalError(), "invoke");
}
methodHandle method(THREAD, m);

return invoke(klass, method, receiver, override, ptypes, rtype, args, true, THREAD);
}

生成cglib代理类

使用System.setPropert()定义输出路径,将 YOUR_CLASS 替换成你生成代理类的原生类,最后调用一下类方法,在对应路径下找到生成的代理类。

当然生成后的是class文件,需要反编译源码,可以使用jd-gui:https://github.com/java-decompiler/jd-gui/releases

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Main {
public static void main(String[] args) {
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\class");
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(YOUR_CLASS.class);
enhancer.setCallback(new MyMethodInterceptor());
YOUR_CLASS class = (YOUR_CLASS) enhancer.create();
class.function();
}

static class MyMethodInterceptor implements MethodInterceptor {

@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
return methodProxy.invokeSuper(o, objects);
}
}
}

代理类过滤链路

  • 代理类调用前都是通过Enhancer设置基类和回调,对应上文中的enhancer.setSuperclass()enhancer.setCallback(),回调类实现cglib的扩展接口,也就是实现intercept()
  • 外部调用cglib的invoke()通过两个fastclass相关的映射类去调用代理类的方法
  • 代理方法,通过判断是否还有拦截增强器,进行增强或走原生方法,代理类的基类是我们的原生方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// ***$$FastClassByCGLIB$$***
public Object invoke(int paramInt, Object paramObject, Object[] paramArrayOfObject) throws InvocationTargetException {
try {
switch (paramInt) {
case 0:
return ((OhbController)paramObject).add((Ohb)paramArrayOfObject[0]);
...
}
} catch () {...}
}
// ***$$EnhancerByCGLIB$$***$$FastClassByCGLIB$$***
public Object invoke(int paramInt, Object paramObject, Object[] paramArrayOfObject) throws InvocationTargetException {
try {
switch (paramInt) {
case 0:
return ((OhbController$$EnhancerByCGLIB$$a5ddc9c3)paramObject).add((Ohb)paramArrayOfObject[0]);
}
} catch () {...}
}
// ***$$EnhancerByCGLIB$$***
public final Object add(Ohb paramOhb) {
if (this.CGLIB$CALLBACK_0 == null) CGLIB$BIND_CALLBACKS(this);
return (this.CGLIB$CALLBACK_0 != null)
? this.CGLIB$CALLBACK_0.intercept(this, CGLIB$add$0$Method, new Object[] { paramOhb }, CGLIB$add$0$Proxy)
: super.add(paramOhb);
}

cglib

初始化生成抽象类的时候,就把private方法排除了。

现在问题点是invoke0方法执行的时候是个什么调用逻辑

1
private static native Object invoke0(Method m, Object obj, Object[] args);

debug结果是,obj是代理对象,而代理对象中不存在已被过滤的方法,不能被调用链路增强,直接反射调用了方法,此时的类相当于是反射映射的,非spring管理。

而public可以在增强链路中,可以被增强,通过增强链路调用了容器中bean

1
2
3
4
5
6
-- cglib源码
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.1</version>
</dependency>

invoke0

调用代理类方法,能调用到就调用,private方法调用不到就直接映射到父类了,这个是猜测,需要验证。

多个代理,EnhancerBySpringCGLIB、FastClassBySpringCGLIB,存在映射关系,intercept走aop链

看源码,如果没有设置Enhancer callback,则直接调用父类原方法

所以private没有的方法转发是在invoke0字节码中被发射处理了