字节一面关于Spring AOP的问题让我对Spring AOP有了深入了解的想法,现在来整理一下对Spring AOP的理解
class A{
@Logxxx
public void methodA(){
methodB();
}
@Logxxx
public void methodB(){
}
}
问题:
A类中有两个方法 methodA和methodB,方法是都有自定义注解@Logxxx,@Logxxx的作用是在方法执行之后输出一段日志,如果执行了方法methodA会输出几次日志信息?
结论:
只有方法methodA执行之后输出一段log日志。
原因:
因为调用方法methodA的时候,因为加了@Logxxx日志,spring aop会生成代理对象,但是在执行methodB方法的时候使用的对象不是代理对象,所以不会进行增强。
我的理解:
对aop的理解:
1.什么是aop: 面向切面编程,通过预编译或者运行时动态编译实现程序功能增强的一种技术。
2. aop可以拦截指定的方法并对方法进行增强,不需要在代码中加入需求代码,使业务代码与非业务代码分离。
3. AOP 采取横向抽取机制(动态代理),取代了传统纵向继承机制的重复性代码,其应用主要体现在事务处理、日志管理、权限控制、异常处理等方面。
4. 通俗的说就是在代码执行前后进行某些处理比如日志、权限校验等
aop术语:
对象术语:描述aop中各个对象的部分
连接点(Join Point):所有符合条件的切点的集合,可能有一个或者多个我们成为连接点。
切入点/切点(Pointcut):切点是指通知(Advice)所要织入(Weaving)的具体位置,满足我们条件的目标方法。
比如使用自定义注解,切点就是这个注解所标志的地方。比如我们指定规则,以find开头的才执行我们自定义通 知,那么这些满足条件的以find开头的方法就是切点。
切面 (Aspect):切点和通知的组合叫法。连接点是我们没有定义那个find开头规则时,满足条件的全部的方法。
通知/增强(Advice):我们编写的希望在aop时执行的方法,通常希望在目标方法前后执行。
五种通知:
前置通知(Before):在目标方法被调用前执行的行为。
后置通知(After):在目标方法执行完成后执行的行为,不能获取到方法的输出。
后置返回通知(After-returning):在目标方法成功执行后执行的行为,可以获取到方法的输出。
后置异常通知(After-throwing):在目标方法抛出异常后执行的行为。
环绕通知(Around):包裹了目标方法,可以在其执行前后执行自定义的行为。
行为术语:aop实现过程中涉及的具体行为
引入(Introduction):向现有类中添加新的方法和属性,我们可以把切面的属性引入到目标类。
织入(Weaving):织入指的是将切面应用到目标对象,在这个过程中会生成新的代理对象。
织入三种时期
编译期:在目标类被编译时进行织入,需要依赖于特殊的编译器。AspectJ 的织入编译器就采用的这种方式。
类加载期:在目标类被加载到 JVM 时进行织入,需要依赖于特殊的类加载器。这种方式会在目标类被引入应用前增强目标类的字节码。
运行期:在应用运行的某一具体时刻进行织入。在这种情况下,AOP 容器会为目标对象动态地创建一个代理对象,Spring AOP 就采用的这种方式。
具体例子理解:
被增强的方法:王老师的语文课
目标方法所在类:王老师
通知(前置通知):做某事前擦黑板,重点是擦黑板这一行为
连接点:老师上课前的时间,并不确定是哪些老师上哪门课前
切点:王老师上语文课前这一时间点
切面:在王老师上语文课前擦黑板
Spring AOP的实现机制
spring 是通过动态代理和动态字节码实现AOP的。
动态代理:JDK动态代理和CGLIB动态代理。
1. JDK动态代理:Spring AOP的首选方法,当目标对象实现一个接口时可以使用JDK动态代理。
2. CGLIB代理:目标对象如果没有实现接口,可以使用CGLIB代理,原理是继承实现的所以不能代理final,static修饰的目标类,并且方法不能用private修饰。
我在项目中的应用-自定义log注解
//自定义注解
@Retention(RetentionPolicy.RUNTIME) //该注解用来修饰注解的生命周期
@Target(value = {ElementType.METHOD})// 表示该注解的作用范围
@Documented //有该注解的注解生成javadoc的时候是否会被记录
public @interface Log{
int level() default 2;
}
//定义切面
@Component
@Aspect
@RequiredArgsConstructor
@Slf4j
public class LogAspect {
//切点
@Pointcut("@annotation(com.xx.xx.xx.Log)")
public void printLog(){
}
/**
* 输出日志
* @param joinPoint
*/
@After(value = "printLog()")
public void doAfterPrintLog(JoinPoint joinPoint){
//获取参数
Object[] args = joinPoint.getArgs();
// 获取方法名
String methodName = joinPoint.getSignature().getName();
// 反射获取目标类
Class<?> targetClass = joinPoint.getTarget().getClass();
// 拿到方法对应的参数类型
Class<?>[] parameterTypes = ((MethodSignature) joinPoint.getSignature()).getParameterTypes();
// 根据类、方法、参数类型(重载)获取到方法的具体信息
Method objMethod = null;
try {
objMethod = targetClass.getMethod(methodName, parameterTypes);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
// 拿到方法定义的注解信息
PreAuthCheck annotation = objMethod.getDeclaredAnnotation(Log.class);
System.out.println("日志级别:"annotation.level());
}
}