Spring IOC 及其 AOP是其两大核心功能,本篇介绍下AOP的相关实际知识和实际应用。下面先简单介绍下aop的概念和基础使用。
一、基本介绍
AOP(Aspect Oriented Programming),即面向切面编程,可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。它利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。
使用"横切"技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事物。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。业务处理的代码完全不知道他会被代理,他也不清楚自己在执行前后会发生什么事情。核心关注点和横切关注点 是完全不相干的两种的处理方式。
AOP核心概念
1、横切关注点
对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点
2、切面(aspect)
类是对物体特征的抽象,切面就是对横切关注点的抽象
3、连接点(joinpoint)
被拦截到的点,因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器
4、切入点(pointcut)
对连接点进行拦截的定义
5、通知(advice)
所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类
6、目标对象
代理的目标对象
7、织入(weave)
将切面应用到目标对象并导致代理对象创建的过程
8、引入(introduction)
在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段
Spring对AOP的支持
Spring中AOP代理由Spring的IOC容器负责生成、管理,其依赖关系也由IOC容器负责管理。因此,AOP代理可以直接使用容器中的其它bean实例作为目标,这种关系可由IOC容器的依赖注入提供。Spring创建代理的规则为:
1、默认使用Java动态代理来创建AOP代理,这样就可以为任何接口实例创建代理了
2、当需要代理的类不是代理接口的时候,Spring会切换为使用CGLIB代理,也可强制使用CGLIB
AOP编程其实是很简单的事情,纵观AOP编程,程序员只需要参与三个部分:
1、定义普通业务组件
2、定义切入点,一个切入点可能横切多个业务组件
3、定义增强处理,增强处理就是在AOP框架为普通业务组件织入的处理动作
所以进行AOP编程的关键就是定义切入点和定义增强处理,一旦定义了合适的切入点和增强处理,AOP框架将自动生成AOP代理,即:代理对象的方法=增强处理+被代理对象的方法。
二、实际使用
aop最常见的使用场景就是 认证、权限、日志、事务管理四大方向。当然,根据自己的业务需求可以实现很多功能。下面就聊聊我的实战代码。
需求:程序API的调用入口需要记录调用方的相关信息,传入参数,执行成功与否。
实现:常用的就是两种方式:
一是 限定包名+方法名,比如: "execution(public * com.yss.shopping.controller..*.*(..))"
二是 采用注解的方式。
分析:由于不同模块的不同方法都有可能需要记录相关日志,比如 controller或者service等,用于排查问题定位使用。而这些方法的包名也可能不一致,方法名称也取的不一致。所以采用包名+方法名就不够灵活,代码复用性不高,因此,我采用了注解的方式。
思路:开发一个注解类,定义相关属性,在有需要的日志记录的地方就加上相关注解。切入点则选取有注解类的方法,在业务方法执行前后进行相应日志记录。
注解类:
我定义了三个属性,module——真正的业务分为好多模块的,比如 支付,浏览记录、购物车、评论、优惠券等等。objectType——操作类型,比如 更新、创建、删除,手动、自动 等,description——描述,即一些通用的记录信息,比如可以加上时间、地点、成功或者失败等等。
其核心作用就是会将这些信息在advice里做真正的日志记录时,会将其丰富为相应的日志对象。
package com.log.api.entity;
import java.lang.annotation.*;
/**
* 日志记录
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogRecord {
String module() default ""; // 操作模块
String objectType() default ""; // 操作类型
String description() default ""; // 操作说明
}
下面就是真正的切面类,用于处理日志记录。
@Component 交给spring管理
@Aspect 这是一个切面类
@PointCut 标记切入点,也就是哪个方法需要被切入。
@AfterThrowing 抛异常时候将会执行该方法。
@After 业务方法执行完了将会执行该方法。
除此之外了,还有三种通知方式,@Around, @Before @ AfterReturning 可结合业务需求选择相应的通知方式。
重点说明:
1. 建议将通知里的代码 try catch ,确保切面类不会抛出异常,以免影响正常的业务逻辑。这个坑已经踩过了。
2. 注意包名一定要被spring 扫描到,如果你的切面类没有发生作用,首先检查包名,其次查看切入点的表达式是否正确。
/**
* 日志记录
*/
@Component
@Aspect
@Slf4j
public class LogAspect {
@Autowired
protected ILogService logService;
@Pointcut("@annotation(com.log.api.entity.LogRecord)")
public void pointcut() {
}
@AfterThrowing(value = "pointcut()", throwing = "e")
public void saveExceptionLog(JoinPoint joinPoint, Throwable e) {
log.warn("object sync error ",e);
try {
LogBrief logBrief = enrichLog(joinPoint);
logBrief.setExecuteMessage(e.getMessage()+" ## "+logBrief.getExecuteMessage());
logService.addLog(logBrief);
}catch (Exception e1){
log.error("aop record log error,{}",e);
}
}
/**
* 正常返回通知,拦截用户操作日志,连接点正常执行完成后执行, 如果连接点抛出异常,则不会执行
*
* @param joinPoint 切入点
*/
@After("pointcut()")
public void saveNormalLog(JoinPoint joinPoint) {
try{
LogBrief logBrief = enrichLog(joinPoint);
logBrief.setExecuteStatus("SUCCESS");
//保存日志
logService.addLog(logBrief);
}catch (Exception e){
log.error("aop record log error,{}",e);
}
}
public LogBrief enrichLog(JoinPoint joinPoint){
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
LogRecord logAnnotation = method.getAnnotation(LogRecord.class);
LogBrief logBrief = buildLogBrief();
if (logAnnotation == null) {
return logBrief;
}
logBrief.setModule(logAnnotation.module());
logBrief.setObjectType(logAnnotation.objectType());
// 请求的方法参数值
Object[] args = joinPoint.getArgs();
// 请求的类名
String className = joinPoint.getTarget().getClass().getName();
// 请求的方法参数名称
LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
String[] paramNames = u.getParameterNames(method);
String description = logAnnotation.description();
if (args != null) {
for (int i = 0; i < args.length; i++) {
description+= JSON.toJSONString(args[i] )+"==";
}
}
logBrief.setExecuteMessage(description);
return logBrief;
}
代码中的LogBrief对象,请根据自身需要定义。以上便是自己使用aop+注解解决实际需求的开发过程。
来源:oschina
链接:https://my.oschina.net/woniuyi/blog/3220292