Spring aop (一)

###1. spring aop支持AspectJ

  1. 启用@AspectJ支持
@Configuration
@ComponentScan("com.good.aop.demo4") // 为了引入AnimalService
@EnableAspectJAutoProxy(proxyTargetClass = false)
public class AppConfig4 {
}
  1. 声明一个Aspect
  2. 申明一个pointCut,切入点表达式由@Pointcut注释表示。切入点声明由两部分组成:一个签名包含名称和任何参数,以及一个切入点表达式,该表达式确定我们对哪个方法执行感兴趣。
  3. 申明一个Advice通知,advice通知与pointcut切入点表达式相关联,并在切入点匹配的方法执行@Before之前、@After之后或前后运行。
@Aspect
@Component
public class LoggingAspect {
    /**
     * 2.定义一个方法, 用于声明切入点表达式. 一般地,
     * 该方法中再不需要添入其他的代码.
     * 使用 @Pointcut 来声明切入点表达式.
     * 后面的其他通知直接使用方法名来引用当前的切入点表达式.
     */
    @Pointcut("execution(* com.good.aop.demo4.service.impl.*.*(..))")
    public void declareJointPointExpression(){}
    /**
     * 3.前置通知
     * 在 com.atguigu.spring.aop.ArithmeticCalculator
     * 接口的每一个实现类的每一个方法开始之前执行一段代码
     * 用通配符*来表示所有
     */
    @Before("declareJointPointExpression()")
    public void beforeMethod(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        System.out.println("before method " + methodName + " begin with:" + Arrays.asList(args));
    }
    /**
     * 后置通知
     * 在方法执行之后执行的代码,无论该方法是否出现异常,类似于finally的作用;
     * @param joinPoint
     */
    @After("execution(public double com.good.aop.demo4.service.impl.*.*(..))")
    public void afterMethod(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        System.out.println(" after method " + methodName +
                " end " + Arrays.asList(args));
    }

    /**
     * 返回通知
     * 在方法法正常结束受执行的代码
     * 返回通知是可以访问到方法的返回值的!
     */
    @AfterReturning(value = "execution(public double com.good.aop.demo4.service.impl.*.*(..))",
            returning = "result")
    public void afterReturning(JoinPoint joinPoint,
                               Object result) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("afterReturning The method " + methodName + " ends with " + result);
    }

    /**
     * 异常通知
     * 在目标方法出现异常时会执行的代码.
     * 可以访问到异常对象; 且可以指定在出现特定异常时在执行通知代码
     */
    @AfterThrowing(value = "execution(public double com.good.aop.demo4.service.impl.*.*(..))", throwing = "e")
    public void afterThrowing(JoinPoint joinPoint, Exception e) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("afterThrowing The method " + methodName + " occurs excetion:" + e);
    }

    /**
     * 环绕通知需要携带 ProceedingJoinPoint 类型的参数.
     * 环绕通知类似于动态代理的全过程: ProceedingJoinPoint 类型的参数可以决定是否执行目标方法.
     * 且环绕通知必须有返回值, 返回值即为目标方法的返回值
     */
    @Around("execution(public double com.good.aop.demo4.service.impl.*.*(..))")
    public Object aroundMethod(ProceedingJoinPoint pjd) {

        Object result = null;
        String methodName = pjd.getSignature().getName();
        try {
            //前置通知
            System.out.println("around The method " + methodName + " begins with " + Arrays.asList(pjd.getArgs()));
            //执行目标方法
            result = pjd.proceed();
            //返回通知
            System.out.println("around The method " + methodName + " ends with " + result);
        } catch (Throwable e) {
            //异常通知
            System.out.println("around The method " + methodName + " occurs exception:" + e);
            throw new RuntimeException(e);
        }
        //后置通知
        System.out.println("around The method " + methodName + " ends");
        return result;
    }
}

补充一下被切面的方法:

public interface ArithmeticCalculator {
    double plus(int i, int j);
    double sub(int i, int j);
    double multi(int i, int j);
    double div(int i, int j);
}
@Service
public class ArithmeticCalculatorImpl implements ArithmeticCalculator {

    public double plus(int i, int j) {
        double result = i + j;
        return result;
    }

    public double sub(int i, int j) {
        double result = i - j;
        return  result;
    }

    public double multi(int i, int j) {
        double result = i * j;
        return result;

    }

    public double div(int i, int j) {
        double result = i / j;
        return result;
    }
}

测试类:

public class TestDemo4 {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context =
                new AnnotationConfigApplicationContext(AppConfig4.class);
        ArithmeticCalculator bean = context.getBean(ArithmeticCalculator.class);
        System.out.println(bean.div(2, 2));
        System.out.println(bean.div(2, 0));
    }
}

打印结果:

around The method div begins with [2, 2]
before method div begin with:[2, 2]
around The method div ends with 1.0
around The method div ends
 after method div end [2, 2]
