关于aop:Spring-AOP

2次阅读

共计 7894 个字符,预计需要花费 20 分钟才能阅读完成。

1. 概述

从实现的角度来说,代理分为基于类的代理和基于接口的代理,基于接口的代理有 动态代理 动静代理 ,而基于类的代理须要依赖第三方库,比方 cglibcglib 的代理在运行时动静生成字节码文件来实现代理。

2. 动态代理

在编译期间就曾经实现代理

2.1 实现动态代理的必要条件

  1. 基于接口或者抽象类
  2. 代理类实现接口或者继承抽象类,通过构造函数传入指标对象
  3. 重写办法,通过指标对象去执行业务逻辑
  4. 而代理对象去执行与业务不想关的逻辑

2.2 示例代码

public interface UserService {
    /**
     * 增加
     * @param email 邮箱
     * @return 受影响的行数
     */
    int add(String email);
}
public class UserServiceImpl implements UserService {
    @Override
    public int add(String email) {System.out.println(String.format("email:%s", email));
        return 1;
    }
}
public class UserServiceProxyImpl implements UserService {
    private final UserService targetObject;

    public UserServiceProxyImpl(UserService targetObject) {this.targetObject = targetObject;}

    @Override
    public int add(String email) {System.out.println("begin transaction");
        int affectedRow = targetObject.add(email);
        System.out.println("commit transaction");
        System.out.println(String.format("affected row:%s", affectedRow));
        return affectedRow;
    }
}

2.4 动态代理的毛病

  1. 只能代理一个实现类或接口,无奈做到代理多个类,这样照成了代理类的作用范畴的局限性
  2. 对于不须要代理的接口办法也须要实现,造成的代码的冗余,扩大难的问题
  3. 依赖于接口

3. 动静代理

在运行期间动静生成字节码文件

3.1 示例代码

public class ServiceProxy implements InvocationHandler {
    private final Object targetObject;
    private ClassLoader classLoader;
    private Class<?>[] interfaces;

    public ServiceProxy(Object targetObject) {
        this.targetObject = targetObject;
        this.classLoader = targetObject.getClass().getClassLoader();
        this.interfaces = targetObject.getClass().getInterfaces();
    }

    public Object getProxyObject() {return Proxy.newProxyInstance(classLoader, interfaces, this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println(String.format("%s.%s", targetObject.getClass().getName(), method.getName()));
        System.out.println("begin transaction");
        if (args != null) {System.out.println(String.format("params:%s", Arrays.toString(args)));
        }
        Object retVal = method.invoke(targetObject, args);
        System.out.println("commit transaction");
        System.out.println(String.format("affected row:%s", retVal));
        return retVal;
    }
}
public interface UserService {
    /**
     * 增加
     * @param email 邮箱
     * @return 受影响的行数
     */
    int add(String email);
}
public class UserServiceImpl implements UserService {
    @Override
    public int add(String email) {return 1;}
}

3.2 与动态代理的区别

  1. 动态代理在编译期就曾经实现,而动静代理在运行时实现
  2. 动态代理只能代理一个类,而动静代理能够代理多个类
  3. 都依赖与接口
  4. 动态代理扩大难以及难以保护,而动静代理代理了接口中的所有的办法,达到了通用性。
  5. 因为动静代理代理了接口中的所有办法,如果指标对象须要扩大接口办法且不依赖代理对象就须要关注代理的实现细节。

4. CgLig 代理

cglib全称 Code Generation Libary,即 代码生成库,能可能在运行时生成子类对象从而达到对指标对象扩大的性能。

4.1 长处

  1. 无论是动态代理还是动静代理都依赖与接口,而 cglib 能够代理一个类,这样就达到了代理类的无侵入性。
  2. 在运行时动静生成字节码文件

4.2 代码示例

要是用 cglib 代理,咱们须要引入 cglib的依赖:

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.2.5</version>
</dependency>
public class ServiceProxyFactory implements MethodInterceptor {
    private final Object targetObject;

    public ServiceProxyFactory(Object targetObject) {this.targetObject = targetObject;}

