Spring学习笔记(二)AOP

Spring学习笔记(二)AOP

AOP

  • AOP(Aspect-Oriented Programming,面向切面编程):是一个编程思想,是对OOP的补充。
  • AOP的主要编程对象是切面(aspect),而切面模块化横切关注点。
  • 在应用AOP时,需要定义公共功能,明确定义的功能位置,以什么方式应用,但不必修改受影响的类。

在这里插入图片描述

AOP术语

  • 切面(Aspect):横切关注点被模块化的特殊对象。
  • 通知(Advice):切面必须要完成的工作。
  • 目标(target):被通知的对象。
  • 代理(Proxy):向目标对象应用通知之后创建的对象。
  • 连接点(Joinpoint):程序执行的某个特定位置。连接点由两个信息确定:方法表示的程序执行点;相对点表示的方位。
  • 切点(pointcut):每个类拥有多个连接点。连接点是程序类中客观存在的事务。AOP通过切点定位到特定的连接点。类比:连接点相当于数据库中的记录,切点相当于查询条件。

Spring AOP

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

基于注解

  1. 导入maven依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.atguigu</groupId>
    <artifactId>spring_02_aop</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.2.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.4</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>5.2.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.2</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>
  1. 配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       https://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/aop
       https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--    配置自动扫描包-->
    <context:component-scan base-package="com.nl.springaop"/>
<!--    使AspectJ注解起作用:自动为匹配的类生成代理对象-->
    <aop:aspectj-autoproxy/>
</beans>
  1. 将目标类交给spring管理
package com.nl.springaop;

import org.springframework.stereotype.Component;

@Component("arithmeticCalculator")
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;
    }
}
  1. 设置切面
package com.nl.springaop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.List;

/**
 * 把该类声明为一个切面
 * 1.把该类放入到IOC容器中 @Component
 * 2.声明切面 @Aspect
 */
@Aspect
@Component
public class LoggingAspect {
    //前置通知(@Before):在目标方法开始之前执行
    //* 表示返回值类型任意
    //com.nl.springaop.ArithmeticCalculatorImpl 为要切入的方法所在类的全限定名
    //add 为要将日志切入到哪个方法
    //(int,int) 为方法的参数类型
    @Before("execution(* com.nl.springaop.ArithmeticCalculatorImpl.add(int,int))")
    public void before(JoinPoint joinPoint){
        //JoinPoint对象可以获取连接的细节,方法名、参数等。
        String methodName = joinPoint.getSignature().getName();//获取连接点的签名
        System.out.println("before " + methodName + " method-----");
    }
    
    //后置通知:在目标方法执行后(无论是否发生异常)执行
    //在后置通知中还不能访问目标方法的执行结果
    @After("execution(* com.nl.springaop.ArithmeticCalculatorImpl.*(int,int))")
    public void after(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("after " + methodName + " method...");
    }

    //返回通知:在方法正常结束后执行
    //返回通知是可以访问到方法的返回值的。
    @AfterReturning(value = "execution(* com.nl.springaop.ArithmeticCalculatorImpl.*
                    		(int,int))",returning = "result")
    public void afterReturning(JoinPoint joinPoint,Object result){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("afterReturning " + methodName + " method..." + "|result: " + 
                           result);
    }

    /**
     * 异常通知:在目标方法出现异常时会执行
     * 可以访问到异常对象,且可以指定在出现特定异常时再执行
     * @param joinPoint
     * @param e
     */
    @AfterThrowing(value = "execution(* com.nl.springaop.ArithmeticCalculatorImpl.*
                   (int,int))",throwing = "e")
    public void afterThrowing(JoinPoint joinPoint, ArithmeticException e){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("Method: " + methodName + "||Exception:" + e);
    }

