Day4 AOP与动态代理
AOP
AOP(Aspect Oriented Programming, 面向切面编程),是Spring的两大核心之一。
AOP概述
问题引入
- 程序员小强维护着一千个方法,一天老板要求小强把这一千个方法都要加上事务代码(统一代码);
- 小强咬咬牙,添加了一个新的方法,然后让这一千个方法去调用这个事务的方法,解决了当前问题。
- 但是过了一段时间,老板又跑过来和小强说,这一千个方法又要调用日志打印的方法功能,同时又要添加用户合法性验证。
这个时候小强有两种选择:
- 对老板说:滚,然后闪酷跑路。
- 使用AOP技术来实现这个功能,以后老板随便加都可以轻松搞定。
什么是AOP?
######## 简单理解:方法增强。
AOP可以增强方法的功能,而不需要修改原业务代码。
######## 深入理解
- 从编程语言的角度:处理粒度不同
AOP是对OOP的扩展,OOP(即面向对象编程)能处理的最大粒度是对象,对OOP而言,当需要增强方法的功能时,必须修改类的定义。而AOP能处理的粒度可以深入到对象内部,可以是方法或者字段(Spring暂不支持字段增强),所以AOP可以使我们不用重新定义类,而增强原方法。 - 从设计模式的角度:AOP进一步降低了模块间的耦合度
使得业务代码和功能性代码分离,降低它们之间的耦合度。
功能性代码,如:事务处理、参数校验、日志、监控、负载均衡
- 从解放程序员生产力的角度:更少的代码
AOP只需要少量的配置或者注解就可以完成 - 从代码可读性的角度:业务逻辑更清晰
程序员可以更聚焦于业务逻辑。
AOP实现原理:动态代理
代理模式
代理模式是常用的java设计模式,它的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。我们在访问实际对象时,是通过代理对象来访问的。
何为动态代理?
普通的代理模式(属于静态代理)需要我们定义相关的代理类,而动态代理则使用一些工具类帮我们在程序运行时,自动生成代理类。
AOP与动态代理
动态代理是AOP的一种实现方式,AOP也可以使用静态织入的方式实现。
Spring使用动态代理来实现AOP的。
回顾 IOC与 DI :DI是IOC的一种实现方式,IOC也可以使用DL的方式实现。
Spring使用DI来说实现IOC。IOC和AOP是Spring两个最核心的特性。
下面用一个简单的例子,分别使用非代理模式、普通代理模式和动态代理模式来实现。
有一个UserService接口和其实现类UserServiceImpl,现需要给该接口内的每一个方法添加一个功能(打印运行时间)。
非代理
非代理模式,则需要修改原实现类,或者重新定义一个实现类,来添加新的功能要求。
原接口和实现类:
public interface UserService {
void getUser();
void createUser();
void updateUser();
void deleteUser();
}
public class UserServiceImpl implements UserService {
@Override
public void getUser() {
System.out.println("getUser");
}
@Override
public void createUser() {
System.out.println("createUser");
}
@Override
public void updateUser() {
System.out.println("updateUser");
}
@Override
public void deleteUser() {
System.out.println("deleteUser");
}
}
定义一个新的实现类,打印时间和方法名:
public class UserServiceImpl2 implements UserService {
private SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@Override
public void getUser() {
System.out.println("getUser");
System.out.println(dateFormat.format(new Date()).toString() + " Log: method getUser invoked.");
}
@Override
public void createUser() {
System.out.println("createUser");
System.out.println(dateFormat.format(new Date()).toString() + " Log: method createUser invoked.");
}
@Override
public void updateUser() {
System.out.println("updateUser");
System.out.println(dateFormat.format(new Date()).toString() + " Log: method updateUser invoked.");
}
@Override
public void deleteUser() {
System.out.println("deleteUser");
System.out.println(dateFormat.format(new Date()).toString() + " Log: method deleteUser invoked.");
}
}
测试:
public static void main(String[] args) {
UserService userDao = new UserServiceImpl2();
userDao.getUser();
userDao.createUser();
userDao.updateUser();
userDao.deleteUser();
}
结果:
getUser
2019-12-22 10:29:26: method getUser invoked
createUser
2019-12-22 10:29:26: method createUser invoked
updateUser
2019-12-22 10:29:26: method updateUser invoked
deleteUser
2019-12-22 10:29:26: method deleteUser invoked
代理模式
代理模式,也需要定义一个实现代理接口的类,但不需要重写原方法,而是组合一个被代理对象。
代理类:
public class UserServiceProxy implements UserService {
private SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
private UserService userService;
public UserServiceProxy(UserService userService) {
this.userService = userService;
}
@Override
public void getUser() {
userService.getUser();
System.out.println(dateFormat.format(new Date()).toString() + " Log: method getUser invoked.");
}
@Override
public void createUser() {
userService.createUser();
System.out.println(dateFormat.format(new Date()).toString() + " Log: method createUser invoked.");
}
@Override
public void updateUser() {
userService.updateUser();
System.out.println(dateFormat.format(new Date()).toString() + " Log: method updateUser invoked.");
}
@Override
public void deleteUser() {
userService.deleteUser();
System.out.println(dateFormat.format(new Date()).toString() + " Log: method deleteUser invoked.");
}
}
测试:
public static void main(String[] args) {
UserService userServiceOriginal = new UserServiceImpl();
UserService userServiceProxy = new UserServiceProxy(userServiceOriginal);
userServiceProxy.getUser();
userServiceProxy.createUser();
userServiceProxy.updateUser();
userServiceProxy.deleteUser();
}
结果:
getUser
2019-12-22 11:34:49 Log: method getUser invoked.
createUser
2019-12-22 11:34:49 Log: method createUser invoked.
updateUser
2019-12-22 11:34:49 Log: method updateUser invoked.
deleteUser
2019-12-22 11:34:49 Log: method deleteUser invoked.
JDK动态代理
JDK动态代理不需要重写实现类,也不需要定义一个实现UserService接口的代理类。而是使用Proxy类帮我们动态生成代理类。
public class UserServiceProxy {
private static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
/**
* 返回动态代理对象
* @param userService 被代理对象
* @return 代理对象
*/
public static UserService newProxyInstance(UserService userService) {
/*
* Proxy.newProxyInstance方法返回动态生成的代理类
* 参数:
* userService.getClass().getClassLoader():被代理类的类加载器;
* userService.getClass().getInterfaces():被代理类的接口,重要!说明JDK动态代理是基于代理接口的。
* 第三个参数定义了一个内部类,实现了代理逻辑,内部参数:
* o:代理类对象;
* method:被代理方法;
* objects:被代理方法的参数;
*/
return (UserService) Proxy.newProxyInstance(userService.getClass().getClassLoader(),
userService.getClass().getInterfaces(), (o, method, objects) -> {
// 调用被代理方法
Object ret = method.invoke(userService, objects);
// 编写增强功能
System.out.println(dateFormat.format(new Date()) + ": method " + method.getName() + " invoked");
return ret;
});
}
}
测试:
public static void main(String[] args) {
// 保存动态生成的代理类的字节码
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
UserService userService = UserServiceProxy.newProxyInstance(new UserServiceImpl());
userService.getUser();
userService.createUser();
userService.updateUser();
userService.deleteUser();
}
结果:
getUser
2019-12-22 11:48:44: method getUser invoked
createUser
2019-12-22 11:48:44: method createUser invoked
updateUser
2019-12-22 11:48:44: method updateUser invoked
deleteUser
2019-12-22 11:48:44: method deleteUser invoked
JDK 动态代理解析
JDK 动态代理类是在运行时生成的,默认不会保存到本地文件,除非设置了如下属性:
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
动态生成的代理类路径为:"工程目录/aop/dynamic_proxy/cglibproxy/$Proxy0.class",$Proxy0就是代理类的类名。
JDK动态代理使用了反射,以getUser()方法为例,我们可以查看保存的代理类中,该方法定义为:
public final void getUser() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
关键就一句:super.h.invoke(this, m3, (Object[])null);
- invoke就是我们定义的:
(o, method, objects) -> {
// 调用被代理方法
Object ret = method.invoke(userService, objects);
// 编写增强功能
System.out.println(dateFormat.format(new Date()) + ": method " + method.getName() + " invoked");
return ret;
}
- m3在动态生成的代理类中定义为:
m3 = Class.forName("com.bailiban.day4.aop.dynamic_proxy.noproxy.UserService").getMethod("getUser");
Cglib动态代理
public class UserServiceCglibProxy {
private static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
/**
* 返回动态代理对象
* @param userService 被代理对象
* @return 代理对象
*/
public static UserService newProxyInstance(UserService userService) {
/*
* Enhancer.create方法返回动态生成的代理类
* 参数:
* userService.getClass():被代理类的类;
* 第二个参数定义了一个内部类,实现了代理逻辑,内部参数:
* o:代理类对象;
* method:被代理方法;
* objects:被代理方法的参数;
* methodProxy:方法代理;
*/
return (UserService) Enhancer.create(userService.getClass(), (MethodInterceptor) (o, method, objects, methodProxy) -> {
// 调用被代理方法
Object ret = methodProxy.invokeSuper(o, objects);
// 编写增强功能
System.out.println(dateFormat.format(new Date()) + ": method " + method.getName() + " invoked");
return ret;
});
}
}
测试:
public static void main(String[] args) {
// 保存动态生成的代理类的字节码
// System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "C:\\code");
UserService userService = UserServiceCglibProxy.newProxyInstance(new UserServiceImpl());
userService.getUser();
userService.createUser();
userService.updateUser();
userService.deleteUser();
}
结果:
getUser
2019-12-22 12:10:01: method getUser invoked
createUser
2019-12-22 12:10:01: method createUser invoked
updateUser
2019-12-22 12:10:01: method updateUser invoked
deleteUser
2019-12-22 12:10:01: method deleteUser invoked
Cglib动态代理解析
Cglib生成的代理类也需要设置才能保存的本地:
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "C:\\code");
Cglib动态生成的代码更多,更复杂。
以getUser()方法为例,我们可以查看保存的代理类中,该方法定义为:
public final void getUser() {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (var10000 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
if (var10000 != null) {
var10000.intercept(this, CGLIB$getUser$0$Method, CGLIB$emptyArgs, CGLIB$getUser$0$Proxy);
} else {
super.getUser();
}
}
关键代码:var10000.intercept(this, CGLIB$getUser$0$Method, CGLIB$emptyArgs, CGLIB$getUser$0$Proxy);
其中:
- intercept就是我们定义的:
(o, method, objects, methodProxy) -> {
// 调用被代理方法
Object ret = methodProxy.invokeSuper(o, objects);
// 编写增强功能
System.out.println(dateFormat.format(new Date()) + ": method " + method.getName() + " invoked");
return ret;
}
- this:代理对象;
CGLIB$getUser$0$Method
代理方法,可以理解为就是UserServiceImpl中的getUser方法;CGLIB$emptyArgs
:方法参数;CGLIB$getUser$0$Proxy
一个MethodProxy类型的对象,可以避免使用方法反射。有没有使用方法反射是Cglib和JDK动态代理的区别之一。
二者区别
######## 简单理解
- 使用场景
- JDK动态代理用于接口
例如,我们要代理UserServiceImpl,则UserServiceImpl必须实现一个接口,例如UserService接口; - Cglib用于类或者接口
例如,我们要代理UserServiceImpl,不需要关注该类是否实现接口。
- 效率
- Java8之前,Cglib效率高;
- Java8开始,JDK动态代理效率高;
######## 深入理解
- 为什么JDK动态代理必须要有接口,而Cglib则不需要
这是由他们的实现方式决定的。
- JDK动态代理生成类继承关系
JDK动态代理生成的代码:
public final class $Proxy0 extends Proxy implements UserService {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m6;
private static Method m0;
private static Method m4;
private static Method m5;
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 void getUser() throws {
try {
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 void createUser() throws {
try {
super.h.invoke(this, m6, (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 updateUser() throws {
try {
super.h.invoke(this, m4, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final void deleteUser() throws {
try {
super.h.invoke(this, m5, (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"));
m3 = Class.forName("com.bailiban.day4.aop.dynamic_proxy.noproxy.UserService").getMethod("getUser");
m2 = Class.forName("java.lang.Object").getMethod("toString");
m6 = Class.forName("com.bailiban.day4.aop.dynamic_proxy.noproxy.UserService").getMethod("createUser");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
m4 = Class.forName("com.bailiban.day4.aop.dynamic_proxy.noproxy.UserService").getMethod("updateUser");
m5 = Class.forName("com.bailiban.day4.aop.dynamic_proxy.noproxy.UserService").getMethod("deleteUser");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
从代理类继承关系看:public final class $Proxy0 extends Proxy implements UserService
可以看到,$Proxy0继承了Proxy类,而Java只有单继承,为了使使用被代理类的地方,都可以透明地使用代理类,只能使得它们拥有共同的接口。所以JDK动态代理只支持接口。
- Cglib代理类继承关系
public class UserDaoImpl$$EnhancerByCGLIB$$4a28906a extends UserDaoImpl implements Factory
可以看到代理类并没有继承其他类,而是继承了被代理的类。所以Cglib的方式不关注被代理类是否有接口。
- 效率问题
- 实现方式不同带来的效率不同
- JDK代理使用了反射,而在之前反射是很“重”的操作;
- Cglib使用了映射,通过方法名查找到对应方法,然后调用,没有使用到反射;
- 官方与非官方带来的优化差异
- 虽然JDK代理使用了反射这个“重操作”,但它会随着官方对Java语言,对JVM,对JIT即时编译器的不断优化,而带来效率的巨大改进;
- Cglib是非官方的实现,优化力度不够;