    public Object createProxyObject() {Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(targetObject.getClass());
        enhancer.setCallback(this);
        return enhancer.create();}

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {//        System.out.println(String.format("obj:%s",obj));
        if (args != null) {System.out.println(String.format("params:%s", Arrays.asList(args)));
        }
        System.out.println(String.format("method proxy:%s",proxy));
        System.out.println(String.format("method signature:%s",proxy.getSignature()));
        System.out.println(String.format("method super index:%s",proxy.getSuperIndex()));
        System.out.println(String.format("method super name:%s",proxy.getSuperName()));
        System.out.println("begin transaction");
        Object affectedRow = method.invoke(targetObject, args);
        System.out.println("commit transaction");
        System.out.println(String.format("affected row:%s", affectedRow));
        return affectedRow;
    }
}
public class UserService {public int add(String email) {System.out.println(String.format("email:%s", email));
        return 1;
    }
}
public class Main {public static void main(String[] args) {UserService userService = new UserService();
        System.out.println(userService);
        ServiceProxyFactory serviceProxyFactory = new ServiceProxyFactory(userService);
        UserService proxyObject = (UserService) serviceProxyFactory.createProxyObject();
        System.out.println(proxyObject);
        proxyObject.add("shaw@gmail.com");
    }
}

5. 小结

  1. 动态代理实现比较简单,通过代理对象对指标对象进行包装,就能够实现加强性能,但动态代理只能对一个类进行代理,如果代理指标对象过多则会产生很多代理类。
  2. JDK 代理也就是动静代理,代理类须要实现 InvocationHandler 接口
  3. 动态代理在编译生成字节码文件,间接应用,效率高。
  4. 动静代理和 cglib 代理基于字节码,在运行时生成字节码文件,效率比动态代理略低。
  5. 动态代理和动静代理都是基于接口的,而 cglib 能够基于类进行代理
  6. cglib代理在运行时生成代理对象的子类,重写该子类的办法来实现代理,因而代理对象被代理的办法不能被 final 润饰,代理类须要实现MethodInterceptor

6. AOP

Aspect-oriented programming,即面向切面编程,将代码进行模块划分,升高耦合,拆散关注点,缩小对业务代码的侵入性。

在面向对象中,咱们有三大外围特色,别离是:

  1. 封装
  2. 继承
  3. 多态

这三种个性中应用的最多的就是多态,加上因为应用组合代替继承升高对父类的依赖不再关怀实现细节,而实现多态更多的形象出一个顶级接口,因而咱们从面向对象的封装特称形象出 ” 面向接口编程 ” 这个概念。而 AOP 是对面向对象的补充,面向对象以 ” 类 ” 作为根本单元,依据不同的业务场景去将咱们的类进行模块化,咱们的 AOP 模块化的要害是 切面 /AspectAspect不再关怀具体的类型,通过代理技术对指标对象进行加强。

其中代理又分为基于 classinterface的代理,基于 class 的代理咱们个别指的是 aspectJ,而基于interface 的代理则是动态代理和动静代理。

6.1 为什么要应用 AOP

6.1.2 代码无侵入性

在不扭转类构造的状况下,动静的生成字节码对类进行扩大。

6.1 AOP 相干概念

6.1.1 Aspect

首先第一个就是咱们的 aspectaspect 相似于咱们的类,在很多工具类中,它表白的更多其实是通用函数的一个容器,用于保留与这个函数容器相干的办法,而 aspect 就是 pointCutJoinPointAdvice 的容器。

一个 aspect 能够蕴含多个pointCutAdvice,然而只能蕴含一个JoinPoint,因而关系是一对一对多对多。

6.1.2 PointCut

Spring AOP 中咱们只能对办法进行拦挡,但并不是所有办法都须要咱们进行一个加强,因而 PointCut 就是 Spring AOP 给咱们提供的对办法进行过滤的一个性能,在 Spring 官网文档中把它称为 切入点

6.1.3 JoinPoint
6.1.4 Advice

在拦挡了须要代理之后,咱们能够就能够对办法进行加强,Spring给咱们提供以下执行加强的动作:

  1. around
  2. before
  3. after-returning
  4. after-throwing
  5. (finally)after

beforeafter-returningafter-throwing这都很好了解,别离是办法执行之前的动作、办法失常返回的动作、办法异样退出的动作。而 finally-aftertry-catch-finally如果把 after-retruning 比作 try,把after-throwing 比作 catch,那么after 就是finally

咱们能够简略比照 try-catch-finally 的执行流程来了解after-retruningafter-thrwoingfinally-after

