AOP是什么?
AOP(Aspect Oriented Programming),即为面向切面编程。其作为一种新的编程思想,主要是将多数代码中共用的部分抽象出来,采用动态代理、静态代理等方式,自动添加到对应代码的首部或尾部。从而简化业务代码重复逻辑,提升开发效率。
AOP常见概念
- 增强/通知(advice),在特定连接点需要执行的动作。Spring下主要包括五种通知类型:
- 前置通知(Before)
- 后置通知(After)
- 返回通知(After-returning)
- 异常通知(After-throwing)
- 环绕通知(Around)
- 切点(pointcut),指在特定连接点应该调用的时机。
- 连接点(Joint Point),指的是可以应用通知进行增强的方法。
- 切面(Aspect),切入点和通知的结合
- 织入(weaving),通过代理对目标对象方法进行增强的过程。
AOP原理
AOP实现的原理,主要基于代理模式。其又主要分为两种:1、静态代理;2、动态代理。
静态代理
静态代理来说相对比较简单,主要逻辑如下所示:
主要关注几个点:
1、被代理类与代理类需要共同实现一个代理接口的方法。
2、被代理类需要通过getter、setter方法注入到代理类中。
3、外部调用代理类的方法,从而实现增强逻辑。
具体代码实现如下:
@Data
@Slf4j
public class NormalAction implements MyFunction { // 被代理类
@Override
public void display() {
LOGGER.info("我是被代理的用户,我很苦逼!");
}
}
@Slf4j
@Data
public class StaticProxyAction implements MyFunction { // 代理类
NormalAction normalAction;//被注入的代理类,由getter、setter方法注入
@Override
public void display() {
LOGGER.info("我是代理人>>>>>>>>");
LOGGER.info("开始代理>>>>>>>>");
normalAction.display();
LOGGER.info("结束代理>>>>>>>>");
}
}
@ApiModel(value = "需实现的代理接口")
public interface MyFunction {
public void display();
}
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
//Spring首先注入被代理人到容器中
NormalAction normalAction = new NormalAction();
//然后Spring将被代理人注入到代理类中
StaticProxyAction staticProxyAction = new StaticProxyAction();
staticProxyAction.setNormalAction(normalAction);
//调用MyFunction方法的时候,即可实现增强!
staticProxyAction.display();
}
}
输出结果:
可以看到,整个过程中,被代理类的方法并没有进行修改,但是前后逻辑确实发生了变化,这就是AOP所谓的无侵入性。
动态代理
JDK动态代理
代码实践:
采用JDK的动态代理,需要自定义一个对应的Handler类,其继承了InvocationHandler类,并需要对相应的invoke方法进行重载。通过传入的方式对当前的invokeResult进行调用。
@Data
@Slf4j
public class DynamicProxyHandler implements InvocationHandler {
//被代理的对象
private Object object;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
LOGGER.info("你被增强了!快上!");
Object invokeResult = method.invoke(object, args);
LOGGER.info("增强结束!");
return invokeResult;
}
}
主函数中,通过Proxy对象新建对应的类加载器,并调用对应的方法实现。
public static void main(String[] args) {
NormalAction normalAction = new NormalAction();
DynamicProxyHandler dynamicProxyHandler = new DynamicProxyHandler();
dynamicProxyHandler.setObject(normalAction);
//获取对应的代理接口
MyFunction proxyInstance = (MyFunction) Proxy.newProxyInstance(NormalAction.class.getClassLoader(), // 类加载器
NormalAction.class.getInterfaces(), // 需要增强的接口
dynamicProxyHandler);// 实际代理类
//调用对应的接口信息
proxyInstance.display();
}
底层原理:
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class>[] interfaces,
InvocationHandler h) throws IllegalArgumentException
{
Objects.requireNonNull(h);
// 拷贝出接口数组
final Class>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
//依据类加载器及接口,查找并创建对应的代理类
//将相应的字节码文件,注入到被代理类中。同时,调用defineClass0来解析字节码,最终生成了Proxy的Class对象。
Class> cl = getProxyClass0(loader, intfs);
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
//获取到InvocationHandler类的构建器
final Constructor> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
// 根据构造器创建对应的新实例
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
CGLIB动态代理
代码实践:
@Slf4j
public class CglibProxy implements MethodInterceptor { // 继承相应的方法拦截器
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
LOGGER.info("你被增强了!");
Object object = methodProxy.invokeSuper(o, objects);
LOGGER.info("增强结束!");
return object;
}
}
public class ProxyApplication {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer(); // 创建增强器
enhancer.setSuperclass(NormalAction.class); // 设置被增强的类
enhancer.setCallback(new CglibProxy()); // 设置代理类
NormalAction proxyInstance = (NormalAction) enhancer.create(); // 创建代理实例
proxyInstance.display(); // 调用被代理方法
}
}
底层原理:
private Object createHelper() {
// 预先的校验逻辑
preValidate();
// 根据对应的superclassName 生成对应的类
Object key = KEY_FACTORY.newInstance((superclass != null) ? superclass.getName() : null,
ReflectUtils.getNames(interfaces),
filter == ALL_ZERO ? null : new WeakCacheKey(filter),
callbackTypes,
useFactory,
interceptDuringConstruction,
serialVersionUID);
this.currentKey = key;
// 根据这个key再生成相应的代理类对象
Object result = super.create(key);
return result;
}
protected Object create(Object key) { // 根据被代理类的加载器加载对应的类书籍
try {
// 获取类加载器
ClassLoader loader = this.getClassLoader();
// 获取缓存
Map cache = CACHE;
AbstractClassGenerator.ClassLoaderData data = (AbstractClassGenerator.ClassLoaderData)cache.get(loader);
//经典的双重校验锁,保证线程安全
if (data == null) {
Class var5 = AbstractClassGenerator.class;
synchronized(AbstractClassGenerator.class) {
cache = CACHE;
data = (AbstractClassGenerator.ClassLoaderData)cache.get(loader);
if (data == null) {
// 创建弱引用的哈希表,并保存到缓存中
Map newCache = new WeakHashMap(cache);
data = new AbstractClassGenerator.ClassLoaderData(loader);
newCache.put(loader, data);
CACHE = newCache;
}
}
}
//保存对应的key值
this.key = key;
Object obj = data.get(this, this.getUseCache()); //=====>深入进入
return obj instanceof Class ? this.firstInstance((Class)obj) : this.nextInstance(obj);
} catch (Error | RuntimeException var9) {
throw var9;
} catch (Exception var10) {
throw new CodeGenerationException(var10);
}
}
// 获取对应的类数据信息
public Object get(AbstractClassGenerator gen, boolean useCache) {
if (!useCache) {
// 不使用缓存的话,直接利用数据生成新的抽象类
return gen.generate(this);
} else {
// 从缓存中获取
Object cachedValue = generatedClasses.get(gen);
return gen.unwrapCachedValue(cachedValue);
}
}
AOP最佳实践
RPC日志组件
首先引入对应的包依赖:
org.aspectj
aspectjrt
1.9.4
org.aspectj
aspectjweaver
1.9.4
定义切点有两种方式:
1、采用execution表达式,指定对应的函数执行的时刻。
2、采用注解,在对应需要增强的地方进行织入。
这里我们采用方法二,首先定义一个自己的注解。需要注意的是,注解的作用只是给你的代码打下一个标记,需要对应采用反射或者特殊的处理类对标记的代码进行处理。
@Target({ElementType.TYPE,ElementType.METHOD,ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyAnnotation {
String SERVER_NAME() default "";
String action() default "";
}
紧接着,定义我们的切面类,对相应的切面方法进行增强。
@Aspect
@Component
@Slf4j
public class MyAspect {
//采用execution表达式的方式定义切点
@Pointcut("execution(* com.example.demo.controller..*.*(..)) ")
private void PointCutofAnno(){}
//或是直接 采用注解的方法定义,需要注意的是,如果需要使用注解内的值,需要保持变量名称都是“myAnnotation”
@Around(value = "@annotation(myAnnotation)")
public T test(ProceedingJoinPoint point, MyAnnotation myAnnotation) throws Throwable {
// 编写相应的增强代码
ResultDTO res;
String serverName = myAnnotation.SERVER_NAME();
String action = myAnnotation.action();
LOGGER.info("AOP执行前,{}", JSONObject.toJSONString(point.getArgs()));
try{
res = (ResultDTO) point.proceed();
}catch (Exception e){
throw new NrsBusinessException(500, String.format("请求%s%s时失败,错误信息为:%s",serverName,action,e.getMessage()),e);
}
if (res == null) {
throw new NrsBusinessException(500, String.format("请求%s%s时返回值为空", serverName,action));
}
if (res.isSuccess()) {
return res.getData();
} else {
throw new NrsBusinessException(500,String.format("请求%s%s时, %s异常,code为:%d,错误信息为:%s",serverName, action, serverName,res.getCode(), res.getMessage()));
}
}
}
最后,在我们调用下游的函数处添加上对应的注解,从而实现对相应RPC异常的处理。
@Component
public class MyRpc {
@MyAnnotation(SERVER_NAME = "下游系统",action = "调用下游系统时")
public ResultDTO testFunction(){
ResultDTO resultDTO = new ResultDTO<>();
resultDTO.success(true);
return resultDTO;
}
}
@Service("testService")
public class TestService {
@Resource
MyRpc myRpc;
public Boolean test(){
return myRpc.testFunction().getData();
}
}
@RestController
@RequestMapping(value = "")
public class TestController {
@Resource
TestService testService;
@GetMapping("/test")
public ResultDTO myTest() {
return new ResultDTO<>().success(testService.test());
}
}
PS:ResultDTO为自定义的结果类,可以根据自己的业务自定义,一般包括返回的错误码、返回的数据内容以及调用的错误信息。
参考文献
AOP实战:一个面向切面的实战项目,方法级别的简单监控
java-AOP彻底解析
JAVA动态代理
cglib动态代理机制