上文我們學習了SpringAOP Cglib動態代理的實現,本文主要是SpringAOP JDK動態代理的案例和實現部分。@pdai
上文我們學習了SpringAOP Cglib動態代理的實現,本文主要是SpringAOP JDK動態代理的案例和實現部分。
JDK動態代理是有JDK提供的工具類Proxy實現的,動態代理類是在執行時生成指定介面的代理類,每個代理範例(實現需要代理的介面)都有一個關聯的呼叫處理程式物件,此物件實現了InvocationHandler,最終的業務邏輯是在InvocationHandler實現類的invoke方法上。
這裡我們寫一個使用jdk代理的簡單例子。@pdai
jdk代理不需要任何依賴。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>tech-pdai-spring-demos</artifactId>
<groupId>tech.pdai</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>006-spring-framework-demo-aop-proxy-jdk</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<!--based on jdk proxy -->
<dependencies>
</dependencies>
</project>
User
package tech.pdai.springframework.entity;
/**
* @author pdai
*/
public class User {
/**
* user's name.
*/
private String name;
/**
* user's age.
*/
private int age;
/**
* init.
*
* @param name name
* @param age age
*/
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
介面如下
package tech.pdai.springframework.service;
import tech.pdai.springframework.entity.User;
import java.util.List;
/**
* @author pdai
*/
public interface IUserService {
/**
* find user list.
*
* @return user list
*/
List<User> findUserList();
/**
* add user
*/
void addUser();
}
實現類如下:
package tech.pdai.springframework.service;
import tech.pdai.springframework.entity.User;
import java.util.Collections;
import java.util.List;
/**
* @author pdai
*/
public class UserServiceImpl implements IUserService {
/**
* find user list.
*
* @return user list
*/
@Override
public List<User> findUserList() {
return Collections.singletonList(new User("pdai", 18));
}
/**
* add user
*/
@Override
public void addUser() {
// do something
}
}
代理類如下:
package tech.pdai.springframework.proxy;
import tech.pdai.springframework.service.IUserService;
import tech.pdai.springframework.service.UserServiceImpl;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
/**
* This class is for proxy demo.
*
* @author pdai
*/
public class UserLogProxy {
/**
* proxy target
*/
private IUserService target;
/**
* init.
*
* @param target target
*/
public UserLogProxy(UserServiceImpl target) {
super();
this.target = target;
}
/**
* get proxy.
*
* @return proxy target
*/
public IUserService getLoggingProxy() {
IUserService proxy;
ClassLoader loader = target.getClass().getClassLoader();
Class[] interfaces = new Class[]{IUserService.class};
InvocationHandler h = new InvocationHandler() {
/**
* proxy: 代理物件。 一般不使用該物件 method: 正在被呼叫的方法 args: 呼叫方法傳入的引數
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
// log - before method
System.out.println("[before] execute method: " + methodName);
// call method
Object result = null;
try {
// 前置通知
result = method.invoke(target, args);
// 返回通知, 可以存取到方法的返回值
} catch (NullPointerException e) {
e.printStackTrace();
// 異常通知, 可以存取到方法出現的異常
}
// 後置通知. 因為方法可以能會出異常, 所以存取不到方法的返回值
// log - after method
System.out.println("[after] execute method: " + methodName + ", return value: " + result);
return result;
}
};
/**
* loader: 代理物件使用的類載入器.
* interfaces: 指定代理物件的型別. 即代理代理物件中可以有哪些方法.
* h: 當具體呼叫代理物件的方法時, 應該如何進行響應, 實際上就是呼叫 InvocationHandler 的 invoke 方法
*/
proxy = (IUserService) Proxy.newProxyInstance(loader, interfaces, h);
return proxy;
}
}
啟動類中指定代理目標並執行。
package tech.pdai.springframework;
import tech.pdai.springframework.proxy.UserLogProxy;
import tech.pdai.springframework.service.IUserService;
import tech.pdai.springframework.service.UserServiceImpl;
/**
* Jdk proxy demo.
*
* @author pdai
*/
public class ProxyDemo {
/**
* main interface.
*
* @param args args
*/
public static void main(String[] args) {
// proxy
IUserService userService = new UserLogProxy(new UserServiceImpl()).getLoggingProxy();
// call methods
userService.findUserList();
userService.addUser();
}
}
我們啟動上述類main 函數,執行的結果如下:
[before] execute method: findUserList
[after] execute method: findUserList, return value: [User{name='pdai', age=18}]
[before] execute method: addUser
[after] execute method: addUser, return value: null
JDK代理自動生成的class是由sun.misc.ProxyGenerator來生成的。
我們看下sun.misc.ProxyGenerator生成程式碼的邏輯:
/**
* Generate a proxy class given a name and a list of proxy interfaces.
*
* @param name the class name of the proxy class
* @param interfaces proxy interfaces
* @param accessFlags access flags of the proxy class
*/
public static byte[] generateProxyClass(final String name,
Class<?>[] interfaces,
int accessFlags)
{
ProxyGenerator gen = new ProxyGenerator(name, interfaces, accessFlags);
final byte[] classFile = gen.generateClassFile();
...
}
generateClassFile方法如下:
/**
* Generate a class file for the proxy class. This method drives the
* class file generation process.
*/
private byte[] generateClassFile() {
/* 第一步:將所有方法包裝成ProxyMethod物件 */
// 將Object類中hashCode、equals、toString方法包裝成ProxyMethod物件
addProxyMethod(hashCodeMethod, Object.class);
addProxyMethod(equalsMethod, Object.class);
addProxyMethod(toStringMethod, Object.class);
// 將代理類介面方法包裝成ProxyMethod物件
for (Class<?> intf : interfaces) {
for (Method m : intf.getMethods()) {
addProxyMethod(m, intf);
}
}
// 校驗返回型別
for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
checkReturnTypes(sigmethods);
}
/* 第二步:為代理類組裝欄位,建構函式,方法,static初始化塊等 */
try {
// 新增建構函式,引數是InvocationHandler
methods.add(generateConstructor());
// 代理方法
for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
for (ProxyMethod pm : sigmethods) {
// 欄位
fields.add(new FieldInfo(pm.methodFieldName,
"Ljava/lang/reflect/Method;",
ACC_PRIVATE | ACC_STATIC));
// 上述ProxyMethod中的方法
methods.add(pm.generateMethod());
}
}
// static初始化塊
methods.add(generateStaticInitializer());
} catch (IOException e) {
throw new InternalError("unexpected I/O Exception", e);
}
if (methods.size() > 65535) {
throw new IllegalArgumentException("method limit exceeded");
}
if (fields.size() > 65535) {
throw new IllegalArgumentException("field limit exceeded");
}
/* 第三步:寫入class檔案 */
/*
* Make sure that constant pool indexes are reserved for the
* following items before starting to write the final class file.
*/
cp.getClass(dotToSlash(className));
cp.getClass(superclassName);
for (Class<?> intf: interfaces) {
cp.getClass(dotToSlash(intf.getName()));
}
/*
* Disallow new constant pool additions beyond this point, since
* we are about to write the final constant pool table.
*/
cp.setReadOnly();
ByteArrayOutputStream bout = new ByteArrayOutputStream();
DataOutputStream dout = new DataOutputStream(bout);
try {
/*
* Write all the items of the "ClassFile" structure.
* See JVMS section 4.1.
*/
// u4 magic;
dout.writeInt(0xCAFEBABE);
// u2 minor_version;
dout.writeShort(CLASSFILE_MINOR_VERSION);
// u2 major_version;
dout.writeShort(CLASSFILE_MAJOR_VERSION);
cp.write(dout); // (write constant pool)
// u2 access_flags;
dout.writeShort(accessFlags);
// u2 this_class;
dout.writeShort(cp.getClass(dotToSlash(className)));
// u2 super_class;
dout.writeShort(cp.getClass(superclassName));
// u2 interfaces_count;
dout.writeShort(interfaces.length);
// u2 interfaces[interfaces_count];
for (Class<?> intf : interfaces) {
dout.writeShort(cp.getClass(
dotToSlash(intf.getName())));
}
// u2 fields_count;
dout.writeShort(fields.size());
// field_info fields[fields_count];
for (FieldInfo f : fields) {
f.write(dout);
}
// u2 methods_count;
dout.writeShort(methods.size());
// method_info methods[methods_count];
for (MethodInfo m : methods) {
m.write(dout);
}
// u2 attributes_count;
dout.writeShort(0); // (no ClassFile attributes for proxy classes)
} catch (IOException e) {
throw new InternalError("unexpected I/O Exception", e);
}
return bout.toByteArray();
}
一共三個步驟(把大象裝進冰箱分幾步?):
從上述sun.misc.ProxyGenerator類中可以看到,這個類裡面有一個設定引數sun.misc.ProxyGenerator.saveGeneratedFiles
,可以通過這個引數將生成的Proxy類儲存在本地,比如設定為true 執行後,生成的檔案如下:
我們看下生成後的程式碼:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package com.sun.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import java.util.List;
import tech.pdai.springframework.service.IUserService;
// 所有類和方法都是final型別的
public final class $Proxy0 extends Proxy implements IUserService {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
private static Method m4;
// 建構函式注入 InvocationHandler
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final List findUserList() throws {
try {
return (List)super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final void addUser() throws {
try {
super.h.invoke(this, m4, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
// 初始化 methods, 2個IUserService介面中的方法,3個Object中的介面
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m3 = Class.forName("tech.pdai.springframework.service.IUserService").getMethod("findUserList");
m2 = Class.forName("java.lang.Object").getMethod("toString");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
m4 = Class.forName("tech.pdai.springframework.service.IUserService").getMethod("addUser");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
上述程式碼是比較容易理解的,我就不畫圖了。
主要流程是:
SpringAOP扮演的是JDK代理的建立和呼叫兩個角色,我們通過這兩個方向來看下SpringAOP的程式碼(JdkDynamicAopProxy類)
代理的建立比較簡單,呼叫getProxy方法,然後直接呼叫JDK中Proxy.newProxyInstance()方法將classloader和被代理的介面方法傳入即可。
@Override
public Object getProxy() {
return getProxy(ClassUtils.getDefaultClassLoader());
}
@Override
public Object getProxy(@Nullable ClassLoader classLoader) {
if (logger.isTraceEnabled()) {
logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource());
}
return Proxy.newProxyInstance(classLoader, this.proxiedInterfaces, this);
}
執行的方法如下:
/**
* Implementation of {@code InvocationHandler.invoke}.
* <p>Callers will see exactly the exception thrown by the target,
* unless a hook method throws an exception.
*/
@Override
@Nullable
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object oldProxy = null;
boolean setProxyContext = false;
TargetSource targetSource = this.advised.targetSource;
Object target = null;
try {
// 執行的是equal方法
if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
// The target does not implement the equals(Object) method itself.
return equals(args[0]);
}
// 執行的是hashcode方法
else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
// The target does not implement the hashCode() method itself.
return hashCode();
}
// 如果是包裝類,則dispatch to proxy config
else if (method.getDeclaringClass() == DecoratingProxy.class) {
// There is only getDecoratedClass() declared -> dispatch to proxy config.
return AopProxyUtils.ultimateTargetClass(this.advised);
}
// 用反射方式來執行切點
else if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&
method.getDeclaringClass().isAssignableFrom(Advised.class)) {
// Service invocations on ProxyConfig with the proxy config...
return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
}
Object retVal;
if (this.advised.exposeProxy) {
// Make invocation available if necessary.
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}
// Get as late as possible to minimize the time we "own" the target,
// in case it comes from a pool.
target = targetSource.getTarget();
Class<?> targetClass = (target != null ? target.getClass() : null);
// 獲取攔截鏈
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
// Check whether we have any advice. If we don't, we can fallback on direct
// reflective invocation of the target, and avoid creating a MethodInvocation.
if (chain.isEmpty()) {
// We can skip creating a MethodInvocation: just invoke the target directly
// Note that the final invoker must be an InvokerInterceptor so we know it does
// nothing but a reflective operation on the target, and no hot swapping or fancy proxying.
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
}
else {
// We need to create a method invocation...
MethodInvocation invocation =
new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
// Proceed to the joinpoint through the interceptor chain.
retVal = invocation.proceed();
}
// Massage return value if necessary.
Class<?> returnType = method.getReturnType();
if (retVal != null && retVal == target &&
returnType != Object.class && returnType.isInstance(proxy) &&
!RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
// Special case: it returned "this" and the return type of the method
// is type-compatible. Note that we can't help if the target sets
// a reference to itself in another returned object.
retVal = proxy;
}
else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
throw new AopInvocationException(
"Null return value from advice does not match primitive return type for: " + method);
}
return retVal;
}
finally {
if (target != null && !targetSource.isStatic()) {
// Must have come from TargetSource.
targetSource.releaseTarget(target);
}
if (setProxyContext) {
// Restore old proxy.
AopContext.setCurrentProxy(oldProxy);
}
}
}
https://github.com/realpdai/tech-pdai-spring-demos
首先, 從Spring框架的整體架構和組成對整體框架有個認知。
其次,通過案例引出Spring的核心(IoC和AOP),同時對IoC和AOP進行案例使用分析。
基於Spring框架和IOC,AOP的基礎,為構建上層web應用,需要進一步學習SpringMVC。
Spring進階 - IoC,AOP以及SpringMVC的原始碼分析
ConcurrentHashMap<String, Object>
;並且BeanDefinition介面中包含了這個類的Class資訊以及是否是單例等。那麼如何從BeanDefinition中範例化Bean物件呢,這是本文主要研究的內容?