    /**
     * 环绕通知须有proceedingJoinPoint参数
     * 环绕通知类似于动态代理的全过程
     * @param proceedingJoinPoint 可以决定是否执行目标方法
     * 且环绕通知必须有返回值,返回值即为目标方法的返回值。
     */
    @Around("execution(* com.nl.springaop.ArithmeticCalculatorImpl.*(int,int))")
    public Object around(ProceedingJoinPoint proceedingJoinPoint){
        Object result = null;
        String methodName = proceedingJoinPoint.getSignature().getName();

        try {
            //前置通知
            System.out.println( "method:" + methodName + ",args:" + 
                               Arrays.asList(proceedingJoinPoint.getArgs()));
            //执行目标方法
            result = proceedingJoinPoint.proceed();
            //返回通知
            System.out.println("method_result:" + result);
        } catch (Throwable throwable) {
            //异常通知
            System.out.println("method_exception:" + throwable);
            throw new RuntimeException(throwable);
        }
        //后置通知
        System.out.println(methodName + " ends...");
        return result;
    }
}
  1. 测试代码
public class ArithmeticCalculatorImplTest {
@Test
public void baseOnAnnotationTest(){
        ApplicationContext context = new
            ClassPathXmlApplicationContext("applicationContext.xml");
        ArithmeticCalculator arithmeticCalculatorImpl = 
            context.getBean("arithmeticCalculator", ArithmeticCalculator.class);
        int res = arithmeticCalculatorImpl.add(1, 9);
        System.out.println(res);
    	//执行结果
    	//method:add,args:[1, 9]
        //before add method-----
        //method_result:10
        //add ends...
        //after add method...
        //afterReturning add method...|result: 10
        //10
    }
}

通知:前置通知,返回通知,异常通知,后置通知,环绕通知

try{
    //前置通知
    result = method.invoke(target,args);
    //返回通知,可以访问到方法的返回值。
}catch(Exception e){
    //异常通知,可以访问到方法出现的异常。
}
	//后置通知,方法可能会出现异常,所以访问不到方法的返回值。

切面的优先级

可通过@Order()注解设置切面的优先级,值越小优先级越高。如@Order(1)切面就会先于@Order(2)切面执行。

切点表达式的复用

若多个地方需要使用到同一个切点表达式时,可以单独声明一个方法用于承载切点表达式。

/**
*该方法用于声明切点表达式
* 使用@Ponicut注解来声明
*/
 @Pointcut("execution(* com.nl.springaop.ArithmeticCalculatorImpl.*(int,int))")
public void declareJointPointExpression(){}

其他地方若是需要使用他,同一个类中可通过方法名调用。

@Before("declareJointPointExpression()")
public void before(JoinPoint joinPoint){}

其他类中可使用全类名+方法名的方式来使用。

@Before("com.nl.springaop.LoggingAspect.declareJointPointExpression()")
public void verificationArgs(JoinPoint joinPoint){}

基于xml


<!--    配置bean-->
    <bean id="arithmeticCalculator" class="com.nl.xml.springaop.ArithmeticCalculatorImpl"/>
<!--    配置切面的bean-->
    <bean id="loggingAspect" class="com.nl.xml.springaop.LoggingAspect"/>
    <bean id="verificationAspect" class="com.nl.xml.springaop.VerificationAspect"/>

<!--    配置AOP-->
    <aop:config>
<!--        配置切点表达式-->
        <aop:pointcut id="pointcut" expression="execution(*     
        		com.nl.xml.springaop.ArithmeticCalculatorImpl.*(..))"/>
<!--        配置切面及通知-->
        <aop:aspect ref="loggingAspect" order="8">
            <aop:before method="before" pointcut-ref="pointcut"/>
        </aop:aspect>

        <aop:aspect ref="verificationAspect" order="5">
            <aop:before method="verificationArgs" pointcut-ref="pointcut"/>
        </aop:aspect>
    </aop:config>

也可以通过通过<aop:advisor>节点来配置切面,但必须实现相关的接口。

<bean id="customAdvisor" class="com.nl.xml.springaop.CustomAdvisor"/>
<aop:config>
        <aop:pointcut id="p" expression="execution(* 
        		com.nl.xml.springaop.ArithmeticCalculatorImpl.*(..))"/>
        <aop:advisor advice-ref="customAdvisor" pointcut-ref="p" order="1"/>
</aop:config>

自定义切面类:

package com.nl.xml.springaop;

import org.springframework.aop.MethodBeforeAdvice;

import java.lang.reflect.Method;

public class CustomAdvisor implements MethodBeforeAdvice {

    //重写前置通知方法
    @Override
    public void before(Method method, Object[] args, Object target){
        System.out.println("实现MethodBeforeAdvice接口");
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值