Spring - AOP(10)

2020-03-17 18:26:15 浏览数 (1)

编写一个简单的需求:要求在程序执行期间追踪正在发生的活动

代码语言:javascript复制
// 接口
public interface AtithmeticCalculator {
    int add(int i,int j);
    int sub(int i,int j);
    int mul(int i,int j);
    int div(int i,int j);
}
代码语言:javascript复制
// 实现
public class AtithmeticCalculatorImpl implements AtithmeticCalculator {
    @Override
    public int add(int i, int j) {
        System.out.println("The method add begins with ["   i   ","   j   "]");
        int result = i   j;
        System.out.println("The method add ends with "   result);
        return result;
    }
    @Override
    public int sub(int i, int j) {
        System.out.println("The method add begins with ["   i   ","   j   "]");
        int result = i - j;
        System.out.println("The method add ends with "   result);
        return result;
    }
    @Override
    public int mul(int i, int j) {
        System.out.println("The method add begins with ["   i   ","   j   "]");
        int result = i * j;
        System.out.println("The method add ends with "   result);
        return result;
    }
    @Override
    public int div(int i, int j) {
        System.out.println("The method add begins with ["   i   ","   j   "]");
        int result = i / j;
        System.out.println("The method add ends with "   result);
        return result;
    }
}
代码语言:javascript复制
// Main
public class Main {
    public static void main(String[] args) {
        AtithmeticCalculator atithmeticCalculator = new AtithmeticCalculatorImpl();
        int result = atithmeticCalculator.add(1,2);
        System.out.println(result);
        result = atithmeticCalculator.div(4,2);
        System.out.println(result);
    }
}
// Output
The method add begins with [1,2]
The method add ends with 3
3
The method add begins with [4,2]
The method add ends with 2
2

上面例子中的实现代码存在的问题:

  1. 代码混乱:非业务需求(日志和验证等)加入后,原有的业务方法急剧膨胀,每个方法在处理核心逻辑的同时还必须兼顾其他多个关注点
  2. 代码分散:以日志需求为例,只是为了满足这个单一的需求,就不得不在多个模块(方法)里多次重复相同的日志代码,如果日志需求发生变化,必须修改所有的模块

使用动态代理解决上述问题 代理设计模式的原理:使用一个代理将对象包装起来,然后用该代理对象取代原始对象,任何对原始对象的调用都要通过代理,代理对象决定是否以及合适将方法调用转到原始对象上

代码语言:javascript复制
// 接口
public interface ArithmeticCalculator {
    int add(int i, int j);
    int sub(int i, int j);
    int mul(int i, int j);
    int div(int i, int j);
}
代码语言:javascript复制
// 实现类
public class ArithmeticCalculatorImpl implements ArithmeticCalculator {
    @Override
    public int add(int i, int j) {
        int result = i   j;
        return result;
    }
    @Override
    public int sub(int i, int j) {
        int result = i - j;
        return result;
    }
    @Override
    public int mul(int i, int j) {
        int result = i * j;
        return result;
    }
    @Override
    public int div(int i, int j) {
        int result = i / j;
        return result;
    }
}
代码语言:javascript复制
// 代理
public class ArithmeticCalculatorLoggingProxy {
    // 要代理的对象
    private ArithmeticCalculator target;