afterReturning The method div ends with 1.0
1.0
around The method div begins with [2, 0]
before method div begin with:[2, 0]
around The method div occurs exception:java.lang.ArithmeticException: / by zero
 after method div end [2, 0]
afterThrowing The method div occurs excetion:java.lang.RuntimeException: java.lang.ArithmeticException: / by zero

2. 各种连接点joinPoint的意义:

1. execution
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
这里问号表示当前项可以有也可以没有,其中各项的语义如下
modifiers-pattern:方法的可见性,如publicprotected;
ret-type-pattern:方法的返回值类型,如intvoid等;
declaring-type-pattern:方法所在类的全路径名,如com.spring.Aspect;
name-pattern:方法名类型,如buisinessService();
param-pattern:方法的参数类型,如java.lang.String;
throws-pattern:方法抛出的异常类型,如java.lang.Exception;
example:
@Pointcut("execution(* com.chenss.dao.*.*(..))")//匹配com.chenss.dao包下的任意接口和类的任意方法
@Pointcut("execution(public * com.chenss.dao.*.*(..))")//匹配com.chenss.dao包下的任意接口和类的public方法
@Pointcut("execution(public * com.chenss.dao.*.*())")//匹配com.chenss.dao包下的任意接口和类的public 无方法参数的方法
@Pointcut("execution(* com.chenss.dao.*.*(java.lang.String, ..))")//匹配com.chenss.dao包下的任意接口和类的第一个参数为String类型的方法
@Pointcut("execution(* com.chenss.dao.*.*(java.lang.String))")//匹配com.chenss.dao包下的任意接口和类的只有一个参数,且参数为String类型的方法
@Pointcut("execution(* com.chenss.dao.*.*(java.lang.String))")//匹配com.chenss.dao包下的任意接口和类的只有一个参数,且参数为String类型的方法
@Pointcut("execution(public * *(..))")//匹配任意的public方法
@Pointcut("execution(* te*(..))")//匹配任意的以te开头的方法
@Pointcut("execution(* com.chenss.dao.IndexDao.*(..))")//匹配com.chenss.dao.IndexDao接口中任意的方法
@Pointcut("execution(* com.chenss.dao..*.*(..))")//匹配com.chenss.dao包及其子包中任意的方法

关于这个表达式的详细写法,可以脑补也可以参考官网很容易的,可以作为一个看spring官网文档的入门,打破你害怕看官方文档的心理,其实你会发觉官方文档也是很容易的
https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#aop-pointcuts-examples

由于Spring切面粒度最小是达到方法级别,而execution表达式可以用于明确指定方法返回类型,类名,方法名和参数名等与方法相关的信息,并且在Spring中,大部分需要使用AOP的业务场景也只需要达到方法级别即可,因而execution表达式的使用是最为广泛的

2. within
// 表达式的最小粒度为类,其参数为全路径的类名(可使用通配符)
// ------------
// within与execution相比,粒度更大,仅能实现到包和接口、类级别。而execution可以精确到方法的返回值,参数个数、修饰符、参数类型等
@Pointcut("within(com.chenss.dao.*)")//匹配com.chenss.dao包中的任意方法
@Pointcut("within(com.chenss.dao..*)")//匹配com.chenss.dao包及其子包中的任意方法
3. args

args表达式的作用是匹配指定参数类型和指定参数数量的方法,与包名和类名无关

/**
 * args同execution不同的地方在于:
 * args匹配的是运行时传递给方法的参数类型,args指定的参数必须是全路径的
 * execution(* *(java.io.Serializable))匹配的是方法在声明时指定的方法参数类型。
 */
@Pointcut("args(java.io.Serializable)")//匹配运行时传递的参数类型为指定类型的、且参数个数和顺序匹配
@Pointcut("@args(com.chenss.anno.Chenss)")//接受一个参数,并且传递的参数的运行时类型具有@Classified
3.4 this和target

  this和target需要放在一起进行讲解,主要目的是对其进行区别。this和target表达式中都只能指定类或者接口,在面向切面编程规范中,this表示匹配调用当前切点表达式所指代对象方法的对象,target表示匹配切点表达式指定类型的对象。比如有两个类A和B,并且A调用了B的某个方法,如果切点表达式为this(B),那么A的实例将会被匹配,也即其会被使用当前切点表达式的Advice环绕;如果这里切点表达式为target(B),那么B的实例也即被匹配,其将会被使用当前切点表达式的Advice环绕。
  在讲解Spring中的this和target的使用之前,首先需要讲解一个概念:业务对象(目标对象)和代理对象。对于切面编程,有一个目标对象,也有一个代理对象,目标对象是我们声明的业务逻辑对象,而代理对象是使用切面逻辑对业务逻辑进行包裹之后生成的对象。如果使用的是Jdk动态代理,那么业务对象和代理对象将是两个对象,在调用代理对象逻辑时,其切面逻辑中会调用目标对象的逻辑;如果使用的是Cglib代理,由于是使用的子类进行切面逻辑织入的,那么只有一个对象,即织入了代理逻辑的业务类的子类对象,此时是不会生成业务类的对象的。
  在Spring中,其对this的语义进行了改写,即如果当前对象生成的代理对象符合this指定的类型,那么就为其织入切面逻辑。简单的说就是,this将匹配代理对象为指定类型的类。target的语义则没有发生变化,即其将匹配业务对象为指定类型的类。如下是使用this和target表达式的简单示例:

