前言
又是一年疫情,让大家在本就不平缓的生活里,变得更加坎坎坷坷。好好保护自己,保护身边的陌生人,大家记得戴口罩呀!生活再苦,也别忘记微笑。
正文
一、首先说说什么是AOP?
AOP:Aspect Oriented Programming,面向切面编程
通过预编译,在服务运行期间动态代理实现功能的一种方式,也是一种维护。
在Spring中,也是一个非常重要的功能。
AOP 利用一种称为横切的技术,剖开对象的封装,并将影响多个类的公共行为封装到一个可重用模块,组成一个切面,即 Aspect 。
切面就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度,利于可操作性和可维护性。
实现AOP的方式,一共有两种:
1、采用动态代理技术,利用拦截方法的方式,对该方法进行装饰,以取代原有对象行为的执行;
2、 采用静态织入的方式,引入特定的语法创建"切面",从而使得编译器可以在编译期间织入有关"切面"的代码。
AOP相关名词及解释
切面(Aspect)、连接点(Join point)、通知(Advice)、切点(Pointcut)、引入(Introduction)、目标对象(Target Object)、AOP代理(AOP Proxy)、织入(Weaving)
切面(Aspect)
横切关注点的模块化(跨越应用程序多个模块的功能,比如 日志功能),这个关注点实现可能另外横切多个对象。
连接点(Join point)
连接点是在应用执行过程中能够插入切面的一个点。这个点可以是类的某个方法调用前、调用后、方法抛出异常后等。切面代码可以利用这些点插入到应用的正常流程之中,并添加行为。
通知(Advice)
在特定的连接点,AOP框架执行的动作。Spring AOP 提供了5种类型的通知:
1、前置通知(Before):在目标方法被调用之前调用通知功能。
2、后置通知(After):在目标方法完成之后调用通知,无论该方法是否发生异常。
3、后置返回通知(After-returning):在目标方法成功执行之后调用通知。
4、后置异常通知(After-throwing):在目标方法抛出异常后调用通知。
5、环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。
切点(Pointcut)
指定一个通知将被引发的一系列连接点的集合。AOP 通过切点定位到特定的连接点。切点和连接点不是一对一的关系,一个切点匹配多个连接点,切点通过 org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条件。每个类都拥有多个连接点,例如 ArithmethicCalculator类的所有方法实际上都是连接点。
引入(Introduction)
添加方法或字段到被通知的类。 Spring允许引入新的接口到任何被通知的对象。例如,你可以使用一个引入使任何对象实现 IsModified接口,来简化缓存。Spring中要使用Introduction, 可有通过DelegatingIntroductionInterceptor来实现通知,通过DefaultIntroductionAdvisor来配置Advice和代理类要实现的接口
目标对象(Target Object)
包含连接点的对象。也被称作被通知或被代理对象。
AOP代理(AOP Proxy)
AOP框架创建的对象,包含通知。 在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。
织入(Weaving)
织入描述的是把切面应用到目标对象来创建新的代理对象的过程。 Spring AOP 的切面是在运行时被织入,原理是使用了动态代理技术。Spring支持两种方式生成代理对象:JDK动态代理和CGLib,默认的策略是如果目标类是接口,则使用JDK动态代理技术,否则使用Cglib来生成代理。
二、使用动态代理(自定义注解)的方式,完成日志记录
话不多说,直接上干货!
1、引入maven
<!--aop-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2、在这个配置中,就可以指定注解的参数,一般来说日志我只记录增删改操作,所以我用了logType,来区分是否是访问操作。
/**
* 日志 自定义注解
*/
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SystemLogAop {
//日志名称
String logName() default "";
//日志类型 0页面跳转 1操作
String logType() default "1";
}
3、这里就是记录日志的具体方法,方法最后面,调用相应的数据库保存接口。
为了复用切点,建议使用@Pointcut来定义切点,然后在 @Around、@After等注解中引用定义好的切点
获取IP地址的这部分,可能会存在一些问题,这里只用HttpServletRequest,写简单的获取IP地址的方式。
@Aspect
@Component
public class SystemLogAspect {
/**
* 切点
*/
@Pointcut("@annotation(SystemLogAop完整路径)")
public void logPointcut(){}
/**
* 环绕触发
*/
@Around("logPointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
RequestAttributes ra = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes sra = (ServletRequestAttributes)ra;
HttpServletRequest request = sra.getRequest();
Map<String, Object> map = new HashMap<>();
//操作人person
map.put("userName","-");
//请求IP
String requestIp = request.getRemoteAddr();
if(EmptyUtil.isEmpty(requestIp)){
requestIp = "-";
}
map.put("ip",requestIp);
//请求时间
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String requestTime = simpleDateFormat.format(new Date());
map.put("createTime",requestTime);
//获取日志名字
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
SystemLogAop systemLogAop = method.getAnnotation(SystemLogAop.class);
String logName = systemLogAop.logName();
map.put("logName",logName);
//获取日志类型
String logType = systemLogAop.logType();
map.put("logType",logType);
//获取请求地址
String pathName = request.getRequestURI();
if(EmptyUtil.isEmpty(pathName)){
pathName = "-";
}
map.put("pathName",pathName);
//获取输入参数
Map argsMap = request.getParameterMap();
map.put("args",argsMap);
//执行完方法的返回值
Object result = joinPoint.proceed();
map.put("result",result);
//保存日志
//保存数据库的操作。。。
return result;
}
}
4、下面就是如何使用,只用作参考,具体写在要记录的方法上,我一般是写在Controller中。
总结
使用自定义注解方式的日志功能,大致就是这样了,也可以使用XML的方式来完成AOP的配置,这里就不写这种方式了,个人比较喜欢用注解,省事为主,能干活就行!哈。
如果有错误,请大家指出,我会进行修改。
我是左小涩,一个独自在大城市努力的年轻人。
对您有帮助的话,希望献上您的【三连】呦!