SpringAOP面向切面编程
大纲:
-
- 什么是AOP
- [AOP的功能](#2. AOP的功能)
- 如何实现AOP/AOP的底层
1、认识AOP
1.什么是AOP
AOP,即面向切面编程,通过面向对象的思想,将重复的功能代码从逻辑代码中分离出来,降低业务逻辑各部分之间的耦合度,提高程序的可重用性
2. AOP的功能
主要功能
日志记录,性能统计,安全控制,事务处理,异常处理等等。
将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。
3.AOP的底层
-
AOP底层使用动态代理实现。具体为
- JDK动态代理
- cglib动态代理
- 关于静态代理和动态代理的区别
- 静态代理在编译阶段对程序源代码进行修改,生成了静态的代理类(生成了原有的class文件被修改的.class)
- 动态代理在运行阶段生成代理对象
详情转载:(154条消息) Java两种动态代理JDK动态代理和CGLIB动态代理_ready? go!的博客-CSDN博客_jdk动态代理和cglib动态代理
-
JDK动态代理和CGLIB代理的区别
1、底层实现:
- 只能对实现了接口的类生成代理,而不是针对类,该目标类型实现的接口都将被代理。原理是通过在运行期间创建一个接口的实现类来完成对目标对象的代理。
- Cglib代理使用字节码处理框架ASM,对代理对象类的class文件加载进来,通过修改字节码生成子类。 通过“继承”可以继承父类所有的公开方法,然后可以重写这些方法,在重写时对这些方法增强,这就是cglib的思想。
2、代理方式的选择
- JDK创建对象的速度大于Cglib,这是由于Cglib创建对象时需要操作字节码。Cglib执行速度略大于JDK
- 由于Cglib大部分类是直接对Java字节码进行操作,这样生成的类会存放在 堆区中,使用Cglib动态代理太多,容易造成堆空间不足。Spring默认使用JDK动态代理
- JDK需要的委托类要有接口,否则就使用Cglib动态代理
2、AOP的核心概念
转载 (158条消息) Spring AOP详解_苏州-DaniR的博客-CSDN博客_springaop
切面(Aspect): Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。
连接点(join point):连接点表示应用执行过程中能够插入切面的一个点,这个点可以是方法的调用、异常的抛出。
切点(PointCut): 表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。
通知(Advice):AOP框架的增强处理。 Advice 定义了在
Pointcut
里面定义的程序点具体要做的操作, 以及何时执行目标对象(Target):将要被增强的对象/织入Advice的对象
织入(Weaving):将增强处理添加到目标对象中,并创建一个被增强的对象,这个过程被称为织入
3、SpringAOP的相关注解
@Aspect @Pointcut @Before @AfterReturning @AfterThrowing @After @Around 一共7种
转载 Spring AOP常用注解_@未安的博客-CSDN博客_aop 常用注解
SpringAOP实现了AOP的思想,实际使用的话,会配合AspectJ一起使用
3.1 @Aspect (切面注解)
功能:声明当前类为切面类,该注解要放在类上
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface Aspect {
String value() default "";
}
切面类一般为单例,当一个切面类为多例时,指定预处理的切入点表达式
支持指定切入点表达式或 用**@Pointcut修饰的方法名称(要求全限定方法名)**
@Scope("prototype")
@Component
@Aspect(value = "execution(top.beau.service.Impl.*.*(..)")
public class LogUtil {
/**
* 用于配置当前方法的前置通知
*/
@Before("execution(top.beau.service.Impl.*.*(..)")
public void printLog(){
System.out.println("执行打印日志的功能");
}
}
3.2@PointCut (切入点注解)
功能:指定切入点表达式
使用场景:当有多个通知需要执行时,同时增强规则确定的情况下,就可以将切入点表达式通用化
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Pointcut {
/**
* 指定切入点表达式
*/
String value() default "";
/**
* 指定切入点表达式的参数,可以为execution或args中的,通常情况不使用此属性也
* 可以获得参数
*/
String argNames() default "";
}
@Component
@Aspect(value = "execution(* top.beau.service.Impl.*.*(..)")
public class LogUtil {
/***
* 切入点表达式
* value属性中使用了 &&符号,表示并且的关系;&&符号后的args和execution一样,都是切入点表达式支持的关键字,表示参数。
* *可以是全限定名,或者是名称。当指定参数名称时,要求与方法的形参名称相同
* argNames属性,是定义参数的名称,该名称必须和args关键字的名称一致
*/
@Pointcut(value = "execution(* top.beau.service.Impl.*.*(java.lang.String))&&args(str)",argNames = "str")
private void print(String str){
}
}
3.3通知注解
3.3.1@Before 前置通知的注解
被@Before注解修饰的方法为 前置通知
功能:指定在切入点方法执行前执行前置通知
使用场景: 需要对切入点方法执行之前进行增强,若要获取切入点方法中的参数进行处理,可配合切点表达式参数使用
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Before {
/**
* 用于指定切入点表达式,也可为表达式引用
*/
String value();
/**
* 指定切入点表达式参数的名称。要求和切入点表达式中的参数名称一致
* 通常不指定也可以获取切入点方法的参数内容
*/
String argNames() default "";
}
/**
* 前置通知
*/
@Before(value = "print(str)",argNames = "str")
public void beforeLog(String str){
System.out.println("执行切入点方法前记录日志"+str);
}
3.3.2@AfterReturning
功能:配置后置通知,在切入点方法正常执行后执行
基于注解配置时,Spring创建通知方法的拦截器链时,后置通知在最终通知之后,先执行@After注解修饰的方法
使用场景: 此注解是用于配置后置增强切入点方法的,被修饰的方法会在切入点方法正常执行后执行,提交事务,记录访问日志,统计方法执行效率等可以用后置通知实现
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AfterReturning {
/**
* 指定切入点表达式或表达式引用
*/
String value() default "";
/**
* 等同于value
*/
String pointcut() default "";
/**
* 指定切入点方法返回值的变量名称,与切入点方法返回值名称一致
*/
String returning() default "";
/**
* 指定切入点表达式参数的名称。要求和切入点表达式中的参数名称一致
* 通常不指定也可以获取切入点方法的参数内容
*/
String argNames() default "";
}
/**
* 后置通知
*/
@AfterReturning(value = "execution(* top.beau.service.impl.*.*(..))&&args(param)",returning = "str")
public void afterReturningLog(String param,Object str){
System.out.println("正常执行切入点方法后记录日志,切入点方法的参数
是:"+param);
System.out.println("正常执行切入点方法后记录日志,切入点方法的返回值
是:"+str);
}
3.3.3@AfterThrowing
功能:配置异常通知
使用场景: 此注解修饰方法在切入点方法执行产生异常后执行
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AfterThrowing {
/**
* 指定切入点表达式或表达式引用
*/
String value() default "";
/**
* 等同于value
*/
String pointcut() default "";
/**
* 指定切入点方法执行产生异常时的异常对象变量名称,与切入点方法异常变量名称一致
*/
String throwing() default "";
/**
* 指定切入点表达式参数的名称。要求和切入点表达式中的参数名称一致
* 通常不指定也可以获取切入点方法的参数内容
*/
String argNames() default "";
}
/**
* 异常通知
*/
@AfterThrowing(value = "execution(* top.beau.service.impl.*.*(..))&&args(param)",throwing = "e")
public void afterThrowingLog(String param,Throwable e){
System.out.println("执行切入点方法产生异常后记录日志,切入点方法的参数
是:"+param);
System.out.println("执行切入点方法产生异常后记录日志,切入点方法的异常
是:"+e);
}
3.3.4@After
功能:指定最终通知
使用场景: 最终通知于切入点方法执行完成之后执行,无论切入点方法是否产生异常最终通知都会执行,通常用来 做一些清理操作
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface After {
/**
* 指定切入点表达式或表达式引用
*/
String value();
/**
* 指定切入点表达式参数的名称。要求和切入点表达式中的参数名称一致
* 通常不指定也可以获取切入点方法的参数内容
*/
String argNames() default "";
}
/**
* 最终通知
*/
@After(value = "execution(* top.beau.service.impl.*.*(..))")
public void afterLog(){
System.out.println("无论切入点方法执行是否有异常都记录日志");
}
3.3.5@Around
功能:指定环绕通知
特点:不用来指定增强方法执行时间,而通过编码方式手动控制增强方法何时执行
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Around {
/**
* 指定切入点表达式或表达式引用
*/
String value();
/**
* 指定切入点表达式参数的名称。要求和切入点表达式中的参数名称一致
* 通常不指定也可以获取切入点方法的参数内容
*/
String argNames() default "";
}
/**
* 环绕通知
*/
@Around("execution(* top.beau.service.impl.*.*(..))")
public Object arountPrintLog(ProceedingJoinPoint joinPoint){
//1.定义返回值
Object res = null;
try{
//前置通知
System.out.println("执行切入点方法前记录日志");
//2.获取方法执行所需的参数
Object[] args = joinPoint.getArgs();
//3.执行切入点方法
res = joinPoint.proceed(args);
//后置通知
System.out.println("正常执行切入点方法后记录日志");
}catch (Throwable t){
//异常通知
System.out.println("执行切入点方法产生异常后记录日志");
}finally {
//最终通知
System.out.println("无论切入点方法执行是否有异常都记录日志");
}
return rese;
}
4、SpringAOP中的JointPoint和ProceedingJointPoint
转载 (158条消息) SpringAOP中的JointPoint和ProceedingJoinPoint使用总结_啊~~噙!的博客-CSDN博客
4.1 Point Cut
pointcut 是一种程序结构和规则,它用于选取join point并收集这些point的上下文信息。
pointcut通常包含了一系列的Joint Point,我们可以通过pointcut来同时操作jointpoint。
JointPoint通过JpointPoint对象可以获取到下面信息
// 返回目标对象,即被代理的对象
Object getTarget();
//返回切入点的参数
Object[] getArgs();
// 返回切入点的Signature
Signature getSignature();
//返回切入的类型,比如method-call,field-get等等,感觉不重要
String getKind();
4.2Joint Point
作为AOP的切入点.JointPoint对象则包含了和切入相关的很多信息。比如切入点的对象,方法,属性等。我们可以通过反射的方式获取这些点的状态和信息,用于追踪tracing和记录logging应用信息。
-
JointPoint使用:
- 切入点的方法名及其参数
- 切入点方法标注的注解对象(通过该对象可以获取注解信息)
- 切入点目标对象(可以通过反射获取对象的类名,属性和方法名)
-
相关API
-
获取切入点所在目标对象
Object targetObj =joinPoint.getTarget(); // 可以发挥反射的功能获取关于类的任何信息,例如获取类名如下 String className = joinPoint.getTarget().getClass().getName();
-
获取切入点方法的名字 getSignature();是获取到这样的信息 :修饰符+ 包名+组件名(类名) +方法名
String methodName = joinPoint.getSignature().getName()
-
获取方法上的注解
- 方法一
Signature signature = joinPoint.getSignature(); MethodSignature methodSignature = (MethodSignature) signature; Method method = methodSignature.getMethod(); if (method != null) { xxxxxx annoObj= method.getAnnotation(xxxxxx.class); } return null;
2.方法二: 上面我们已经知道了方法名和类的对象,通过反射可以获取类的内部任何信息。
// 切面所在类 Object target = joinPoint.getTarget(); String methodName = joinPoint.getSignature().getName(); Method method = null; for (Method m : target.getClass().getMethods()) { if (m.getName().equals(methodName)) { method = m; // xxxxxx annoObj= method.getAnnotation(xxxxxx.class);同上 break; } }
-
获取方法的参数
Object[] args = joinPoint.getArgs();
-
4.3 ProceedingJointPoint (只有环绕通知才能使用)
Proceedingjoinpoint 继承了 JoinPoint。是在JoinPoint的基础上暴露出 proceed 这个方法。proceed很重要,这个是aop代理链执行的方法。
环绕通知=前置+目标方法执行+后置通知,proceed方法就是用于启动目标方法执行的
5、SpringAOP切点表达式
转载 Spring AOP常用注解_@未安的博客-CSDN博客_aop 常用注解
5.1切入点表达式标准格式
动作关键字(访问修饰符 返回值 包.类/接口名.方法名(参数)异常名)
- 动作关键字:描述切入点的行为动作
execution:用于匹配方法执行的连接点;
within:用于匹配指定类型内的方法执行;
this:用于匹配当前AOP代理对象类型的执行方法;注意是AOP代理对象的类型匹配,这
样就可能包括引入接口也类型匹配;
target:用于匹配当前目标对象类型的执行方法;注意是目标对象的类型匹配,这样就
不包括引入接口也类型匹配;
args:用于匹配当前执行的方法传入的参数为指定类型的执行方法;
@within:用于匹配所以持有指定注解类型内的方法;
@target:用于匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解;
@args:用于匹配当前执行的方法传入的参数持有指定注解的执行;
@annotation:用于匹配当前执行方法持有指定注解的方法;
bean:Spring AOP扩展的,AspectJ没有对于指示符,用于匹配特定名称的Bean对象的
执行方法;
reference pointcut:表示引用其他命名切入点,只有@ApectJ风格支持,Schema风
格不支持。
5.2AOP切入点表达式通配符
AspectJ类型匹配的通配符:
*:匹配任何数量字符;
..:匹配任何数量字符的重复,如在类型模式中匹配任何数量子包;而在方法参数模式
中匹配任何数量参数。
+:匹配指定类型的子类型;仅能作为后缀放在类型模式后边。
5.3切入点表达式逻辑
&& and || or ! not
关于切入点表达式下面这篇文章讲的很细
于匹配所以持有指定注解类型内的方法;
@target:用于匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解;
@args:用于匹配当前执行的方法传入的参数持有指定注解的执行;
@annotation:用于匹配当前执行方法持有指定注解的方法;
bean:Spring AOP扩展的,AspectJ没有对于指示符,用于匹配特定名称的Bean对象的
执行方法;
reference pointcut:表示引用其他命名切入点,只有@ApectJ风格支持,Schema风
格不支持。
##### 5.2AOP切入点表达式通配符
AspectJ类型匹配的通配符:
*:匹配任何数量字符;
…:匹配任何数量字符的重复,如在类型模式中匹配任何数量子包;而在方法参数模式
中匹配任何数量参数。
+:匹配指定类型的子类型;仅能作为后缀放在类型模式后边。
##### 5.3切入点表达式逻辑
&& and || or ! not
**关于切入点表达式下面这篇文章讲的很细**
[(158条消息) SpringAOP的使用_小老虎Love的博客-CSDN博客_springaop使用](https://blog.csdn.net/tenghu8888/article/details/122444743?ops_request_misc=%7B%22request%5Fid%22%3A%22165819100916780357279424%22%2C%22scm%22%3A%2220140713.130102334..%22%7D&request_id=165819100916780357279424&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-1-122444743-null-null.142^v32^new_blog_pos_by_title,185^v2^control&utm_term=Spring使用aop&spm=1018.2226.3001.4187)