欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Spring框架(中) AOP

程序员文章站 2022-05-06 20:36:54
...

Spring(中) AOP

(一)代理模式

1.静态代理

静态代理在使用时,需要定义接口或者父类,被代理对象(即目标对象)与代理对象一起实现相同的接口或者是继承相同父类。

举例:

package com.deserts.proxy.staticproxy;

/**
 * @ClassName ProxyTest
 * @Description TODO
 * @Author deserts
 * @Date 2020/10/16 12:12
 */

interface IShoeFactory{
    void produce();
}

class ShoeFactory implements IShoeFactory{

    @Override
    public void produce() {
        System.out.println("生产鞋子");
    }
}

class ShoeProxy implements IShoeFactory{
    private IShoeFactory shoeFactory;

    public ShoeProxy (IShoeFactory shoeFactory){
        this.shoeFactory = shoeFactory;
    }

    @Override
    public void produce() {
        shoeFactory.produce();
    }
}

public class ProxyTest {
    public static void main(String[] args) {
        ShoeProxy proxy = new ShoeProxy(new ShoeFactory());
        proxy.produce();
    }
}

优点:在不修改目标对象的功能前提下, 能通过代理对象对目标功能扩展

缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类;或者需要实现很多接口,代码混乱。

一旦接口增加方法,目标对象与代理对象都要维护。

2.JDK动态代理

基本介绍:

  1. 代理对象,不需要实现接口,但是目标对象要实现接口,否则不能用动态代理

  2. 代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象

  3. 动态代理也叫做:JDK代理、接口代理

举例:

package com.deserts.proxy.jdkproxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * @ClassName JDKProxyTest
 * @Description TODO
 * @Author deserts
 * @Date 2020/10/16 12:23
 */

interface IShoeFactory{
    void produce();
}
class ShoeFactory implements IShoeFactory{

    @Override
    public void produce() {
        System.out.println("生产鞋子中");
    }
}

class ShoeProxyFactory{
    private Object target;

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

    public Object getProxyInstance(){
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        Object returnResult = method.invoke(target, args);
                        return returnResult;
                    }
                });
    }
}

public class JDKProxyTest {
    public static void main(String[] args) {
        IShoeFactory proxy = (IShoeFactory)new ShoeProxyFactory(new ShoeFactory()).getProxyInstance();
        proxy.produce();
    }

}

(二)AOP概述

1.一个动态代理的例子

1.1 要求

①执行加减乘除运算

②日志:在程序执行期间追踪正在发生的活动

1.2 实现

创建计算器接口,让计算器类去实现;创建动态代理的类,利用构造器或者方法传入计算器对象,创建获取被代理类的方法。

1.ICalculator接口

public interface ICalculator {
    int add(int i, int j);
    int sub(int i, int j);
    int mul(int i, int j);
    int div(int i, int j);
}

2.Calculator类

package com.deserts.aop.proxy;

/**
 * @ClassName Calculator
 * @Description TODO
 * @Author deserts
 * @Date 2020/10/15 19:23
 */
public class Calculator implements ICalculator{

    @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;
    }
}

3.代理类

package com.deserts.aop.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.function.Predicate;

/**
 * @ClassName ProxyFactory
 * @Description TODO
 * @Author deserts
 * @Date 2020/10/15 19:26
 */
public class ProxyFactory {
    Object target;
    public ProxyFactory(Object target){
        this.target = target;
    }

    public Object getProxy(){
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        Object returnResult = null;
                        try {
                            Log.before(method.getName(), args);
                            returnResult = method.invoke(target, args);
                            Log.after(method.getName(), returnResult);
                        } catch (Exception e) {
                            Log.exceptionInfo(method.getName(), e);
                        } finally {
                           Log.end();
                        }
                        return returnResult;
                    }
                });
    }
}

4.封装日志功能,错误信息的类

package com.deserts.aop.proxy;

import java.util.Arrays;

/**
 * @ClassName Log
 * @Description TODO
 * @Author deserts
 * @Date 2020/10/15 19:54
 */
public class Log {
    public static void before(String methodName, Object... args){
        System.out.println(methodName + "即将执行,参数为:" + Arrays.asList(args));
    }

    public static void after(String methodName, Object result){
        System.out.println(methodName + "执行结束,结果为:" + result);
    }

    public static void exceptionInfo(String methodName, Exception e){
        System.out.println(methodName + "执行过程出现了异常,异常信息为:" + e.getCause());
    }

    public static void end(){
        System.out.println("程序执行结束");
    }
}

