美文网首页
Spring AOP

Spring AOP

作者: BlueSkyBlue | 来源:发表于2020-03-19 18:13 被阅读0次

为什么要使用AOP

问题:
代码混乱:越来越多的非业务需求(日志和验证等)加入后,原有的业务方法急剧膨胀,每个方法在处理核心逻辑的同时还必须兼顾其它多个关注点。

代码分散:以日志需求为例,只是为了满足这个单一的需求,就不得不在多个模块里重复相同的日志代码。如果日志需求发生变化必须修改所有的模块。

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

举例说明:
比如我们要写一个计算器相关的功能。首先我们需要创建一个接口。

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

之后我们写在实现类

public class ArithmaticCalculatorLoggingImpl 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;
    }
}

此时我们需要加入日志的功能。在每个计算方法前后加上日志。此时我们是这样写的。

public class ArithmaticCalculatorImpl implements ArithmeticCalculator {
    @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 sub begins with " + i + ", " + j);
        int result = i - j;
        System.out.println("The method sub ends with " + result);
        return result;
    }

    @Override
    public int mul(int i, int j) {
        System.out.println("The method mul begins with " + i + ", " + j);
        int result = i * j;
        System.out.println("The method mul ends with " + result);
        return result;
    }

    @Override
    public int div(int i, int j) {
        System.out.println("The method div begins with " + i + ", " + j);
        int result = i / j;
        System.out.println("The method div ends with " + result);
        return result;
    }
}

可以看到这样写会很繁琐。如果有一天我们不需要日志记录的功能时,需要一个一个的删除。此时AOP的作用就体现出来了。

这里我们需要把日志的功能抽象出来作为一个代理类。

public class ArithmeticCalculatorLoggingProperty {
    private ArithmeticCalculator target;

    public ArithmeticCalculator getLoggingProxy(){
        ArithmeticCalculator proxy = null;

        //代理对象由那一个类加载器负责加载
        ClassLoader loader = target.getClass().getClassLoader();
        //代理对象的类型,即其中有哪些方法
        Class [] interfaces = new Class[]{ArithmeticCalculator.class};
        //调用代理对象中的方法时,该执行的代码
        InvocationHandler h = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("Invoke...");
                return 0;
            }
        };
        proxy = (ArithmeticCalculator)Proxy.newProxyInstance(loader, interfaces, h);

        return proxy;
    }
}

之后我们按照需求来改写代理类。

public ArithmeticCalculator getLoggingProxy(){
    ArithmeticCalculator proxy = null;
    ClassLoader loader = target.getClass().getClassLoader();
    Class [] interfaces = new Class[]{ArithmeticCalculator.class};
    InvocationHandler h = new InvocationHandler() {
        @Override
        /**
         * @param
         * proxy: 正在返回的代理对象,一般情况下在invoke方法中不使用该对象。
         * method: 正在被调用的方法。
         * args: 调用方法时传入的参数。
         */
            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;
}

之后写测试类

public static void main(String[] args) {
    ArithmeticCalculator calculator = new ArithmaticCalculatorLoggingImpl();
    ArithmeticCalculator proxy = new ArithmeticCalculatorLoggingProperty(calculator).getLoggingProxy();

    System.out.println("Add");
    System.out.println(proxy.add(1, 2));

    System.out.println("Subtract");
    System.out.println(proxy.sub(2, 1));
}

此时,我们如果要更改日志只需要修改一个地方不需要更改多个地方。

AOP简介

AOP(Aspect-oriented programming)面向切面编程,是对OOP(Object-oriented programming)的补充。

AOP的主要编程对象是切面(Aspect),而切面模块化横切关注点。

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

AOP的好处:

  • 每个事物逻辑位于一个位置,代码不分散,便于维护和升级。
  • 业务模块更简洁,只包含核心业务代码。

如何使用AOP处理上述业务


AOP相关术语

切面(Aspect):横切关注点(跨越应用程序多个模块的功能)被模块化的特殊对象。
通知(Advice):切面必须要完成的工作。
目标(Target):被通知的对象。
代理(Proxy):向目标对象应用通知之后创建的对象。
连接点(jointpoint):程序执行的某个特定位置。如类某个方法调用前,调用后,方法抛出异常后等。连接点由两个信息确定:方法表示的程序执行点。相对点表示的方位。例如ArithmeticCalculator的add方法执行前的连接点ArithmeticCalculator的add方法之前的位置。
切点(pointcut):每个类都拥有多个连接点。例如ArithmeticCalculator的所有方法实际上都是连接点,即连接点是程序类中客观存在的事务。AOP通过切点定位到特定的连接点。类比,连接点相当于数据库中的记录,切点相当于查询条件。切点和连接点不是一一对应的关系,一个切点匹配多个连接点。切点通过org.springframework.app.Pointcut接口进行描述,它使用类和方法作为连接点的查询条件。

Spring AOP

Aspect J:Java社区里最完整最流行的AOP框架。在Spring 2.0以上版本中,可以使用基于Aspect J注解或基于XML配置的AOP。

使用Spring AOP的过程:

  1. Spring的AOP需要在配置文件中加入aop的命名空间
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/aop
            http://www.springframework.org/schema/aop/spring-aop.xsd"
  1. 基于注解的方式,我们需要在配置文件中加入如下配置。它会自动匹配Aspect注解标注的类生成代理对象。
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
  1. 把横切关注点的代码抽象到切面的类中。

    1. 切面首先是一个IOC中的bean,即加入@Component注解。
    2. 切面还需要加上@Aspect注解。
    3. 在类中声明各种通知。

AspectJ支持五种通知类型:

  • @Before: 前置通知,在方法执行前执行。
  • @After:后置通知,在方法执行后执行。
  • @AfterReturning:返回通知,在方法返回结果之后执行。
  • @AfterThrowing:异常通知,在方法抛出异常之后执行。
  • @Around:环绕通知,围绕着方法执行。
@Component
@Aspect
public class LoggingAspect {
    //声明该方法是一个前置通知
    @Before("execution(public int com.spring.impl.ArithmaticCalculatorLoggingImpl.*(int, int))")
    public void beforeMethod(){
        System.out.println("The method " + methodName + " begins with args " + args);
    }
}
  1. 可以在通知方法中声明一个类型为JointPoint的参数,然后就能访问链接细节,如方法名称和参数值。
@Before("execution(public int com.spring.impl.ArithmaticCalculatorLoggingImpl.*(int, int))")
    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 " + args);
    }

后置通知

在目标方法执行之后(无论是否发生异常)执行的通知。

示例:

@After("execution(public int com.spring.impl.ArithmaticCalculatorLoggingImpl.*(int, int))")
public void afterMethod(JoinPoint joinPoint){
    String methodName = joinPoint.getSignature().getName();
    System.out.println("The method " + methodName + " ends");
}

注意:在后置通知中不能访问目标方法执行的结果。

相关文章

网友评论

      本文标题:Spring AOP

      本文链接:https://www.haomeiwen.com/subject/pvgkyhtx.html