this(com.spring.service.BusinessObject)
target(com.spring.service.BusinessObject)

  通过上面的讲解可以看出,this和target的使用区别其实不大,大部分情况下其使用效果是一样的,但其区别也还是有的。Spring使用的代理方式主要有两种:Jdk代理和Cglib代理。针对这两种代理类型,关于目标对象与代理对象,理解如下两点是非常重要的:

  • 如果目标对象被代理的方法是其实现的某个接口的方法,那么将会使用Jdk代理生成代理对象,此时代理对象和目标对象是两个对象,并且都实现了该接口;
  • 如果目标对象是一个类,并且其没有实现任意接口,那么将会使用Cglib代理生成代理对象,并且只会生成一个对象,即Cglib生成的代理类的对象。
    结合上述两点说明,这里理解this和target的异同就相对比较简单了。我们这里分三种情况进行说明:
  • this(SomeInterface)或target(SomeInterface):这种情况下,无论是对于Jdk代理还是Cglib代理,其目标对象和代理对象都是实现SomeInterface接口的(Cglib生成的目标对象的子类也是实现了SomeInterface接口的),因而this和target语义都是符合的,此时这两个表达式的效果一样;
  • this(SomeObject)或target(SomeObject),这里SomeObject没实现任何接口:这种情况下,Spring会使用Cglib代理生成SomeObject的代理类对象,由于代理类是SomeObject的子类,子类的对象也是符合SomeObject类型的,因而this将会被匹配,而对于target,由于目标对象本身就是SomeObject类型,因而这两个表达式的效果一样;
  • this(SomeObject)或target(SomeObject),这里SomeObject实现了某个接口:对于这种情况,虽然表达式中指定的是一种具体的对象类型,但由于其实现了某个接口,因而Spring默认会使用Jdk代理为其生成代理对象,Jdk代理生成的代理对象与目标对象实现的是同一个接口,但代理对象与目标对象还是不同的对象,由于代理对象不是SomeObject类型的,因而此时是不符合this语义的,而由于目标对象就是SomeObject类型,因而target语义是符合的,此时this和target的效果就产生了区别;这里如果强制Spring使用Cglib代理,因而生成的代理对象都是SomeObject子类的对象,其是SomeObject类型的,因而this和target的语义都符合,其效果就是一致的。

关于this和target的异同,我们使用如下示例进行简单演示:

// 目标类
public class Apple {
  public void eat() {
    System.out.println("Apple.eat method invoked.");
  }
}
// 切面类
@Aspect
public class MyAspect {
  @Around("this(com.good.apo.demo5.service.AppleService)")
  public Object around(ProceedingJoinPoint pjp) throws Throwable {
    System.out.println("this is before around advice");
    Object result = pjp.proceed();
    System.out.println("this is after around advice");
    return result;
  }
}
// 驱动类
public class MainTest {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
        AppleService fruit = (AppleService) ctx.getBean("appleService");
        fruit.eat();
    }
}

执行驱动类中的main方法,结果如下:

this is before around advice
AppleService.eat method invoked.
this is after around advice

上述示例中,Apple没有实现任何接口,因而使用的是Cglib代理,this表达式会匹配Apple对象。这里将切点表达式更改为target,还是执行上述代码,会发现结果还是一样的:

 @Around("target(com.good.apo.demo5.service.AppleService)")

如果我们对Apple的声明进行修改,使其实现一个接口,那么这里就会显示出this和target的执行区别了:

public interface PearService {
    void eat();
}

@Service
public class PearServiceImpl implements PearService {

    @Override
    public void eat() {
        System.out.println("PearService.eat method invoked.");
    }
}

public class MainTest {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
        PearService fruit = (PearService) ctx.getBean("pearServiceImpl");
        fruit.eat();
    }
}

我们还是执行上述代码,对于this表达式,其执行结果如下:

PearService.eat method invoked.

对于target表达式,其执行结果如下:

this is before around advice
PearService.eat method invoked.
this is after around advice

可以看到,这种情况下this和target表达式的执行结果是不一样的,这正好符合我们前面讲解的第三种情况。

参考:
https://www.cnblogs.com/happyflyingpig/p/8023148.html
https://shimo.im/docs/Nj0bcFUy3SYyYnbI/read

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值