###1. spring aop支持AspectJ
- 启用@AspectJ支持
@Configuration
@ComponentScan("com.good.aop.demo4") // 为了引入AnimalService
@EnableAspectJAutoProxy(proxyTargetClass = false)
public class AppConfig4 {
}
- 声明一个Aspect
- 申明一个pointCut,切入点表达式由@Pointcut注释表示。切入点声明由两部分组成:一个签名包含名称和任何参数,以及一个切入点表达式,该表达式确定我们对哪个方法执行感兴趣。
- 申明一个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:方法的可见性,如public,protected;
ret-type-pattern:方法的返回值类型,如int,void等;
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