5.测试类

package com.deserts.aop.proxy;

/**
 * @ClassName Client
 * @Description TODO
 * @Author deserts
 * @Date 2020/10/15 19:34
 */
public class Client {
    public static void main(String[] args) {
        ICalculator proxy = (ICalculator)new ProxyFactory(new Calculator()).getProxy();
        proxy.add(1,2);
        proxy.div(2, 0);
    }
}

2.AOP概述

  1. AOP(Aspect-Oriented Programming,面向切面编程):是一种新的方法论,是对传统 OOP(Object-Oriented Programming,面向对象编程)的补充。

    • 面向对象 纵向继承机制
    • ​ 面向切面 横向抽取机制
  2. AOP编程操作的主要对象是切面(aspect),而切面用于模块化横切关注点(公共功能)

  3. 在应用AOP编程时,仍然需要定义公共功能,但可以明确的定义这个功能应用在哪里,以什么方式应用,并且不必修改受影响的类。这样一来横切关注点就被模块化到特殊的类里——这样的类我们通常称之为“切面”

  4. AOP的好处:

    ① 每个事物逻辑位于一个位置,代码不分散,便于维护和升级

    ② 业务模块更简洁,只包含核心业务代码

    ③ AOP图解

    Spring框架(中) AOP

3.AOP术语

3.1 横切关注点

从每个方法中抽取出来的同一类非核心业务。

Spring框架(中) AOP

3.2 切面(Aspect)

封装横切关注点信息的类,每个关注点体现为一个通知方法。

3.3 通知(Advice)

切面必须要完成的各个具体工作

3.4 目标(Target)

被通知的对象

3.5 代理(Proxy)

向目标对象应用通知之后创建的代理对象

3.6 连接点(Joinpoint)

横切关注点在程序代码中的具体体现,对应程序执行的某个特定位置。例如:类某个方法调用前、调用后、方法捕获到异常后等。

3.7 切入点(pointcut)

定位连接点的方式。每个类的方法中都包含多个连接点,所以连接点是类中客观存在的事物。如果把连接点看作数据库中的记录,那么切入点就是查询条件——AOP可以通过切入点定位到特定的连接点。切点通过org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条件。

3.8 图解

Spring框架(中) AOP

4.AspectJ

4.1 简介

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

在Spring2.0以上版本中,可以使用基于AspectJ注解或基于XML配置的AOP。

4.2 在Spring中启用AspectJ注解支持

1.导入jar包

  • com.springsource.net.sf.cglib-2.2.0.jar
  • com.springsource.org.aopalliance-1.0.0.jar
  • com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
  • spring-aop-4.0.0.RELEASE.jar
  • spring-aspects-4.0.0.RELEASE.jar

2.引入AOP命名空间

Spring框架(中) AOP

3.配置扫描包,配置被代理的类和日志类

日志类需要@Aspect注解,@Before注解将方法配置为前置通知,需要指定value值,值为切入点表达式。同时需要@Component注解让Spring组件扫描去管理。

Spring框架(中) AOP

被代理类也需要@Component注解,让Spring去管理:

Spring框架(中) AOP

测试类使用时需要创建Spring容器去管理被配置好的类的对象:

Spring框架(中) AOP

运行结果:

Spring框架(中) AOP

4.3 用AspectJ注解声明切面

① @Before:前置通知,在方法执行之前执行

② @After:后置通知,在方法执行之后执行

③ @AfterRunning:返回通知,在方法返回结果之后执行

④ @AfterThrowing:异常通知,在方法抛出异常之后执行

⑥ @Around:环绕通知,围绕着方法执行

Spring框架(中) AOP

(三) AOP细节

1.细节一:容器创建的对象

IOC容器中保存的是组件的代理对象,可以理解成这个代理对象也去实现了目标对象所实现的接口

如:

Spring框架(中) AOP

运行结果:

Spring框架(中) AOP

可以看到,该对象是class com.sun.proxy.$Proxy7代理对象,底层原理是jdk动态代理。

除了实现接口的方式去代理,spring还提供了cglib代理,为没有接口的类进行代理。

如:

Spring框架(中) AOP

测试类:

Spring框架(中) AOP

运行结果:

Spring框架(中) AOP

可以看到cglib为我们创建了代理对象

2.细节二:切入点表达式

2.1 *:

1)匹配一个或多个字符,如:execution(public int com.deserts.aop.proxy.Calculator.*(int,int))

  1. 匹配任意类型的参数,如:execution(public int com.deserts.aop.proxy.Calculator.add(*,*))

  2. 只能匹配一层路径,如:execution(public int com.deserts.aop.*.Calculator.add(int,int))