  1. try 块中没有抛出异样,那么不会走catch
  2. try 中抛出异样且被 catch 捕捉,如果办法抛出那么会终止 try 后续的代码执行catch
  3. 除非 JVM 虚拟机退出,否者肯定执行finally

Spring-AOP 执行的动作中:

  1. 如果办法失常执行没有抛出异样,那么不会执行after-throwing
  2. 如果办法执行过程中抛出了异样,那么会执行after-throwing
  3. 无论有没有抛出异样都会执行finally-after

try-catch-finaly 中,咱们能够依据 finally 这个个性来执行一些资源开释的操作,比方数据库的敞开、IO 的敞开。同样的,在 after-finlaly 中咱们能够执行一些资源开释操作,比方开释锁。

7. Spring 对 AOP 的整合

要应用 apring-aop 咱们须要引入 SpringAspectJ的反对:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>${spring.framework.version}</version>
</dependency>

7.1 启用 AspectJ 的反对

7.1.1 Java Configuration
@EnableAspectJAutoProxy
public class AspecJConfiguration{}
7.1.2 XML Configuration
<aop:aspectj-autoproxy/>

7.2 定义 Aspect

7.2.1 Java Configuration
import org.aspect.lang.annotaion.Aspect;

@Aspect
public class RepositoryPerformance {}

7.3 定义 PointCut

https://blog.csdn.net/qq52509…

7.3.1 Java Configuration
@PointCut("execution* com.mobile.train.diga.mapper..*(..))")
public void repositoryOps(){}

7.4 定义告诉

7.4.1 Before 告诉

在办法执行之前的告诉

@Before("repositoryOps()")
public void before() {System.out.println("BEFORE CALLING");
}
7.4.2 After Returning 告诉

在办法失常执行返回之后执行

@AfterReturning(value = "repositoryOps()", returning = "retVal")
public void afterReturning(Object retVal) {System.out.println(String.format("retVal:%s", retVal));
}
7.4.3 After Throwing 告诉

在办法异样退出之后执行

@AfterThrowing(value = "repositoryOps()", throwing = "throwable")
public void afterThrowing(Throwable throwable) {throwable.printStackTrace(System.err);
}
7.4.4 After Finally 告诉

在办法执行实现退出时候,能够利用这个告诉开释资源,例如,锁

@After(value = "repositoryOps()")
public void releaseLock() {System.out.println("DO RELEASE LOCK!");
}
7.4.5 around 告诉

在办法执行过程中进行包装盘绕运行,能够利用盘绕告诉进行行性能统计

@Around(value = "repositoryOps()")
public Object doBasicProfiling(ProceedingJoinPoint joinPoint) throws Throwable {StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    Object[] args = joinPoint.getArgs();
    Object retVal = joinPoint.proceed(args);
    stopWatch.stop();
    System.out.println(String.format("%sms", stopWatch.getTotalTimeMillis()));
    return retVal;
}
@Around(value = "repositoryOps()")
public Object doLogPerformanceProfiling(ProceedingJoinPoint pjp) throws Throwable {long startTime = System.currentTimeMillis();
    String name = "-";
    String result = "Y";
    try {name = pjp.getSignature().toShortString();
        return pjp.proceed();} catch (Throwable t) {
        result = "N";
        log.error(t);
        throw t;
    } finally {long endTime = System.currentTimeMillis();
        log.info(String.format("%s;%s;%sms", name, result, endTime - startTime));
    }
}

8. AOP 应用场景

8.1 日志场景

  1. 诊断上下文,入 log4j 或者 logbak 中的_x0008_MDC
  2. 辅助信息,如:办法执行工夫

8.2 统计场景

  1. 办法调用次数
  2. 执行异样次数
  3. 数据抽样
  4. 数值累加

8.3 安防场景

  1. 熔断,如:Netflix Hystrix
  2. 限流和降级,如:Alibaba Sentinel
  3. 认证和受权,如:Spring Security
  4. 监控,如:JMX

8.4 性能场景

  1. 缓存,如Spring Cache
  2. 超时管制
正文完
 0