什么是代理
对于什么是代理你可以理解为生活中的中介,例如我现在需要租房子,但是因为我没有足够的时间去找房子,最简单的方式就是去找中介帮我去找房子。而中介就是代理,它代理我们去帮我们找房子。例如下面的示例代码:
public interface UserService {
/**
* 保存用户
* @param user
*/
void save(User user);
}
@Slf4j
public class UserServiceImpl implements UserService {
private Map map = new HashMap<>();
@Override
public void save(User user) {
map.put(user.getUserName(),user);
log.info("保存用户成功!");
}
}
public class App {
public static void main(String[] args) {
UserService userService = new UserServiceImpl();
User user = new User();
user.setUserName("mac");
user.setAge(18);
userService.save(user);
}
}
上面只是一个简单的示例,在UserService定义了一个保存用户的方法,而它的实现类UserServiceImpl实现了该方法。现在我们有一个需求,需要判断如果用户的年龄小于18岁,我们不能让他保存成功,同时我们不能去修改实现类UserServiceImpl的实现。对于这个需求我们可以用代理的方式去实现。
public class UserServiceImplProxy1 implements UserService{
private final UserService userService;
public UserServiceImplProxy1(UserService userService) {
this.userService = userService;
}
@Override
public void save(User user) {
if (user.getAge() < 18){
throw new RuntimeException("用户年龄不满18岁");
}
userService.save(user);
}
}
public class App {
public static void main(String[] args) {
UserService userService = new UserServiceImpl();
UserService userServiceProxy = new UserServiceImplProxy1(userService);
User user = new User();
user.setUserName("mac");
user.setAge(15);
userServiceProxy.save(user);
}
}
实现的方式也很简单,我们创建一个代理类UserServiceImplProxy1实现UserService接口,同时在该代理中持有一个UserService的实现,在代理类中处理校验用户年龄的逻辑,然后调用UserService实现来保存用户信息。
但是上面的方式也存在一个明显弊端,我们现有的类的接口只定义了一个方法,如果UserService定义了很多方法,我们需要将该方法全部实现一遍这无疑是一种很笨重的方式。这个时候你可能想到我通过继承UserServiceImpl然后重写save方法来实现,这样就避免了需要重写实现其他方法。但是在java中只能单继承,如果我的类已经继承了别的类那么我们并不能通过继承来解决。
动态代理
对与上面我们说的代理,我们叫它为静态代理。对于它的弊端,我们可以通过动态代理来解决。在Java中实现动态代理的方式有两种,一种是JDK自带的,它的本质原理是通过反射实现的。而另一种是通过第三方包cglib提供的,它的本质是通过字节码技术实现的。
jdk动态代理
public class JdkProxyUserServiceFactory {
private UserService userService;
private Object realProxyObject;
public Object getRealProxyObject() {
return realProxyObject;
}
public JdkProxyUserServiceFactory(UserService userService) {
this.userService = userService;
}
public UserService createProxyUserService(){
return (UserService) Proxy.newProxyInstance(userService.getClass().getClassLoader(), userService.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
realProxyObject = proxy;
String name = method.getName();
if (!"save".equals(name)){
return method.invoke(userService,args);
}
User user = (User) args[0];
if (user.getAge() < 18){
throw new RuntimeException("用户年龄不满18岁");
}
return method.invoke(userService,args);
}
});
}
}
@Slf4j
public class App {
public static void main(String[] args) {
User user = new User();
user.setUserName("mac");
user.setAge(15);
//jdk动态代理
UserService userService = new UserServiceImpl();
JdkProxyUserServiceFactory factory = new JdkProxyUserServiceFactory(userService);
UserService proxyUserService = factory.createProxyUserService();
try {
proxyUserService.save(user);
}finally {
log.info(proxyUserService.getClass().getName());
log.info("proxyUserService == realProxyObject ? {}",proxyUserService == factory.getRealProxyObject());
}
}
}
上面就是使用JDK自带的方式来实现动态代理的示例代码,通过Proxy.newProxyInstance方法来创建代理对象。该方法总共有三个核心的参数,第一个参数ClassLoader代表使用哪个类加载器来加载类,因为本质上JDK的代理是在运行时在内存中动态生成代理类,既然是类那都需要类加载器来完成加载。第二个参数interfaces则是代理类需要实现的接口,示例中就是UserService接口,最后一个参数是InvocationHandler,这个类就是jdk动态代理的核心类型,示例中我们校验用户年龄的逻辑就是在该实例中实现的。
InvocationHandler是JDK动态代理的核心,该接口只有一个核心方法就是invoke方法,该方法中一共有三个参数,其中第一个参数proxy就是指生成的代理对象。因为在invoke方法内部的this指代的是InvocationHandler实例,而如果想要获取代理对象则可以通过该参数获取。在本示例中,从proxyUserService == realProxyObject打印的结果可以看出,该参数确实就是代理对象。第二个参数method和第三个参数args[]代表了接口中的方法和方法的参数,这也正是实现动态代理的关键,我们可以通过逻辑判断来确定接口中的哪些方法需要被代理,这个判断的依据就是method和方法的参数。
如果需要查看JDK动态代理生成的类文件我们可以通过下面的示例代码生成,下面我使用ProxyGenerator.generateProxyClass生成一个UserService的代理类。
public class App2 {
public static void main(String[] args) throws IOException {
byte[] bytes = ProxyGenerator.generateProxyClass("UserService", new Class[]{UserService.class});
File file = new File("C:\\Users\\buydeem\\Desktop\\UserServiceProxy.class");
FileOutputStream out = new FileOutputStream(file);
out.write(bytes);
out.close();
}
}
代码比较简单,生成的代理类我们将其使用idea打开可以反编译出类原本的原貌。
public final class UserService extends Proxy implements com.buydeem.share.proxy.service.UserService {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
public UserService(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 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 void save(User var1) throws {
try {
super.h.invoke(this, m3, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
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);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("com.buydeem.share.proxy.service.UserService").getMethod("save", Class.forName("com.buydeem.share.proxy.model.User"));
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
通过反编译的文件我们知道下面几点,生成的代理类实现了代理接口继承了Proxy类,这也就解释了JDK为什么使用实现接口的方式来实现动态代理,而不是使用继承,因为在Java中只能单继承。
cglib动态代理
JDK的动态代理需要通过接口的方式实现,如果该类没有接口是无法通过JDK动态代理的方式来对类进行增强的。而cglib对类进行代理就没有这个限制,它的核心原理就是通过在运行的动态的生成生成被代理类的子类来实现的。正式因为是通过继承的方式来实现的,这也导致如果类或者方法被final修饰之后,无法通过cglib的方式对类或者方法进行增强。
@Slf4j
public class App {
public static void main(String[] args) {
User user = new User();
user.setUserName("mac");
user.setAge(20);
//cglib动态代理
UserService userService = new UserServiceImpl();
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(userService.getClass());
MyMethodInterceptor interceptor = new MyMethodInterceptor(userService);
enhancer.setCallback(interceptor);
UserService userServiceProxy = (UserService) enhancer.create();
userServiceProxy.save(user);
log.info("userServiceProxy == interceptor.getO() ? {}",userServiceProxy == interceptor.getRealProxyObject());
}
}
class MyMethodInterceptor implements MethodInterceptor{
private UserService userService;
private Object realProxyObject;
public MyMethodInterceptor(UserService userService) {
this.userService = userService;
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
this.realProxyObject = o;
User temp = (User) objects[0];
if (temp.getAge() < 18) {
throw new RuntimeException("用户年龄不满18岁");
}
return methodProxy.invoke(userService, objects);
}
public Object getRealProxyObject() {
return realProxyObject;
}
}
需要注意的是,使用cglib来实现动态代理需要添加cglib的包。但是在spring环境中,spring的原码里面已经将cglib和asm相关代码加入到其中,而不是通过jar包引用的方式加入,这点你可以通过spring中cglib相关源码的包名称看出。而cglib它核心实现是通过ASM来实现的,简单的说就是可以直接通过ASM来操作字节码相关内容,不过这个需要你对class文件中的字节码技术有特别深入的了解,否则还请不要轻易尝试,特别说明的一点就是java中有门探针技术,通过它可以实现完全无侵入的实现代理方法的。
上面的示例中需要关系的东西就两个,一个就是Enhancer,另一个就是MethodInterceptor。在Enhancer中我们需要设置被代理的类和方法拦截器,例如我们上面被代理的类就是UserServiceImpl,而方法拦截器的核心内容就是intercept方法。intercept方法一共有四个参数,第一个参数Object代表的是生成的代理类,第二个Method和第三个Object[]参数分别代表被代理类的方法和方法的参数。第四个参数methodProxy表示的是代理方法,如果我们对方法处理完增强逻辑,我们可以通过两种方式来调用被代理对象的方法,第一种就是使用method.invoke(被代理对象实例,方法参数),该方式就是使用jdk反射的方式来调用。另外一种则是使用methodProxy.invoke(被代理对象实例,方法参数),不过推荐使用methodProxy这种方式。
对于methodProxy中还提供一个方法invokeSuper,该方法与invoke方法很像。它们之间的区别在于invoke调用的是被代理类的方法,而invokerSuper调用的代理类的方法。正式由于该点的不同,会导致一个代理失效的问题。废话不多说直接看代码:
@Slf4j
public class App3 {
public static void main(String[] args) {
OrderServiceImpl orderService = new OrderServiceImpl();
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(OrderServiceImpl.class);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
log.info("方法前置增强");
//有效
//return methodProxy.invokeSuper(o,args);
//失效
return methodProxy.invoke(orderService,args);
}
});
OrderServiceImpl orderServiceProxy = (OrderServiceImpl) enhancer.create();
orderServiceProxy.a();
}
}
@Slf4j
public class OrderServiceImpl {
public void a(){
log.info("a()");
b();
}
public void b(){
log.info("b()");
}
}
上面的实例代码中OrderServiceImpl类中存在a()方法调用内部的b()方法,如果使用methodProxy.invoke(orderService,args)方式,最终的打印日志如下:
16:29:25.395 [main] INFO com.buydeem.share.proxy.App3 - 方法前置增强
16:29:25.413 [main] INFO com.buydeem.share.proxy.OrderServiceImpl - a()
16:29:25.413 [main] INFO com.buydeem.share.proxy.OrderServiceImpl - b()
如果使用的是methodProxy.invokeSuper(o,args)方式,最终打印的日志如下:
16:48:09.129 [main] INFO com.buydeem.share.proxy.App3 - 方法前置增强
16:48:09.144 [main] INFO com.buydeem.share.proxy.OrderServiceImpl - a()
16:48:09.144 [main] INFO com.buydeem.share.proxy.App3 - 方法前置增强
16:48:09.144 [main] INFO com.buydeem.share.proxy.OrderServiceImpl - b()
从上面两个种方式可以看出,使用第一种方式时是无法进行增强的。
动态代理失效
Spring中的AOP就是基于动态代理来实现的,而使用JDK还是CGLIB来实现取决于类中是否有接口。默认在有接口的情况下是使用JDK动态代理来实现的,如果没有接口的情况下是使用CGLIB来实现。
在Spring的工具类AopUtils中的方法中,代码示例如下:
public static Object invokeJoinpointUsingReflection(@Nullable Object target, Method method, Object[] args)
throws Throwable {
// Use reflection to invoke the method.
try {
ReflectionUtils.makeAccessible(method);
return method.invoke(target, args);
}
catch (InvocationTargetException ex) {
// Invoked method threw a checked exception.
// We must rethrow it. The client won't see the interceptor.
throw ex.getTargetException();
}
catch (IllegalArgumentException ex) {
throw new AopInvocationException("AOP configuration seems to be invalid: tried calling method [" +
method + "] on target [" + target + "]", ex);
}
catch (IllegalAccessException ex) {
throw new AopInvocationException("Could not access method [" + method + "]", ex);
}
}
从该代码可以看出,动态代理调用的是method.invoke(target, args),这个正是导致springAop动态代理时失效的原因。
如果使用的是JDK动态代理,自调用同样也会导致代理失效进而aop失效。
@Slf4j
public class App4 {
public static void main(String[] args) {
BookService bookService = new BookServiceImpl();
BookService bookServiceProxy = (BookService) Proxy.newProxyInstance(App4.class.getClassLoader(), new Class[]{BookService.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log.info("方法增强");
return method.invoke(bookService, args);
}
});
bookServiceProxy.a();
}
}
interface BookService{
void a();
void b();
}
@Slf4j
class BookServiceImpl implements BookService{
@Override
public void a() {
log.info("a()");
b();
}
@Override
public void b() {
log.info("b()");
}
}
例如上面的示例代码中,最后的运行结果只会打印一次方法增强信息,对于方法b的调用是无法走代理的。因为在方法a所持有的实例是被代理的实例而并非是代理实例。现在我们通过ThreadLocal的方式来实现自调用的增强,修改代码如下:
@Slf4j
public class App4 {
public static void main(String[] args) {
ThreadLocal threadLocal = new ThreadLocal<>();
BookService bookService = new BookServiceImpl(threadLocal);
BookService bookServiceProxy = (BookService) Proxy.newProxyInstance(App4.class.getClassLoader(), new Class[]{BookService.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
threadLocal.set((BookService) proxy);
log.info("方法增强");
return method.invoke(bookService, args);
}
});
bookServiceProxy.a();
}
}
interface BookService{
void a();
void b();
}
@Slf4j
class BookServiceImpl implements BookService{
private ThreadLocal proxy;
public BookServiceImpl(ThreadLocal proxy) {
this.proxy = proxy;
}
@Override
public void a() {
log.info("a()");
//b();
proxy.get().b();
}
@Override
public void b() {
log.info("b()");
}
}