AOP

AOP(Aspect Oriented Programming,面向切面编程)是OOPs(面向对象编程)的补充
可以做: 事务管理、权限、日志、安全等。
AOP让你可以使用简单可插拔的配置,在实际逻辑执行之前、之后或周围动态添加横切关注点。这让代码在当下和将来都变得易于维护

方面(Aspect):一个关注点的模块化,这个关注点实现可能另外横切多个对象

连接点(Joinpoint): 程序执行过程中明确的点,如方法的调用或特定的异常被抛出。
JoinPoint: 程序在执行流程中经过的一个个时间点,这个时间点可以是方法调用时,
或者是执行方法中异常抛出时,也可以是属性被修改时等时机,
在这些时间点上你的切面代码是可以(注意是可以但未必)被注入的

Pointcut:
JoinPoints 只是切面代码可以被织入的地方,但我并不想对所有的 JoinPoint 进行织入,
这就需要某些条件来筛选出那些需要被织入的 JoinPoint,
Pointcut 就是通过一组规则(使用 AspectJ pointcut expression language 来描述)
来定位到匹配的 joinpoint

通知(Advice):
在特定的连接点,AOP框架执行的动作。各种类型的通知包括“around”、
“before”和“throws”通知,切面代码真正被执行的地方,主要有五个织入时机
Advice: 代码织入(也叫增强),Pointcut 通过其规则指定了哪些 joinpoint 可以被织入,
而 Advice 则指定了这些 joinpoint 被织入(或者增强)的具体时机与逻辑,是切面代码真正被执行的地方,
主要有五个织入时机
1 Before Advice: 在 JoinPoints 执行前织入
2 After Advice: 在 JoinPoints 执行后织入(不管是否抛出异常都会织入)
3 After returning advice: 在 JoinPoints 执行正常退出后织入(抛出异常则不会被织入)
4 After throwing advice: 方法执行过程中抛出异常后织入
5 Around Advice: 这是所有 Advice 中最强大的,它在 JoinPoints 前后都可织入切面代码,
可以选择是否执行原有正常的逻辑,如果不执行原有流程,它甚至可以用自己的返回值代替原有的返回值,
甚至抛出异常

列子:

@Aspect
@Component
public class TestAdvice {
   // 1. 定义 PointCut
   @Pointcut("execution(* com.example.demo.api.TestServiceImpl.eatCarrot())")
   private void eatCarrot(){}

   // 2. 定义应用于 JoinPoint 中所有满足 PointCut 条件的 advice, 这里我们使用 around advice,在其中织入增强逻辑
   @Around("eatCarrot()")
   public void handlerRpcResult(ProceedingJoinPoint point) throws Throwable {
       System.out.println("吃萝卜前洗手");
       //  原来的 TestServiceImpl.eatCarrot 逻辑,可视情况决定是否执行
       point.proceed();
       System.out.println("吃萝后买单");
   }
}

说到 PointCut 的 AspectJ pointcut expression language 声明式表达式,
这个表达式支持的类型比较全面,可以用正则,注解等来指定满足条件的 joinpoint ,
比如类名后加 .*(..) 这样的正则表达式就代表这个类里面的所有方法都会被织入,
使用 @annotation 的方式也可以指定对标有这类注解的方法织入代码

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface GlobalErrorCatch {

}

//下面这个方法需要加上try catch(){} 可以通过注解加进去 对代码无侵入

public class TestServiceImpl implements TestService {
   @Override
   @GlobalErrorCatch
   public ServiceResultTO<Boolean> test() {
        // 此处写服务里的执行逻辑
        boolean result = xxx;
        return ServiceResultTO.buildSuccess(result);
   }
}
//只要配置了AOP 的pointcut
@Aspect
@Component
public class TestAdvice {

   // 1. 定义所有带有 GlobalErrorCatch 的注解的方法为 Pointcut  (这个注解是自定义的哈)
   @Pointcut("@annotation(com.example.demo.annotation.GlobalErrorCatch)")
   private void globalCatch(){}

   // 2. 将 around advice 作用于 globalCatch(){} 此 PointCut
   @Around("globalCatch()")
   public Object handlerGlobalResult(ProceedingJoinPoint point) throws Throwable {
       try {
           return point.proceed(); //业务代码执行的语句 可以在前后加上东西 如try catch(){}
       } catch (Exception e) {
           System.out.println("执行错误" + e);
           return ServiceResultTO.buildFailed("系统错误");
       }
   }

}

// AOP原理

@SpringBootApplication
@EnableAspectJ***
public class DemoApplication {
   public static void main(String[] args) {
       ConfigurableApplicationContext context = SpringApplication.run(DemoApplication.class, args);
       TestService testService = context.getBean(TestService.class);
 // 这个 bean 的 class 居然不是 TestServiceImpl!而是com.example.demo.impl.TestServiceImplEnhancerBySpringCGLIB$$705c68c7!
       System.out.println("testService = " + testService.getClass());
   }
}

