切面编程
AOP(Aspect-Oriented Programming,面向切面编程)是一种编程技术,它允许开发者定义横切关注点(cross-cutting concerns),这些关注点会跨越多个应用模块,如日志记录、事务管理、安全检查等。
Java中的代理
在Java中,代理是一种设计模式,它提供了一种方式来控制或扩展其他对象的行为。代理模式涉及到一个接口、一个实现该接口的实体类,以及一个代理类,这个代理类同样实现该接口。代理类负责处理对实体类的请求,并在必要时添加额外的逻辑。
Java提供了两种方式来创建代理对象:静态代理和动态代理。
一、静态代理
静态代理在代码编译时就已经确定,代理类和被代理类通常都实现相同的接口。代理类中包含对被代理类的引用,并在其方法调用前后添加额外的逻辑。这种方式需要为每个被代理类手动编写一个代理类,因此不够灵活。
二、动态代理
动态代理在运行时才创建代理对象,因此更加灵活。Java提供了两种实现动态代理的机制:java.lang.reflect.Proxy类和CGLIB库。
使用java.lang.reflect.Proxy类
java.lang.reflect.Proxy类用于创建实现了指定接口的动态代理实例。要使用Proxy类创建动态代理对象,需要实现InvocationHandler接口,并在invoke方法中定义当代理对象上的方法被调用时应该执行的逻辑。
使用CGLIB库
当需要代理的类没有实现任何接口时,可以使用CGLIB库创建代理对象。CGLIB库可以动态生成被代理类的子类,并覆盖其中的方法。这样,当调用代理对象的方法时,就会触发额外的逻辑。
要使用CGLIB创建代理对象,需要添加CGLIB库到项目的依赖中,并创建一个实现了MethodInterceptor接口的类。然后,使用Enhancer类创建代理对象,并设置回调接口的实现。
简单实现一个代理
一、Java动态代理简单实现切面编程
// 方法监测注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MethodMonitor {
}
// 接口
public interface MyInterface {
@MethodMonitor
String myMethodExecute();
}
// 接口实现类
public class MyClass implements MyInterface{
@Override
public String myMethodExecute() {
return "MyClass.myMethodExecute";
}
}
// 切面类
public class MethodMonitorAspect {
public static Object monitorMethorExecution(Object target, Method method, Object[] args) throws Exception {
long startTimes = System.currentTimeMillis();
Object invokeResult = method.invoke(target, args);
long endTimes = System.currentTimeMillis();
System.out.println("MethodMonitorAspect.monitorMethorExecution:执行时间 "+(endTimes-startTimes));
return invokeResult;
}
}
// 代理工厂,生成代理类
public class MethodMonitorProxyFactory {
private static Logger logger = Logger.getLogger(MethodMonitorProxyFactory.class);
@SuppressWarnings("unchecked")
public static <T> T createProxy(T target, Class<T> interfaceType) {
return (T)Proxy.newProxyInstance(target.getClass().getClassLoader(), new Class<?>[] { interfaceType }, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(method.isAnnotationPresent(MethodMonitor.class)) {
Object monitorMethorExecution = MethodMonitorAspect.monitorMethorExecution(target, method, args);
logger.info("代理执行"+monitorMethorExecution);
return monitorMethorExecution;
}
Object nonProxyExecute = method.invoke(target, args);
logger.info("非代理执行"+nonProxyExecute);
return nonProxyExecute;
}
});
}
}
测试方法
private void testProxyAOPSupport() {
MyClass testObject = new MyClass();
MyInterface proxyObject = MethodMonitorProxyFactory.createProxy(testObject, MyInterface.class);
// 这行日志打印出来的是 代理类
logger.info("创建代理对象完成:"+proxyObject.getClass().getName());
// 这行日志中,会调用 proxyObject.toString 方法,从而会触发调用MyClass的invoke调用,返回一个MyClass对象
logger.info("创建代理对象完成:"+proxyObject);
String myMethodExecute = proxyObject.myMethodExecute();
// 这里返回的是实际执行的结果
logger.info("代理对象返回执行结果"+myMethodExecute);
}
在上面的生成代理类的方法中,使用Proxy.newProxyInstance方法创建一个代理对象时,提供了一个InvocationHandler实例作为参数。这个InvocationHandler负责处理所有对代理对象上方法的调用。当通过代理对象调用一个方法时,Java运行时系统不会直接执行这个方法,而是会调用InvocationHandler的invoke方法,并将代理对象、被调用的方法以及方法参数作为参数传递给invoke方法。在代理类的invoke方法中增加被代理类原本方法逻辑意外的处理,简单实现切面编程。这种方法获取代理的方法,要求被代理的类至少实现一个接口才行。
使用这种方法,通过使用代理和InvocationHandler,可以在不修改原始类代码的情况下,拦截和修改对类的方法的调用,从而实现诸如方法执行时间监测这样的AOP(面向切面编程)功能。
利用了Java的动态代理机制。这种实现与Spring AOP和AspectJ相比,有以下几个主要区别:
- 灵活性: Spring AOP和AspectJ提供了更强大和灵活的切面编程模型。它们支持多种类型的通知(如前置通知、后置通知、异常通知和环绕通知),并且允许你定义切点(pointcut),这使得你可以更精确地控制哪些方法应该被拦截。相比之下,手动实现的动态代理更加基础,只提供了环绕通知的功能。
- 自动代理:Spring AOP和AspectJ可以自动为你创建代理对象,你不需要手动调用Proxy.newProxyInstance。你只需要定义切面(Aspect)和切点(pointcut),Spring会负责创建和管理代理对象。
- 切面管理:Spring AOP和AspectJ提供了强大的切面管理功能,包括切面的生命周期管理、切面的优先级、切面的顺序等。这些在手动实现的动态代理中是没有的。
- 兼容性:Spring AOP和AspectJ都支持对接口和类的代理,而Java的动态代理只支持对接口的代理。如果你的目标对象没有实现任何接口,那么你将无法使用Java的动态代理。
- 性能:虽然这在大多数情况下可能不是主要问题,但手动实现的AOP可能比Spring AOP或AspectJ稍慢,因为每次调用代理方法时都会创建一个新的InvocationHandler实例。而在Spring AOP或AspectJ中,代理对象是在应用启动时创建的,因此性能开销较小。
- 维护性:使用Spring AOP或AspectJ可以使代码更加清晰和易于维护,因为它们提供了丰富的功能和清晰的语法。而手动实现的AOP可能会导致代码更加复杂和难以维护。
总的来说,虽然手动实现的AOP在某些简单场景下可能是可行的,但在更复杂的应用中,使用Spring AOP或AspectJ通常是更好的选择。
二、静态代理实现
public interface UserService {
void addUser(int gameID, long userID);
}
public class UserServiceImpl implements UserService{
private static Logger logger = Logger.getLogger(UserServiceImpl.class);
@Override
public void addUser(int gameID, long userID) {
logger.info("执行addUser方法");
}
}
public class UserServiceProxy implements UserService{
private static Logger logger = Logger.getLogger(UserServiceProxy.class);
private UserService userService;
public UserServiceProxy(UserService userService) {
this.userService = userService;
}
@Override
public void addUser(int gameID, long userID) {
logger.info("代理执行前");
userService.addUser(gameID, userID);
logger.info("代理执行后");
}
}
测试方法
private void testProxyAOPSupport() {
UserService userServiceImpl = new UserServiceImpl();
UserServiceProxy userProxy = new UserServiceProxy(userServiceImpl);
userProxy.addUser(84669, 8423304820);
}
这种方法相较动态代理更加简单,但是这种方法,代理与被代理是1对1的,灵活性极差,不推荐使用。
三、CGLIB动态代理实现切面
CGLIB(Code Generation Library)是一个强大的,高性能的代码生成库,它可以扩展JAVA类与实现JAVA接口。它广泛的被许多AOP的框架使用,例如Spring AOP和dynaop,为他们提供方法的拦截。
首先获取到cglib的jar依赖。
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
public class CgLibProxyUtils {
private static Logger logger = LoggerFactory.getLogger(CgLibProxyUtils.class);
/**
* 被代理的类,需要有公共的构造方法
* @param t 被代理的类
* @return
*/
public static <T> T createProxyObject(Class<T> t) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(t);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
logger.info("代理执行前 方法 "+method.getName() + " 参数:"+Arrays.toString(args));
Object invokeResult = null;
try {
invokeResult = proxy.invokeSuper(obj, args);
}catch (Exception e) {
logger.info("执行方法出现异常啦!!!", e);
}
logger.info("代理执行后 方法 "+method.getName());
return invokeResult;
}
});
return (T)enhancer.create();
}
/**
* 多种Callback使用Filter
* @param t
* @return
*/
public static <T> T createProxyObjectWithMoreCallbacks(Class<T> t) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(t);
enhancer.setCallbacks(new Callback[] { new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
logger.info("监控修改行为");
return proxy.invokeSuper(obj, args);
}
}, NoOp.INSTANCE, new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
logger.info("监控查询行为");
return proxy.invokeSuper(obj, args);
}
}});
// NoOp.INSTANCE 代表一个空对象,不执行任何操作,否则下面设置CallbackFilter会出现返回的索引下表异常
enhancer.setCallbackFilter(new CallbackFilter() {
@Override
public int accept(Method method) {
if (method.getName().startsWith("add") || method.getName().startsWith("del")) {
// 符合条件执行上面设置的CallBack数组对应索引的Interceptor
logger.info("执行索引位置0的Callback");
return 0;
} else if (method.getName().startsWith("get")) {
return 2;
}
return 1;
}
});
return (T)enhancer.create();
}
}
public class VipService {
private static Logger logger = Logger.getLogger(VipService.class);
public void addVipInfo(int gameId, long userId) {
logger.info("addVipInfo执行增加vipinfo业务逻辑");
}
public String getUserVipInfo(int gameId, long userId) {
return String.format("UserVipInfo: {gameId:%d,userId:%d,level:999}", gameId, userId);
}
}
@Test
public void test() {
VipService vipServiceProxyObject = CgLibProxyUtils.createProxyObject(VipService.class);
vipServiceProxyObject.addVipInfo(2333, 1516463343);
String userVipInfo = vipServiceProxyObject.getUserVipInfo(30009, 154643131);
logger.info(userVipInfo);
vipServiceProxyObject = CgLibProxyUtils.createProxyObjectWithMoreCallbacks(VipService.class);
vipServiceProxyObject.addVipInfo(2333, 1516463343);
userVipInfo = vipServiceProxyObject.getUserVipInfo(30009, 154643131);
logger.info(userVipInfo);
}
这就是使用 CGLIB 的 Enhancer 类来创建动态代理对象的基本流程。通过使用不同的 Callback 类型,你可以实现更复杂的功能,比如方法替换、方法增强等。
CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。
CglibProxy 类实现了 MethodInterceptor 接口,该接口需要实现 intercept 方法,该方法在代理对象的方法被调用时被调用。与Java的动态代理不同,CGLIB可以代理没有实现接口的类,这是它相比于Java动态代理的一个主要优势。
Enhancer 是 CGLIB 中的一个核心类,它用于动态地创建一个被代理类的子类,并且允许你重写(intercept)或增强(enhance)被代理类的方法。通过使用 Enhancer,你可以在不修改原始类代码的情况下为其添加额外的功能,这是 AOP(面向切面编程)中常用的技术。
Enhancer的一些特性
- 动态代理:Enhancer 可以动态地创建一个新的类,这个新类是原始类的子类,并且会覆盖原始类中的方法。
- 方法拦截:Enhancer 允许你设置一个 Callback,这个 Callback 会在每次调用被代理类的方法时被触发。通过 Callback,你可以控制方法的执行流程。
- 继承:由于 Enhancer 创建的是原始类的子类,因此原始类必须是可继承的(即不能是 final 类)。
- 灵活性:Enhancer 提供了丰富的配置选项,包括设置超类(superclass)、回调(callbacks)、过滤器(filters)等。
这里有一点需要注意的是,这里要求被代理的类必须存在可视的构造方法,即构造方法是public修饰的。
Spring框架中的AOP(切面编程)实现
Spring AOP通过代理模式实现,主要有两种代理方式:JDK动态代理和CGLIB代理。
-
JDK动态代理:
.当目标对象实现了至少一个接口时,Spring AOP默认使用JDK动态代理。
.JDK动态代理基于java.lang.reflect.Proxy类和InvocationHandler接口实现。
创建一个实现InvocationHandler接口的类,并在invoke方法中定义目标方法调用前后需要执行的操作(即切面逻辑)。
.使用Proxy.newProxyInstance方法创建一个代理对象,该对象实现了目标对象的所有接口,并 将上述InvocationHandler实例作为参数传递。
.当调用代理对象的方法时,会触发InvocationHandler的invoke方法,从而执行切面逻辑。 -
CGLIB代理:
.当目标对象没有实现任何接口时,Spring AOP使用CGLIB代理。
.CGLIB是一个强大的高性能的代码生成库,它可以在运行时扩展Java类与实现Java接口。
.Spring AOP使用CGLIB库创建目标对象的子类,并覆盖所有public方法。在子类的方法中,它首先执行切面逻辑,然后调用父类的相应方法。
通过这种方式,CGLIB代理允许在不修改原始类的情况下添加新的行为。
Spring AOP还允许开发者通过@Aspect注解定义切面,并通过@Before、@After、@AfterReturning、@AfterThrowing和@Around等注解指定切面的执行时机。Spring容器会扫描这些注解,并自动创建代理对象,从而实现了面向切面编程。
总之,Spring AOP的实现原理主要是通过动态代理(JDK动态代理或CGLIB代理)在目标对象的方法调用前后添加额外的逻辑,从而实现了横切关注点的模块化。
本文介绍了在不使用Spring框架的情况下,如何通过静态代理、动态代理(包括Java反射代理和CGLIB库)实现切面编程。详细讲解了Java动态代理的InvocationHandler接口和CGLIB代理的Enhancer类,以及对比了手动实现AOP与Spring AOP的优缺点。



1293

被折叠的 条评论
为什么被折叠?