    public ArithmeticCalculatorLoggingProxy(ArithmeticCalculator target){
        this.target = target;
    }
    public ArithmeticCalculator getLoggingProxy(){
        ArithmeticCalculator proxy = null;
        // 代理对象由哪一个类加载器负责加载
        ClassLoader loader = target.getClass().getClassLoader();
        // 代理对象的类型,即其中有哪些方法
        Class [] interfaces = new Class[]{ArithmeticCalculator.class};
        // 当调用代理对象其中的方法时,该执行的代码
        InvocationHandler h = new InvocationHandler() {
            /**
             *
             * @param proxy 正在返回的那个代理对象,一般情况下,在invoke方法中都不使用该对象
             * @param method 正在被调用的方法
             * @param args 调用方法时,传入的参数
             * @return
             * @throws Throwable
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                String methodName = method.getName();
                // 日志
                System.out.println("The method"   methodName   "begins with "   Arrays.asList(args));
                // 执行方法
                Object result = method.invoke(target,args);
                // 日志
                System.out.println("The method"   methodName   "ends with "   result);
                return result;
            }
        };
        proxy = (ArithmeticCalculator) Proxy.newProxyInstance(loader,interfaces,h);
        return proxy;
    }
}
代码语言:javascript复制
// Main
public class Main {
    public static void main(String[] args) {
        ArithmeticCalculator target = new ArithmeticCalculatorImpl();
        ArithmeticCalculator proty = new ArithmeticCalculatorLoggingProxy(target).getLoggingProxy();
        int result = proty.add(1,2);
        System.out.println(result);
        result = proty.div(8,2);
        System.out.println(result);
    }
}

使用AOP

AOP面向切面编程,是一种新的方法论,并对传统OOP(面向对象编程)的补充

在应用AOP编程时,仍然需求定义公共功能,但可以明确的定义这个功能在哪里,以什么方式应用,并且不必修改受影响的类,这样依赖横切关注点就被模块化到特殊的对象(切面)里

AOP术语

切面(Aspect):横切关注点 (跨越应用程序多个模块的功能)被模块化的特殊对象 通知(Advice):切面必须要完成的工作 目标(Target):被通知的对象 代理(Proxy):向目标对象应用通知之后创建的对象 连接点(Joinpoint):程序执行的某个特定位置:如类某个方法调用前,调用后,方法抛出异常后等。 切点(pointcut):每个类都拥有多个连接点。即连接点是程序类中客观存在的事务。AOP通过切点定位到特定的连接点

Spring中启用AspectJ注解支持

AspectJ:Java社区里最完整最流行的AOP框架

通知是标注有注解的简单的Java方法: @Before 前置通知,在方法执行之前执行 @After 后置通知,在方法执行之后执行 @AfterRunning 返回通知,在方法返回结果之后执行 @AfterThrowing 异常通知,在方法抛出异常之后 @Around环绕通知,环绕着方法执行

代码语言:javascript复制
// maven注入依赖
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-aop</artifactId>
  <version>5.1.12.RELEASE</version>
</dependency>

<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjweaver</artifactId>
  <version>1.9.4</version>
</dependency>
代码语言:javascript复制
// ArithmeticCalculator
public interface ArithmeticCalculator {
    int add(int i, int j);
    int sub(int i, int j);
    int mul(int i, int j);
    int div(int i, int j);
}
代码语言:javascript复制
// ArithmeticCalculatorImpl
@Component
public class ArithmeticCalculatorImpl implements ArithmeticCalculator {
    @Override
    public int add(int i, int j) {
        int result = i   j;
        return result;
    }
    @Override
    public int sub(int i, int j) {
        int result = i - j;
        return result;
    }
    @Override
    public int mul(int i, int j) {
        int result = i * j;
        return result;
    }
    @Override
    public int div(int i, int j) {
        int result = i / j;
        return result;
    }
}
代码语言:javascript复制
// ArithmeticCalculatorLoggingProxy
public class ArithmeticCalculatorLoggingProxy {
    // 要代理的对象
    private ArithmeticCalculator target;

    public ArithmeticCalculatorLoggingProxy(ArithmeticCalculator target){
        this.target = target;
    }
    public ArithmeticCalculator getLoggingProxy(){
        ArithmeticCalculator proxy = null;
        // 代理对象由哪一个类加载器负责加载
        ClassLoader loader = target.getClass().getClassLoader();
        // 代理对象的类型,即其中有哪些方法
        Class [] interfaces = new Class[]{ArithmeticCalculator.class};
        // 当调用代理对象其中的方法时,该执行的代码
        InvocationHandler h = new InvocationHandler() {
            /**
             * @param proxy 正在返回的那个代理对象,一般情况下,在invoke方法中都不使用该对象
             * @param method 正在被调用的方法
             * @param args 调用方法时,传入的参数
             * @return
             * @throws Throwable
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                String methodName = method.getName();
                // 日志
                System.out.println("The method"   methodName   "begins with "   Arrays.asList(args));
                // 执行方法
                Object result = method.invoke(target,args);
                // 日志
                System.out.println("The method"   methodName   "ends with "   result);
                return result;
            }
        };
        proxy = (ArithmeticCalculator) Proxy.newProxyInstance(loader,interfaces,h);
        return proxy;
    }
}
代码语言:javascript复制
// LoggingAspect
@Component
@Aspect
public class LoggingAspect {

    // 声明该方法是一个前置通知:在目标方法开始之前执行
    // @Before("execution(public int com.sangyu.test10.ArithmeticCalculator.add(int, int))") // 只针对add方法
    @Before("execution(public int com.sangyu.test10.ArithmeticCalculator.*(int, int))") // 所有方法:add、mul、div、sub
    public void beforeMethod(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        List<Object> args = Arrays.asList(joinPoint.getArgs());
        System.out.println("The method "   methodName   " begins with"   args);
    }
}
代码语言:javascript复制
// Main
public class Main {
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        ArithmeticCalculator arithmeticCalculator = ctx.getBean(ArithmeticCalculator.class);
        int result = arithmeticCalculator.add(3,6);
        System.out.println("result: "   result);
        result = arithmeticCalculator.sub(2,1);
        System.out.println("result: "   result);
        result = arithmeticCalculator.mul(2,1);
        System.out.println("result: "   result);
        result = arithmeticCalculator.div(6,3);
        System.out.println("result: "   result);
    }
}

前置通知

在方法执行之前执行的通知,使用@Before注解,并将切入点表达式的值作为注解值

编写AspectJ切入点表达式

通过方法的签名来匹配各种方法: execution * com.sangyu.test10.ArithmeticCalculator.*(...) 匹配ArithmeticCalculator中声明的所有方法,第一个代表任意修饰符及任意返回值,第二个 代表任意方法,...表示匹配任意数量的参数(若目标类于接口与该切面在同一个包中,可以省略包名)

execution public * com.sangyu.test10.ArithmeticCalculator.*(...) 匹配ArithmeticCalculator接口的所有共有方法

execution public double com.sangyu.test10.ArithmeticCalculator.*(...) 匹配ArithmeticCalculator中返回double类型数值的方法

execution public double com.sangyu.test10.ArithmeticCalculator.*(double,...) 匹配第一个参数为double类型的方法 ...匹配任意数量任意类型的参数

execution public double com.sangyu.test10.ArithmeticCalculator.*(double,double) 匹配参数类型为double,double类型的方法

execution * *,*(...) 执行任意类的任意方法

JoinPoint

通知方法中beforeMethod()中声明一个类型为JoinPoint的参数,可以访问链接细节,如方法名称和参数值

后置通知

在连接点完成之后执行的,即连接点返回结果或者抛出异常的时候,下面的后置通知记录了方法的终止,一个切面可以包括一个或者多个通知

代码语言:javascript复制
@Component
@Aspect
public class LoggingAspect {
    // 声明该方法是一个前置通知:在目标方法开始之前执行
     @Before("execution(public int com.sangyu.test10.ArithmeticCalculator.add(int, int))") // 只针对add方法
//    @Before("execution(public int com.sangyu.test10.ArithmeticCalculator.*(int, int))") // 所有方法:add、mul、div、sub
    public void beforeMethod(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        List<Object> args = Arrays.asList(joinPoint.getArgs());
        System.out.println("The method "   methodName   " begins with"   args);
    }
    // 后置通知:在目标方法执行后(无论是否发生异常),执行的通知
    @Before("execution(public int com.sangyu.test10.ArithmeticCalculatorImpl.*(int, int))") // 所有方法:add、mul、div、sub
    public void afterMethod(JoinPoint joinPoint){
         String methodName = joinPoint.getSignature().getName();
        System.out.println("The method "   methodName   " end");
    }
}

0 人点赞