SpringBoot-AOP日志管理
一 AOP几个重要概念
- Aspect:切面,在Spring中意为所有通知方法所在的类。
- Join point:连接点,程序执行中的一点,在Spring中只表示方法执行(Spring只支 持方法级别的拦截)
- Advice:通知,在特定连接点上采取的操作,Spring将通知抽象为拦截器,并围绕连接点维护拦截器链。共有5种类型。
前置通知(@Before):logStart:在目标方法(div)运行之前运行
后置通知(@After):logEnd:在目标方法(div)运行结束之后运行(无论方法正常结束还是异常结束)
返回通知(@AfterReturning):logReturn:在目标方法(div)正常返回之后运行
异常通知(@AfterThrowing):logException:在目标方法(div)出现异常以后运行
环绕通知(@Around):动态代理,手动推进目标方法运(joinPoint.procced())
AOP拦截器链则为以上五种通知组成。我们可以在通知方法中获得我们需要的参数(返回值,异常信息,代理对象等) - Pointcut:切点,与通知一起出现,使用专门的切点表达式决定在何处执行通知方法。
- Introduction:引入,为类添加新的方法或字段。
- Target object:被代理的对象
- AOP proxy:AOP代理对象,由JDK动态代理或CGLIB代理生成
- Weaving:织入,将通知等织入代理类。Spring AOP是动态织入(运行时织入),AspectJ则是静态织入(编译时织入)
二 AOP实现原理
AOP的原理 是使用JDK动态代理和CGLIB动态代理技术来实现的
- JDK动态代理:通过实现InvocationHandlet接口,并重写里面的invoke方法,通过为proxy类指定classLoader和一组interfaces来创建动态代理,JDK实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,如何实现动态代理呢,这就需要CGLib了,JDK动态代理是面向接口的代理模式,如果被代理目标没有接口那么Spring也无能为力,Spring通过java的反射机制生产被代理接口的新的匿名实现类,重写了其中AOP的增强方法。它是可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术
- JDK代理实现过程:
通过实现InvocationHandlet接口创建自己的调用处理器
通过为Proxy类指定ClassLoader对象和一组interface来创建动态代理
通过反射机制获取动态代理类的构造函数,其唯一参数类型就是调用处理器接口类型
通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数参入 - cglib的动态代理:CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。JDK动态代理与CGLib动态代理均是实现Spring AOP的基础。
三 简单代码实现日志管理
- 在springbootp pom.xml文件中引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.4</version>
</dependency>
- 定义注解
import java.lang.annotation.*;
@Target(ElementType.METHOD)//方法级别注解
@Retention(RetentionPolicy.RUNTIME)//运行时可见
@Documented
public @interface LogAnno {
String operateType(); //日志类型
}
如果是spring框架则需要在配置文件中声明开启aop,加入如下配置:
<aop:aspectj-autoproxy />
但是springboot中会有默认配置,其中spring.aop.auto属性默认是开启的,也就是说只要引入了AOP依赖后,默认已经增加了@EnableAspectJAutoProxy。
spring.aop.auto=true
spring.aop.proxy-target-class=false //默认为false JDK代理实现,如果要更改为CGLIB,则设置为true
- 定义日志插入,查询等dao,service
##日志表sql
CREATE TABLE test_log(
`id` int(11) NOT NULL AUTO_INCREMENT ,
`user_name` varchar(5) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL ,
`log_type` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL ,
`create_date` datetime NULL DEFAULT NULL ,
`log_result` varchar(4) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL ,
`remark` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL ,
PRIMARY KEY (`id`))
ENGINE=InnoDB
DEFAULT CHARACTER SET=utf8
COLLATE=utf8_general_ci
AUTO_INCREMENT=8
ROW_FORMAT=DYNAMIC;
##mybatis
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.study.notes.dao.LogMapper">
<resultMap id="BaseMap" type="com.study.notes.module.entity.LogEntity">
<result column="id" property="id" jdbcType="INTEGER"/>
<result column="user_name" property="userName" jdbcType="VARCHAR"/>
<result column="log_type" property="logType" jdbcType="VARCHAR"/>
<result column="log_result" property="logResult" jdbcType="VARCHAR"/>
<result column="create_date" property="createDate" jdbcType="TIMESTAMP"/>
<result column="remark" property="remark" jdbcType="VARCHAR"/>
</resultMap>
<insert id="addLog" parameterType="com.study.notes.module.entity.LogEntity">
insert into test_log(
user_name,log_type,
log_result,create_date,
remark
)values (
#{userName},
#{logType},
#{logResult},
#{createDate},
#{remark}
)
</insert>
<select id="selectLogInfo" resultMap="BaseMap">
select * from test_log t
<where>
<if test="logType!=null and logType!=''">
t.log_type=#{logType}
</if>
</where>
</select></mapper>
- 定义切面类
import com.study.notes.config.inteface.LogAnno;
import com.study.notes.module.entity.LogEntity;
import com.study.notes.service.ILogService;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Date;
@Aspect
@Component
public class LogAopAspect {
@Autowired
private ILogService iLogService;
/**
* 设置通知,2种方式,@Pointcut注解或者
*/
@Around("@annotation(com.study.notes.config.inteface.LogAnno)")
public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable{
//获取方法签名
MethodSignature methodSignature=(MethodSignature) joinPoint.getSignature();
//获取方法
Method method=methodSignature.getMethod();
//获取方法上面的注解
LogAnno logAnno=method.getAnnotation(LogAnno.class);
//获取操作描述的属性值
String operateType=logAnno.operateType();
//创建日志对象
LogEntity logEntity=new LogEntity();
logEntity.setLogType(operateType);
Object obj=null;
try{
//代理方式执行
obj=joinPoint.proceed();
//方法执行成功走这里
logEntity.setLogResult("执行成功");
}catch (Exception e){
//执行失败走这里
logEntity.setLogResult("执行失败");
}finally {
logEntity.setCreateDate(new Date());
iLogService.addLog(logEntity);
}
return obj;
}
}
- 使用
import com.study.notes.config.inteface.LogAnno;
import com.study.notes.dao.LogMapper;
import com.study.notes.module.entity.LogEntity;
import com.study.notes.service.ILogService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class LogService implements ILogService {
@Autowired
private LogMapper logMapper;
@Override
public void addLog(LogEntity logEntity) {
logMapper.addLog(logEntity);
}
@LogAnno(operateType = "查询操作")
@Override
public List<LogEntity> getLogInfo(String logType) {
return logMapper.selectLogInfo(logType);
}
}