根据动态代理手写一个AOP
Posted Dream_it_possible!
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了根据动态代理手写一个AOP相关的知识,希望对你有一定的参考价值。
目录
SpringAop是面向切面编程的实现手段,也是动态代理的表现,我们可以借助SpringAOP在应用里来实现一个切面,可管理应用的日志、安全和事务等功能,只需要用@Aspect 注解标记一个类为切面,用@Pointcut注解定义一组service为一个切点, 然后定义方法的通知类型即可完成一个Aop的定义。
抛开SpringAOP, 使用JDK 动态代理和Cglib 动态代理如何实现对象的动态代理,对外只需要提供目标对象和切面类,实现对目标对象的环绕通知和异常处理。
功能列表
1. 统一切面的通知模式,模式包含前置通知、后置通知和异常处理,SpringAop环绕通知也就是前置通知和后置通知的结合。
2. 能定制切面,可以对切面进行二次开发,如一组功能类似的类使用同一个切面。
3. 默认采用Cglib 生成代理对象,因为JDK动态代理需要目标对象实现额外的接口。
4. 只要提供目标对象和自定义的切面类即可生成代理对象,实现的目标如下:
FixPhone fixPhone = ProxyUtil.Proxy(new FixPhone(), MyAspect.class);
一、定义切面接口
切面接口只需要包含3个方法before、after和throwException, 主要包含3个参数分别是目标类对象的实例、目标方法对象和目标方法的参数。
package com.bing.sh.aop.aspect;
import java.lang.reflect.Method;
/**
* @Desc:
* @Author: bingbing
* @Date: 2022/4/18 0018 23:12
*/
public interface Aspect
/**
* 目标方法执行前
* @param target
* @param method
* @param args
* @return
*/
boolean before(Object target, Method method, Object[] args);
/**
* 目标方法执行后
* @param target
* @param method
* @param args
* @param returnValue
* @return
*/
boolean after(Object target, Method method, Object[] args, Object returnValue);
/**
* 目标方法调用异常
* @param target
* @param method
* @param args
* @param throwable
* @return
*/
boolean throwException(Object target, Method method, Object[] args, Throwable throwable);
下面使用工厂模式去生产2种代理实现,分别是JDKProxyFactory和CglibProxyFactory。
二、定义ProxyFactory
定义抽象类ProxyFactory, 由JDKProxyFactory和CglibProxyFactory实现。
public abstract class ProxyFactory implements Serializable
private static final long serialVersionUID = 122319479213L;
/**
* 执行代理
*/
public abstract <T> T proxy(T target, Aspect aspect);
JDKProxyFactory
我们可以直接使用java.lang.reflect包下的Proxy对象调用newProxyInstance创建代理对象,参数Class<?>[] interfaces为目标对象实现的接口列表,InvocationHandler 接口需要我们自己去实现。
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
Objects.requireNonNull(h);
final Class<?> caller = System.getSecurityManager() == null
? null
: Reflection.getCallerClass();
/*
* Look up or generate the designated proxy class and its constructor.
*/
Constructor<?> cons = getProxyConstructor(caller, loader, interfaces);
return newProxyInstance(caller, cons, h);
package com.bing.sh.aop.proxy;
import com.bing.sh.aop.aspect.Aspect;
import com.bing.sh.aop.interceptor.JDKInterceptor;
import java.lang.reflect.Proxy;
/**
* @Desc:
* @Author: bingbing
* @Date: 2022/4/18 0018 23:14
*/
public class JDKProxyFactory extends ProxyFactory
@Override
public <T> T proxy(T target, Aspect aspect)
JDKInterceptor interceptor = new JDKInterceptor(target, aspect);
return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), interceptor);
CglibProxyFactory
我们在使用Cglilb生成代理对象时,需要添加cglib的dependency,借助Enhancer创建代理对象 。
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
package com.bing.sh.aop.proxy;
import com.bing.sh.aop.aspect.Aspect;
import com.bing.sh.aop.interceptor.CglibInterceptor;
import net.sf.cglib.proxy.Enhancer;
/**
* @Desc:
* @Author: bingbing
* @Date: 2022/4/18 0018 23:14
*/
public class CglibProxyFactory extends ProxyFactory
@Override
public <T> T proxy(T target, Aspect aspect)
final Enhancer enhancer = new Enhancer();
// super class 为目标对象的class。
enhancer.setSuperclass(target.getClass());
//设置callback, 就是MethodInterceptor的接口的实现类
enhancer.setCallback(new CglibInterceptor(target, aspect));
return (T) enhancer.create();
三、ServiceLoader兼顾可插拔性
为了提高应用的可插拔性,JDK提供了一个SPI给我们开发者,我们可以使用JDK里的ServerLoader类去加载META-INF/services目录下文件名定义的类实现。
这也是JDK对开发者提供的一种SPI机制,当我们对一个接口或抽象类有多个实现时,我们在选择实现类的时候,只需要从多个实现中选择一个即可,如果没有该配置,我们也可以使用一个默认的实现,配置的方式全限定名的方式,如下:
一般实现只配置一个就行,比如Spring框架里的spring-web模块,实现了servlet提供的初始化接口javax.servlet.ServletContainerInitializer,实现类为:org.springframework.web.SpringServletContainerInitializer。
我在项目里的com.bing.sh.aop.proxy.ProxyFactory 抽象类有2个实现,分别是CglibProxyFactory和JDKProxyFactory, 那么我们可以直接将CglibProxyFactory和JDKProxyFactory的全限定名配置到该文件里。
ServiceLoader类提供了一个静态方法load 去加载META-INF/services目录下的clazz和对应的实现:
Params:
service – The interface or abstract class representing the service
loader – The class loader to be used to load provider-configuration files and provider classes, or null if the system class loader (or, failing that, the bootstrap class loader) is to be used
Type parameters:
<S> – the class of the service type
Returns:
A new service loader
Throws:
ServiceConfigurationError – if the service type is not accessible to the caller or the caller is in an explicit module and its module descriptor does not declare that it uses service
API Note:
If the class path of the class loader includes remote network URLs then those URLs may be dereferenced in the process of searching for provider-configuration files.
This activity is normal, although it may cause puzzling entries to be created in web-server logs. If a web server is not configured correctly, however, then this activity may cause the provider-loading algorithm to fail spuriously.
A web server should return an HTTP 404 (Not Found) response when a requested resource does not exist. Sometimes, however, web servers are erroneously configured to return an HTTP 200 (OK) response along with a helpful html error page in such cases. This will cause a ServiceConfigurationError to be thrown when this class attempts to parse the HTML page as a provider-configuration file. The best solution to this problem is to fix the misconfigured web server to return the correct response code (HTTP 404) along with the HTML error page.
@CallerSensitive
public static <S> ServiceLoader<S> load(Class<S> service,
ClassLoader loader)
return new ServiceLoader<>(Reflection.getCallerClass(), service, loader);
我们只需要传入class,使用Iterator去遍历就能拿到service所有的实现实例。
package com.bing.sh.utils;
import com.bing.sh.aop.proxy.ProxyFactory;
import java.util.Iterator;
import java.util.ServiceConfigurationError;
import java.util.ServiceLoader;
import java.util.function.Supplier;
/**
* @Desc:
* @Author: bingbing
* @Date: 2022/4/19 0019 14:26
*/
public class ServiceLoaderUtil
public static <T> T loadService(Class<T> clazz, Supplier<? extends T> supplier)
final Iterator<T> iterator = load(clazz).iterator();
while (iterator.hasNext())
try
return iterator.next();
catch (ServiceConfigurationError e)
// ignore
// 如果没有那么默认注入一个
return supplier.get();
public static <T> ServiceLoader<T> load(Class<T> clazz)
return load(clazz, null);
public static <T> ServiceLoader<T> load(Class<T> clazz, ClassLoader classLoader)
return ServiceLoader.load(clazz, ObjectUtil.defaultIfNull(classLoader, ClassLoaderUtil::getClassLoader));
四、默认使用Cglib创建代理对象
Cglib动态代理不需要目标对象实现接口即可创建代理对象,相比于JDK动态代理稍微更简洁些。
如果没有配置services,那么使用CglibProxcFactory:
private static ProxyFactory createFactory()
return ServiceLoaderUtil.loadService(ProxyFactory.class, () ->
try
return ClassUtil.loadClass(CglibProxyFactory.class);
catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e)
e.printStackTrace();
return null;
);
五、代理核心实现
我们只需要在Interceptor类里实现代理方法的befor、after、throwException的调用即可。
package com.bing.sh.aop.interceptor;
import com.bing.sh.aop.aspect.Aspect;
import com.bing.sh.utils.ClassUtil;
import com.bing.sh.utils.ReflectUtils;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* @Desc: 代理的对象不需要实现额外的接口即可生成一个代理对象,也是默认的代理方式
* @Author: bingbing
* @Date: 2022/4/18 0018 23:32
*/
public class CglibInterceptor implements MethodInterceptor, Serializable
private static final long serialVersionUID = 1L;
private final Object target;
private final Aspect aspect;
public CglibInterceptor(Object target, Aspect aspect)
this.target = target;
this.aspect = aspect;
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable
final Object target = this.target;
Object result = null;
if (aspect.before(target, method, args))
try
ReflectUtils.setAccessible(method);
result = method.invoke(ClassUtil.isStatic(method) ? null : target, args);
catch (InvocationTargetException e)
// 捕获业务代码导致的异常
if (aspect.throwException(target, method, args, e.getTargetException()))
throw e;
if (aspect.after(target, method, args, result))
return result;
return null;
六、ProxyUtil
使用Class<? extends Aspect>表示参数的上限类型为Aspect, 可传入Aspect接口的子类。
package com.bing.sh.aop.proxy;
import com.bing.sh.aop.aspect.Aspect;
import com.bing.sh.aop.interceptor.JDKInterceptor;
import java.lang.reflect.Proxy;
/**
* @Desc:
* @Author: bingbing
* @Date: 2022/4/19 0019 19:48
*/
public class ProxyUtil
public static <T> T Proxy(T target, Class<? extends Aspect> aspect)
return ProxyFactory.createProxy(target, aspect);
public static <T> T newProxyInstance(ClassLoader classLoader, JDKInterceptor interceptor, Class<?>[] interfaces)
return (T) Proxy.newProxyInstance(classLoader, interfaces, interceptor);
七、自定义切面类
实现Aspect接口,所有方法均返回true。
package com.bing.sh.aop.aspect;
import java.lang.reflect.Method;
/**
* @Desc: 继承此类并重新该方法就能创建某个类的代理对象
* @Author: bingbing
* @Date: 2022/4/19 0019 10:59
*/
public class SimpleAspect implements Aspect
@Override
public boolean before(Object target, Method method, Object[] args)
return true;
@Override
public boolean after(Object target, Method method, Object[] args, Object returnValue)
return true;
@Override
public boolean throwException(Object target, Method method, Object[] args, Throwable throwable)
return true;
由于在interceptor里
public class MyAspect extends SimpleAspect
@Override
public boolean before(Object target, Method method, Object[] args)
System.out.println("先开机...");
return true;
@Override
public boolean after(Object target, Method method, Object[] args, Object returnValue)
System.out.println("挂掉...");
return true;
测试:
package com.bing.sh.aop;
import com.bing.sh.aop.aspect.SimpleAspect;
import com.bing.sh.aop.proxy.ProxyFactory;
import com.bing.sh.aop.proxy.ProxyUtil;
import org.junit.Before;
import org.junit.Test;
import java.lang.reflect.Method;
/**
* @Desc:
* @Author: bingbing
* @Date: 2022/4/19 0019 9:41
*/
public class AopTests
// 如果是内部类那么必须为静态内部类//java.lang.IllegalArgumentException: Superclass has no null constructors but no arguments were given
static class FixPhone
public void call()
System.out.println("fixphone打电话...");
public void sendMessage()
System.out.println("发送短信...");
static class MobilePhone implements Phone
public MobilePhone()
//java.lang.IllegalArgumentException: Superclass has no null constructors but no arguments were given
@Override
public void call()
System.out.println("mobile打电话...");
@Override
public void sendMessage()
System.out.println("mobile发送短信...");
interface Phone
void call();
void sendMessage();
public class MyAspect extends SimpleAspect
@Override
public boolean before(Object target, Method method, Object[] args)
System.out.println("先开机...");
return true;
@Override
public boolean after(Object target, Method method, Object[] args, Object returnValue)
System.out.println("挂掉...");
return true;
public class AnimalAspect extends SimpleAspect
@Override
public boolean before(Object target, Method method, Object[] args)
System.out.println("先起床...");
return true;
@Override
public boolean after(Object target, Method method, Object[] args, Object returnValue)
System.out.println("后睡觉...");
return true;
@Test
public void testAopByAutoCgLib()
FixPhone fixPhone = ProxyUtil.Proxy(new FixPhone(), MyAspect.class);
fixPhone.call();
MobilePhone mobilePhone = ProxyUtil.Proxy(new MobilePhone(), MyAspect.class);
mobilePhone.call();
Dog dog = ProxyUtil.Proxy(new Dog(), AnimalAspect.class);
dog.eat();
以上是关于根据动态代理手写一个AOP的主要内容,如果未能解决你的问题,请参考以下文章
Spring aop 基于JDK动态代理和CGLIB代理的原理以及为什么JDK代理需要基于接口