静态代理动态代理区别
Java 源代码经过编译生成字节码,然后再由 JVM 经过类加载,连接,初始化成 Java 类型,
可以看到字节码是关键,静态和动态的区别就在于字节码生成的时机

静态代理:由程序员创建代理类或特定工具自动生成源代码再对其编译。在编译时已经将接口,
被代理类(委托类),代理类等确定下来,在程序运行前代理类的.class文件就已经存在了

动态代理:在程序运行后通过反射创建生成字节码再由 JVM 加载而成

JDK静态代理实现:
缺点 代码量多 多个实例需要多个代理对象

    public interface Subject {
       public void request();
    }
    public class RealSubject implements Subject {
       @Override
       public void request() {
           // 卖房
           System.out.println("卖房");
       }
    }
 public class Proxy implements Subject {

       private RealSubject realSubject;

       public Proxy(RealSubject subject) {
           this.realSubject = subject;
       }
       @Override
       public void request() {
           // 执行代理逻辑
           System.out.println("卖房前");

           // 执行目标对象方法
           realSubject.request();

           // 执行代理逻辑
           System.out.println("卖房后");
 }

   public static void main(String[] args) {
       // 被代理对象
       RealSubject subject = new RealSubject();

       // 代理
       Proxy proxy = new Proxy(subject);

       // 代理请求
       proxy.request();
   }
}

动态代理(JDK)

// 委托类
public class RealSubject implements Subject {
   @Override
   public void request() {
       // 卖房
       System.out.println("卖房");
   }
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class ProxyFactory {

   private Object target;// 维护一个目标对象

   public ProxyFactory(Object target) {
       this.target = target;
   }

   // 为目标对象生成代理对象
   public Object getProxyInstance() {
       return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),
               new InvocationHandler() {

                   //在 invoke() 方法里我们可以加入任何需要增强的逻辑 主要是根据委托类的接口等通过反射生成的
                   @Override
                   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                       System.out.println("计算开始时间");
                       // 执行目标对象方法
                       method.invoke(target, args);
                       System.out.println("计算结束时间");
                       return null;
                   }
               });
   }

   public static void main(String[] args) {
       RealSubject realSubject = new RealSubject();
       System.out.println(realSubject.getClass());
       Subject subject = (Subject) new ProxyFactory(realSubject).getProxyInstance();
       System.out.println(subject.getClass());
       subject.request();
   }
   打印结果如下:
```shell
原始类:class com.example.demo.proxy.staticproxy.RealSubject
代理类:class com.sun.proxy.$Proxy0
计算开始时间
卖房
计算结束时间

InvocationHandler:
        委托对象所有接口方法调用都会转发到 InvocationHandler.invoke(),
        在 invoke() 方法里我们可以加入任何需要增强的逻辑 主要是根据委托类的接口等通过反射生成的
}

CGLib动态代理实现
CGlib 动态代理也提供了类似的 Enhance 类,增强逻辑写在 MethodInterceptor.intercept() 中,
也就是说所有委托类的非 final 方法都会被方法拦截器拦截,在说它的原理之前首先来看看它怎么用的

//定义
public class MyMethodInterceptor implements MethodInterceptor {
   @Override
   public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
       //System.out.println("目标类增强前!!!");

       //注意这里的方法调用,不是用反射哦!!!
       Object object = proxy.invokeSuper(obj, args);

       //System.out.println("目标类增强后!!!");
       return object;
   }
}
// 使用
public class CGlibProxy {
   public static void main(String[] args) {
       //创建Enhancer对象,类似于JDK动态代理的Proxy类,下一步就是设置几个参数
       Enhancer enhancer = new Enhancer();

       //设置目标类的字节码文件
       enhancer.setSuperclass(RealSubject.class);

       //设置回调函数
       enhancer.setCallback(new MyMethodInterceptor());

       //这里的creat方法就是正式创建代理类
       RealSubject proxyDog = (RealSubject) enhancer.create();

       //调用代理类的eat方法
       proxyDog.request();
   }
}
代理类:class com.example.demo.proxy.staticproxy.RealSubject$$EnhancerByCGLIB$$889898c5
目标类增强前!!!
卖房
目标类增强后!!!

可以看到它并不要求委托类实现任何接口,而且 CGLIB 是高效的代码生成包,
底层依靠 ASM(开源的 java 字节码编辑类库)操作字节码实现的,性能比 JDK 强,
所以 Spring AOP 最终使用了 CGlib 来生成动态代理

全部评论

相关推荐

点赞 评论 收藏
分享
评论
点赞
收藏
分享
牛客网
牛客企业服务