4)权限类型前不能写,可以不写权限类型。public(可选类型)。

2.2 … :
  1. 匹配任意多个参数,如:execution(public int com.deserts.aop.proxy.Calculator.add(…))

2)匹配多层路径,如:execution(public int com.deserts…Calculator.*(int,int))

2.3 两种类型

最精确的:execution(public int com.deserts.aop.proxy.Calculator.add(int,int))

最模糊的:execution(*.*(…)) 不建议写,见方法就切

2.4 使用&&、||、!

在AspectJ中,切入点表达式可以通过 “&&”、“||”、“!”等操作符结合起来。

表达式 含义
execution (* *.add(int,…)) || excution( *.sub(int, …))
!execution (* *.add(int,…)) 匹配不是任意类中第一个参数为int类型的add方法

3.细节三:通知方法的执行顺序

正常执行:@Before(前置通知) ==== @After(后置通知) ==== @AfterReturning(正常返回)

异常执行: @Before(前置通知) ==== @After(后置通知) ==== @AfterThrowing(方法异常)

4.细节四:JoinPoint获取获取目标方法的信息

主要通过传入通知方法的JoinPoint joinPoint参数,在方法内调用参数相关方法进行获取,如:

Spring框架(中) AOP

5.细节五:使用returning、throwing来接收返回值、异常

接收返回值:

Spring框架(中) AOP

接收异常信息:

Spring框架(中) AOP

6.细节六:Spring对通知方法的约束

1)spring对通知方法的没有太大约束,唯一的约束是参数列表的参数不能乱写,如果有未知的参数,需要告诉spring这个参数是用来做什么的,因为这些通知方法是spring所管理的,主要是通过反射来调用的。

2)如returning、throwing指定接收返回值、异常的参数,参数类型要尽量的大,如Object、Exception

7.细节七:抽取可重用的切入点表达式

  • 抽取可重用的切入点表达式:

    • 1.创建一个返回值为void,参数为空的空方法
    • 2.给方法加上注解@PointCut,并在参数里写入要抽取的切入点表达式
    • 3.将方法名放入要使用该表达式的值里面
  • 示例:

    Spring框架(中) AOP

8.细节八:环绕通知

  • 环绕通知是通知里面最强大的,可以当作其它四个通知的整合。
  • 对于环绕通知来说,连接点的参数类型必须是ProceedingJoinPoint。它是 JoinPoint的子接口,允许控制何时执行,是否执行连接点。
  • 在环绕通知中需要明确调用ProceedingJoinPoint的proceed()方法来执行被代理的方法。如果忘记这样做就会导致通知被执行了,但目标方法没有被执行
  • 注意:环绕通知的方法需要返回目标方法执行之后的结果,即调用 joinPoint.proceed();的返回值,否则会出现空指针异常。

示例:

Spring框架(中) AOP

9.细节九:环绕通知的执行顺序&抛出异常让其它通知感受到

测试:将四个普通通知和环绕通知都打开,运行查看结果:

Spring框架(中) AOP

通知顺序:

Spring框架(中) AOP

新的顺序:

Spring框架(中) AOP

让普通通知感受到异常:将该异常抛出去

Spring框架(中) AOP

10.细节十:多切面运行顺序

都是普通通知时,会根据切面类的名称的字母顺序,小的在外层,如LogUtil类在VaAspect类的外层,先进去后出来

Spring框架(中) AOP

若想改变切面运行顺序,可以加上@Order注解,如:

Spring框架(中) AOP

value值越小,优先级越高,也就是在最外层。

当有环绕通知时,环绕通知会先于该切面类的普通通知去执行,因为该环绕通知是加在这个切面类的。

如环绕通知加到LogUtils上:

Spring框架(中) AOP

AOP使用场景

1.日志记录

2.权限验证

3.安全检查

4.事务控制

(四)以xml方式配置切面

1.基于注解的AOP

Spring框架(中) AOP

2.基于配置的AOP

步骤

  1. 在IOC容器中创建管理目标类的bean和切面类的bean

    Spring框架(中) AOP

  2. 配置切面类中的通知方法,配置切面时需要引用上面配置好的切面类

    Spring框架(中) AOP

  3. 测试

    Spring框架(中) AOP

3.注解和配置的选择

注解:快速方便

配置:功能完善

选择:重要的用配置,不重要的用注解