赞
踩
目录
AOP是用于简化动态代理的使用
AOP:Aspect Oriented Programming,面向切面编程。是通过预编译方式(aspectj)或者运行期动态代理(Spring)实现程序功能的统一维护的技术。
AOP是OOP(Object Oriented Programming)的技术延续,是软件开发中的一个热点,也是Spring中的一个重要内容。利用AOP可以实现对业务逻辑各个部分之间的隔离,从而使得业务逻辑各部分之间的耦合性降低,提高程序的可重用性,同时提高了开发效率。
- 面试问题:
- Spring的两大核心是什么:IoC和AOP
- 分别有什么作用
- IoC:控制反转,目的用于解耦,底层使用的技术是反射+工厂模式
- AOP:面向切面编程,目的是在不修改目标对象源码的情况下,进行功能增强,底层使用的是动态代理技术
作用:不修改源码的情况下,进行功能增强,通过动态代理实现的
优势:减少重复代码,提高开发效率,降低耦合度方便维护
比如:给功能增加日志输出, 事务管理的功能
实际上,Spring的AOP,底层是通过动态代理实现的。在运行期间,通过代理技术动态生成代理对象,代理对象方法执行时进行功能的增强介入,再去调用目标方法,从而完成功能增强。
常用的动态代理技术有:
JDK的动态代理:基于接口实现的
cglib的动态代理:基于子类实现的
Spring的AOP采用了哪种代理方式?
如果目标对象有接口,就采用JDK的动态代理技术
如果目标对象没有接口,就采用cglib技术
目标对象(Target):要代理的/要增强的目标对象。
代理对象(Proxy):目标对象被AOP织入增强后,就得到一个代理对象
连接点(JoinPoint):能够被拦截到的点,在Spring里指的是方法。能增强的方法
切入点(PointCut):要对哪些连接点进行拦截的定义。要增强的方法
通知/增强(Advice):拦截到连接点之后要做的事情。如何增强,额外添加上去的功能和能力
切面(Aspect):是切入点和通知的结合。 告诉Spring的AOP:要对哪个方法,做什么样的增强
织入(Weaving):把增强/通知 应用到 目标对象来创建代理对象的过程
有哪些事情需要我们完成的:
目标类及里边的方法:完成功能的目标类。
编写切面类,里边配置:
切入点:要对谁进行增强
通知:要做什么样的增强
有哪些事情是由SpringAOP完成的:
代理对象:由SpringAOP根据我们的配置,帮我们生成的
织入:根据切面配置生成代理对象的过程
需求
统计Service层每个方法的执行耗时
分析
目标类:Service层的所有类
编写切面类:
切入点:Service层里所有的方法
通知:计算方法执行耗时,打印出来
实现
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-aop</artifactId>
- </dependency>
Demo01AopAspect
类上加@Component
类上加@Aspect 声明一下这个类是切面类
- package com.itheima.aop;
-
- import org.aspectj.lang.ProceedingJoinPoint;
- import org.aspectj.lang.annotation.Around;
- import org.aspectj.lang.annotation.Aspect;
- import org.springframework.stereotype.Component;
-
-
- @Aspect
- @Component
- public class Demo01AopAspect {
-
- @Around("execution(public * com.itheima.service.impl.*.*(..))")
- public Object executeTime(ProceedingJoinPoint pjp) throws Throwable {
-
- long start = System.currentTimeMillis();
-
- //调用目标方法
- Object res = pjp.proceed();
-
- long end = System.currentTimeMillis();
-
- System.out.println(pjp.getSignature() + "方法耗时:" + (end - start) + "毫秒");
-
- return res;
- }
-
- }
测试
打开浏览器访问 http://localhost:8080/depts
可看到控制台输出了执行时间
AOP的使用步骤:
添加依赖:spring-boot-starter-aop
编写切面类:类上要加 @Component
, @Aspect
类里编写通知方法:要额外增加上去的功能
方法上加注解 @Around("切入点表达式")
Spring的AOP支持以下5种通知类型:
- package com.itheima.aop;
-
- import org.aspectj.lang.ProceedingJoinPoint;
- import org.aspectj.lang.annotation.*;
- import org.springframework.stereotype.Component;
-
- /**
- * 当前类是一个切面配置类:
- * 类上加@Component:当前类对象必须交给Spring进行管理。不给Spring管理,Spring就不能增强
- * 类上加@Aspect:告诉Spring,这个类是切面配置类
- * 需求:
- * 对Service层的每个方法统计执行时间
- */
- @Aspect
- @Component
- public class Demo01Aspect {
-
- /**
- * 方法上加的注解:@Around("切入点表达式"),用于选择对哪些方法进行增强
- * @param pjp 目标方法的信息,被增强的那个方法
- * @return
- */
- @Around("execution(public * com.itheima.service.*.*(..))")
- public Object executeTime(ProceedingJoinPoint pjp) throws Throwable {
-
- long start = System.currentTimeMillis();
- //调用目标方法,得到返回值
- Object res = pjp.proceed();
-
- long end = System.currentTimeMillis();
- System.out.println("目标方法执行耗时:" + (end - start) + "毫秒");
- return res;
- }
-
- /**
- * 需求:要在Service的每个方法执行之前,先输出一句话:前置通知执行了
- * 分析:
- * 切入点:要对谁增强,Service的所有方法
- * 通知:要额外增加的功能,输出“前置通知执行了”
- * 通知类型:要什么时候进行增强,在目标方法执行之前
- */
- @Before("execution(public * com.itheima.service.*.*(..))")
- public void before(){
- System.out.println("前置通知执行了");
- }
-
- /**
- * 需求:要在Service的每个方法执行抛出异常时,输出一句话:异常通知执行了
- * 分析:
- * 切入点:要对谁增强,Service的所有方法
- * 通知:要额外增加的功能,输出“异常通知执行了”
- * 通知类型:要什么时候进行增强,在目标方法执行抛出异常之后
- */
- @AfterThrowing("execution(public * com.itheima.service.*.*(..))")
- public void afterThrowing(){
- System.out.println("异常通知执行了");
- }
-
- /**
- * 需求:要在Service的每个方法正常执行完成之后,输出一句话:后置通知执行了
- * 分析:
- * 切入点:要对谁增强,Service的所有方法
- * 通知:要额外增加的功能,输出“后置通知执行了”
- * 通知类型:要什么时候进行增强,在目标方法正常执行完成之后
- */
- @AfterReturning("execution(public * com.itheima.service.*.*(..))")
- public void afterReturning(){
- System.out.println("后置通知执行了");
- }
-
- /**
- * 需求:要在Service每个方法执行之后,必须执行,输出一句话:最终通知执行了
- * 分析:
- * 切入点:要对谁增强,Service的所有方法
- * 通知:要额外增加的功能,输出“最终通知执行了”
- * 通知类型:要什么时候进行增强,在目标方法执行之后,必须执行
- */
- @After("execution(public * com.itheima.service.*.*(..))")
- public void after(){
- System.out.println("最终通知执行了");
- }
- }
不同通知方法里,获取被调用的目标方法:
可以直接给通知方法,直接加形参 JoinPoint
如果是环绕通知方法,则要加形参 ProceedingJoinPoint
,继承了JoinPoint
JoinPoint常用方法:
getArgs()
:获取调用方法时的实参值
getTarget()
:获取被调用的目标对象(原始目标对象)
getThis()
:获取当前代理对象
getSignature()
:获取被调用的方法信息。Signature
对象的方法:
getName()
:获取被调用的方法名。比如:queryAllDepts
getDeclaringType()
:获取被调用方法所属的类Class
getDeclaringTypeName()
:获取被调用方法所属的类的全限定类名
toLongString()
:获取方法的完整名称。
比如:public java.util.List com.itheima.service.impl.DeptServiceImpl.queryAllDepts()
toShortString
:获取方法的简短名称。
比如:DeptServiceImpl.queryAllDepts()
toString
:获取方法信息。
比如:List com.itheima.service.impl.DeptServiceImpl.queryAllDepts()
- package com.itheima.aop;
-
- import org.aspectj.lang.JoinPoint;
- import org.aspectj.lang.ProceedingJoinPoint;
- import org.aspectj.lang.Signature;
- import org.aspectj.lang.annotation.*;
- import org.springframework.stereotype.Component;
-
- import java.util.Arrays;
-
- /**
- * 当前类是一个切面配置类:
- * 类上加@Component:当前类对象必须交给Spring进行管理。不给Spring管理,Spring就不能增强
- * 类上加@Aspect:告诉Spring,这个类是切面配置类
- * 需求:
- * 对Service层的每个方法统计执行时间
- * @author liuyp
- * @since 2023/08/25
- */
- @Aspect
- @Component
- public class Demo01Aspect {
-
- /**
- * 方法上加的注解:@Around("切入点表达式"),用于选择对哪些方法进行增强
- * @param pjp 目标方法的信息,被增强的那个方法
- * @return
- */
- @Around("execution(public * com.itheima.service.*.*(..))")
- public Object executeTime(ProceedingJoinPoint pjp) throws Throwable {
-
- long start = System.currentTimeMillis();
- //调用目标方法,得到返回值
- Object res = pjp.proceed();
-
- long end = System.currentTimeMillis();
- System.out.println("目标方法执行耗时:" + (end - start) + "毫秒");
- return res;
- }
-
- /**
- * 需求:要在Service的每个方法执行之前,先输出一句话:前置通知执行了
- * 分析:
- * 切入点:要对谁增强,Service的所有方法
- * 通知:要额外增加的功能,输出“前置通知执行了”
- * 通知类型:要什么时候进行增强,在目标方法执行之前
- */
- @Before("execution(public * com.itheima.service.*.*(..))")
- public void before(JoinPoint joinPoint){
- //调用目标方法时的实参值
- Object[] args = joinPoint.getArgs();
- //调用的目标类对象
- Object target = joinPoint.getTarget();
- //当前代理类对象
- Object aThis = joinPoint.getThis();
- //调用的目标方法
- Signature signature = joinPoint.getSignature();
- System.out.println("前置通知执行了, 目标类是:" + target.getClass().getName() + ", 目标方法:" + signature + ", 方法实参是:" + Arrays.toString(args));
- }
-
- /**
- * 需求:要在Service的每个方法执行抛出异常时,输出一句话:异常通知执行了
- * 分析:
- * 切入点:要对谁增强,Service的所有方法
- * 通知:要额外增加的功能,输出“异常通知执行了”
- * 通知类型:要什么时候进行增强,在目标方法执行抛出异常之后
- */
- @AfterThrowing("execution(public * com.itheima.service.*.*(..))")
- public void afterThrowing(JoinPoint joinPoint){
- System.out.println("异常通知执行了, joinPoint = " + joinPoint);
- }
-
- /**
- * 需求:要在Service的每个方法正常执行完成之后,输出一句话:后置通知执行了
- * 分析:
- * 切入点:要对谁增强,Service的所有方法
- * 通知:要额外增加的功能,输出“后置通知执行了”
- * 通知类型:要什么时候进行增强,在目标方法正常执行完成之后
- */
- @AfterReturning("execution(public * com.itheima.service.*.*(..))")
- public void afterReturning(JoinPoint joinPoint){
- System.out.println("后置通知执行了, joinPoint = " + joinPoint);
- }
-
- /**
- * 需求:要在Service每个方法执行之后,必须执行,输出一句话:最终通知执行了
- * 分析:
- * 切入点:要对谁增强,Service的所有方法
- * 通知:要额外增加的功能,输出“最终通知执行了”
- * 通知类型:要什么时候进行增强,在目标方法执行之后,必须执行
- */
- @After("execution(public * com.itheima.service.*.*(..))")
- public void after(JoinPoint joinPoint){
- System.out.println("最终通知执行了, joinPoint = " + joinPoint);
- }
- }
通知类型:
@Around:环绕通知。最灵活的通知方式,必须掌握的方式
@Before:前置通知。通知方法在 目标方法执行之前 先执行
@AfterReturning:返回通知。通知方法在 目标方法正常执行之后 再执行
@AfterThrowing:异常通知。通知方法在 目标方法抛出异常之后 再执行
@After:最终通知/后置通知。通知方法在 目标方法执行之后 一定会执行,无论是否有异常
execution
用于根据方法的名称进行模糊匹配的
语法:execution(权限修饰符 返回值类型 全限定类名.方法名(形参列表))
*
:可使用通配符来表示模糊匹配
..
:用于形参列表里,表示任意个任意形参; 用于其它地方,表示任意层级的package包
权限修饰符写public,通常省略不写
示例:
public void com.itheima.service.XxService.delete(Integer)
void com.itheima.service.XxService.delete(Integer)
* com.itheima.service.XxService.delete(Integer)
* com.itheima.service.*.delete(Integer)
* com.itheima..*.delete(Integer)
* com.itheima..*.*(Integer)
* com.itheima..*.*(Integer, String)
* com.itheima..*.*(Integer, *)
* com.itheima..*.*(..)
- package com.itheima.aop;
-
- import org.aspectj.lang.annotation.After;
- import org.aspectj.lang.annotation.Aspect;
- import org.aspectj.lang.annotation.Before;
- import org.springframework.stereotype.Component;
-
- /**
- * 切入点表达式:
- * 语法:`execution(权限修饰符 返回值类型 全限定类名.方法名(形参列表))`
- * 详解:
- * 权限修饰符:通常是`public`,可省略不写
- * 返回值类型:可以精确写,也可以用通配符*
- * execution(String com.itheima.service.*.*(..))
- 表示要选中com.itheima.service下任意类的、返回值是String类型的方法
- * execution(void com.itheima.service.*.*(..))
- 表示要选中com.itheima.service下任意类的、返回值是void类型的方法
- * execution(* com.itheima.service.*.*(..)),
- 表示要选中com.itheima.service下任意类的、返回值是任意类型的方法
- * 全限定类名:
- * 可以精确写,execution(* com.itheima.service.DeptService.queryAllDepts(..))
- * 表示选中com.itheima.service.DeptService类下名称为queryAllDepts方法
- * 可以用通配符,execution(* com.itheima.service.*.queryAllDepts(..))
- * 表示选中com.itheima.service.包下任意类下名称为queryAllDepts方法
- * 可以使用..表示后代,execution(* com.itheima..*.*(..))
- * 表示选中com.itheima包下所有的类(包括这个包里的类,以下所有下级包里的类)的所有方法
- * 方法名:可以精确写,也可以用通配符*
- * 如果写:execution(* com.itheima..*.queryAllDepts(..))
- * 表示选中com.itheima包下所有类里queryAllDepts方法
- * 如果写:execution(* com.itheima..*.*(..))
- * 表示选中com.itheima包下所有类里任意方法
- * 形参列表:
- * 可以精确写,
- * execution(* com.itheima..*.*(String))
- 表示选中com.itheima包下所有类的任意方法,但是要求方法有1个形参必须是String
- * execution(* com.itheima..*.*(String, Integer))
- 表示选中com.itheima包下所有类的任意方法,但是要求方法有2个形参按顺序必须是String、Integer
- * 也可以用通配符*
- * execution(* com.itheima..*.*(String, *))
- 表示选中com.itheima包下所有类的任意方法,但是要求方法有2个形参按顺序必须是String、任意类型
- * 任意个任意类型:..
- * execution(* com.itheima..*.*(..))
- 表示选中com.itheima包下任意类里任意方法,方法的形参个数和类型都不限制
- * 示例:execution(* *..*.*(..)) 表示选择任意包下、任意类的任意方法,形参不限,返回值不限。不要用
- * @author liuyp
- * @since 2023/08/25
- */
- @Aspect
- @Component
- public class Demo02Aspect {
-
- @Before("execution(* com.itheima.service.*.*(..))")
- public void before(){
- System.out.println("=====Demo02Aspect.before执行了======");
- }
-
-
- @After("execution(* com.itheima.service.*.*(..))")
- public void after(){
- System.out.println("=====Demo02Aspect.after执行了======");
- }
- }
@annotation
根据注解选择要增强的方法
语法:@annotation(注解的全限定类名)
,会选择所有加了指定注解的方法,进行增强
示例:
自定义注解
-
- import java.lang.annotation.ElementType;
- import java.lang.annotation.Retention;
- import java.lang.annotation.RetentionPolicy;
- import java.lang.annotation.Target;
-
- @Target(ElementType.METHOD)
- @Retention(RetentionPolicy.RUNTIME)
- public @interface MyLog {
- }
2.创建切面配置类
- package com.itheima.aop;
-
- import org.aspectj.lang.annotation.Aspect;
- import org.aspectj.lang.annotation.Before;
- import org.springframework.stereotype.Component;
-
-
- @Aspect
- @Component
- public class Demo03Aspect {
-
- @Before("@annotation(com.itheima.aop.MyLog)")
- public void saveLogs(){
- System.out.println("========保存日志信息======");
- }
- }
3.给DeptServiceImpl的queryAllDepts方法上加注解@MyLog
语法:
定义公用的切入点表达式:在切面类里创建一个方法,方法上加注解@Pointcut("公用的切入点表达式")
引用公用的切入点表达式:
完整用法:Before("com.itheima.aop.Demo02Aspect.pc()")
简写形式:Before("pc()")
,仅适合于 切入点方法 和 通知方法在同一个类里
- package com.itheima.aop;
-
- import org.aspectj.lang.annotation.After;
- import org.aspectj.lang.annotation.Aspect;
- import org.aspectj.lang.annotation.Before;
- import org.aspectj.lang.annotation.Pointcut;
- import org.springframework.stereotype.Component;
-
- /**
- * 如果要抽取公用的切入点表达式:
- * 使用注解@Pointcut("公用的切入点表达式")
- * 把注解加在方法上
- * 当需要使用切入点表达式时:
- * 完整的用法:@Before("com.itheima.aop.Demo02Aspect.pc()")
- * 如果调用的同一个类里的切入点表达式方法,可以简写@Before("方法名()")。
- * @author liuyp
- * @since 2023/08/25
- */
- @Aspect
- @Component
- public class Demo02Aspect {
-
- /**这个方法仅仅作为切入点表达式的载体*/
- @Pointcut("execution(* com.itheima.service.*.*(..))")
- public void pc(){}
-
- // @Before("execution(* com.itheima.service.*.*(..))")
- // @Before("com.itheima.aop.Demo02Aspect.pc()")
- @Before("pc()")
- public void before(){
- System.out.println("=====Demo02Aspect.before执行了======");
- }
-
-
- // @After("execution(* com.itheima.service.*.*(..))")
- @After("pc()")
- public void after(){
- System.out.println("=====Demo02Aspect.after执行了======");
- }
- }
切入点表达式:用于 选中要增强的方法
语法:
根据方法名选择:execution(权限修饰符 返回值类型 全限定类名.方法名(形参列表))
根据注解选择:@annotation(注解的全限定类名)
如何抽取公用切入点表达式
在切面类里定义一个方法,方法上加@Pointcut("切入点表达式")
需要使用切入点表达式时
简写:这个方法和当前通知方法在 同一类里,可以简写 @Before("方法名()")
完整:这个方法和当前通知方法不在同一类,完整写@Before("com.itheima.aspect.Demo01Aspect.pc()")
- package com.itheima.aop;
-
- import org.aspectj.lang.JoinPoint;
- import org.aspectj.lang.ProceedingJoinPoint;
- import org.aspectj.lang.annotation.*;
- import org.springframework.core.annotation.Order;
- import org.springframework.stereotype.Component;
-
-
- @Aspect
- //@Order(150)
- @Component
- public class Demo04Aspect {
- @Pointcut("execution(* com.itheima.service.*.*(..))")
- public void pc(){}
-
- @Before("pc()")
- public void before(){
- System.out.println("===Demo04Aspect.before===");
- }
-
- @AfterReturning("pc()")
- public void afterReturning(){
- System.out.println("===Demo04Aspect.afterReturning===");
- }
-
- @AfterThrowing("pc()")
- public void afterThrowing(){
- System.out.println("===Demo04Aspect.afterThrowing===");
- }
-
- @After("pc()")
- public void after(){
- System.out.println("===Demo04Aspect.after===");
- }
-
- @Around("pc()")
- public Object around(ProceedingJoinPoint pjp) throws Throwable {
-
- System.out.println("===Demo04Aspect.around前===");
-
- //调用目标方法
- Object res = pjp.proceed();
-
- System.out.println("===Demo04Aspect.around后===");
-
- return res;
- }
- }
- package com.itheima.aop;
-
- import org.aspectj.lang.ProceedingJoinPoint;
- import org.aspectj.lang.annotation.*;
- import org.springframework.core.annotation.Order;
- import org.springframework.stereotype.Component;
-
-
- @Aspect
- //@Order(100)
- @Component
- public class Demo05Aspect {
- @Pointcut("execution(* com.itheima.service.*.*(..))")
- public void pc(){}
-
- @Before("pc()")
- public void before(){
- System.out.println("===Demo05Aspect.before===");
- }
-
- @AfterReturning("pc()")
- public void afterReturning(){
- System.out.println("===Demo05Aspect.afterReturning===");
- }
-
- @AfterThrowing("pc()")
- public void afterThrowing(){
- System.out.println("===Demo05Aspect.afterThrowing===");
- }
-
- @After("pc()")
- public void after(){
- System.out.println("===Demo05Aspect.after===");
- }
-
- @Around("pc()")
- public Object around(ProceedingJoinPoint pjp) throws Throwable {
-
- System.out.println("===Demo05Aspect.around前===");
-
- //调用目标方法
- Object res = pjp.proceed();
-
- System.out.println("===Demo05Aspect.around后===");
-
- return res;
- }
- }
需求
将tlias案例中增、删、改相关接口的操作日志记录到数据库表中。
就是当访问部门管理和员工管理当中的增、删、改相关功能接口时,需要详细的操作日志,并保存在数据表中,便于后期数据追踪。
操作日志信息包含:操作人、操作时间、执行方法的全类名、执行方法名、方法运行时参数、返回值、方法执行时长
准备
把《资料\AOP-日志案例\tlias-web-management2》拷贝到一个不含中文、空格、特殊字符的目录里,使用idea打开项目,启动引导类
再启动tlias的nginx
打开浏览器访问 http://localhost:90
SQL脚本
- use tlias;
- -- 操作日志表
- create table operate_log(
- id int unsigned primary key auto_increment comment 'ID',
- operate_user int unsigned comment '操作人ID',
- operate_time datetime comment '操作时间',
- class_name varchar(100) comment '操作的类名',
- method_name varchar(100) comment '操作的方法名',
- method_params varchar(1000) comment '方法参数',
- return_value varchar(2000) comment '返回值',
- cost_time bigint comment '方法执行耗时, 单位:ms'
- ) comment '操作日志表';
实体类
- package com.itheima.pojo;
-
- import lombok.AllArgsConstructor;
- import lombok.Data;
- import lombok.NoArgsConstructor;
-
- import java.time.LocalDateTime;
-
- @Data
- @NoArgsConstructor
- @AllArgsConstructor
- public class OperateLog {
- private Integer id; //ID
- private Integer operateUser; //操作人ID
- private LocalDateTime operateTime; //操作时间
- private String className; //操作类名
- private String methodName; //操作方法名
- private String methodParams; //操作方法参数
- private String returnValue; //操作方法返回值
- private Long costTime; //操作耗时
- }
Mapper接口
- package com.itheima.mapper;
-
- import com.itheima.pojo.OperateLog;
- import org.apache.ibatis.annotations.Insert;
- import org.apache.ibatis.annotations.Mapper;
-
- @Mapper
- public interface OperateLogMapper {
-
- //插入日志数据
- @Insert("insert into operate_log (operate_user, operate_time, class_name, method_name, method_params, return_value, cost_time) " +
- "values (#{operateUser}, #{operateTime}, #{className}, #{methodName}, #{methodParams}, #{returnValue}, #{costTime});")
- void insert(OperateLog log);
-
- }
分析:
选择AOP技术实现:
如果不使用AOP,就需要修改源码,给大量的方法增加保存日志的代码。代码重复,日志和业务功能耦合到了一起
希望:不修改源码的情况下,给Service层的方法进行增强,就使用AOP
要使用AOP技术:
切入点:对哪些方法增强
哪个方法需要保存日志,就在哪个方法上加一个自定义注解
使用注解切入点表达式@annotation(自定义注解)
通知:
通知类型:使用@Around
环绕通知
通知方法:获取当前用户、当前时间、当前方法的类名和方法名、方法的实参、返回值,运行耗时,把这些信息保存到日志表
- package com.itheima.aspect;
-
- import com.itheima.mapper.OperateLogMapper;
- import com.itheima.pojo.OperateLog;
- import com.itheima.util.JwtUtils;
- import io.jsonwebtoken.Claims;
- import org.aspectj.lang.ProceedingJoinPoint;
- import org.aspectj.lang.Signature;
- import org.aspectj.lang.annotation.Around;
- import org.aspectj.lang.annotation.Aspect;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Component;
-
- import javax.servlet.http.HttpServletRequest;
- import java.time.LocalDateTime;
- import java.util.Arrays;
-
-
- @Aspect
- @Component
- public class OperateLogAspect {
-
- @Autowired
- private OperateLogMapper logMapper;
-
- @Autowired
- private HttpServletRequest request;
-
- @Around("execution(* com.itheima.service.*.*(..))")
- public Object logSave(ProceedingJoinPoint pjp) throws Throwable {
- long start = System.currentTimeMillis();
- //调用目标方法,得到返回值
- Object res = pjp.proceed();
- long end = System.currentTimeMillis();
-
- //保存日志信息
- OperateLog ol = new OperateLog();
- //填充日志数据-操作人id,当前登录的用户。客户端每次请求都会携带token到服务端,只要从请求里获取token就能得到当前用户
- String token = request.getHeader("token");
- if (token != null && !"".equals(token)) {
- Claims claims = JwtUtils.parseJWT(token);
- Integer userId = claims.get("id", Integer.class);
- ol.setOperateUser(userId);
- }
- //填充日志数据-操作时间
- ol.setOperateTime(LocalDateTime.now());
- //填充日志数据-目标类名
- Signature signature = pjp.getSignature();
- String className = signature.getDeclaringTypeName();
- ol.setClassName(className);
- //填充日志数据-目标方法名
- ol.setMethodName(signature.getName());
- //填充日志数据-方法的实参
- Object[] args = pjp.getArgs();
- ol.setMethodParams(Arrays.toString(args));
- //填充日志数据-方法的返回值
- ol.setReturnValue(res.toString());
- //填充日志数据-操作耗时
- ol.setCostTime(end-start);
- //保存到数据库里
- logMapper.insert(ol);
-
- String remoteAddr = request.getRemoteAddr();
- System.out.println("客户端ip:" + remoteAddr);
- return res;
- }
- }
AOP是什么:面向切面编程,底层是动态代理
AOP的作用:用于在不修改目标对象源码的情况下,进行功能增强
AOP的底层:
JDK的动态代理:如果有接口,就使用JDK的
cglib动态代理:如果没有接口,就使用cglib
AOP的几个概念:
切入点PointCut:要增强的方法
通知Advice:要如何增强
切面Aspect:切入点 + 通知。表示 要对哪些方法 做什么样的增强
AOP的使用:
添加依赖坐标 spring-boot-starter-aop
创建切面类:类上加@Component @Aspect;类里写通知方法,通知方法加注解配置切入点表达式
@Aspect @Order(整数) //整数是排序号,值越小,执行的越早 @Component public class DemoAspect{ @PointCut("切入点表达式") public void pc(){} //环绕通知方法:固定写法 @Around("pc()") public Object method(ProceedingJoinPoint pjp){ //前边写的前置增强 //调用目标方法 Object res = pjp.proceed(); //后边写的后置增强 return res; } //其它通知方法:返回值void,可以无参,方法名随便。 如果需要用到目标方法,就加形参JoinPoint @Before("切入点表达式") public void before(){ } @AfterThrowing("切入点表达式") public void before(){ } @AfterReturning("切入点表达式") public void before(){ } @After("切入点表达式") public void before(){ } }
通知类型:
@Before:通知方法在 目标方法执行之前 先执行
@AfterReturning:通知方法在 目标方法正常执行之后 再执行
@AfterThrowing:通知方法在 目标方法抛出异常之后 再执行
@After:通知方法在 目标方法执行之后,必定会执行,无论有没有异常
切入点表达式:这种表达式是用于选择 要增强的方法
根据方法名选择:execution(权限修饰符 返回值类型 全限定类名.方法名(形参类型列表))
根据注解选择:@annotation(注解的全限定类名)
多个切面的执行顺序:
默认按 切面类的全限定类名 顺序执行
如果要设置执行顺序,就在切面类上加 @Order(整数),整数越小,执行的越早
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。