1、背景:AspectJ作为AOP一大应用已经广为人知了,具体的应用场景也很多,如:日志处理、执行目标方法前做逻辑判断、事物控制等等;其实质大都是抽取出各类、方法中重复的、与业务逻辑无关的代码,形成一个切面(Aspect, 也就是一个类),在切面中定义切点(可以理解为将代码织入到那些 我从方法中提取出重复代码的位置处,一个切点可能包含多个连接点)、连接点(也就是切点的一个具体)、通知等,这个一来就消除了代码的冗余。
那么在这里我要做的是AspectJ的一个应用,在逻辑代码前后,做日志记录,并输出到日志文件。
2、AOP术语说明:
1)切面(Aspect):抽取重复代码形成的一个类
2)切点(Pointcut):抽取重复代码的位置,即将织入代码的那些位置
3)连接点(Joinpoint): 切点的具体表现
关于切点和连接点:比如切点P的定义扫描到了5个方法m1,m2,m3,m4,m5,也就是在这5个方法调用时需要进行增强,而具体的连接点就是m1~m5这5个方法;简单理解为通过切点找到一组连接点
如:execution(public * com.dw.controller..*(..) 是一个切面,意思com.dw.controller包下所有且返回任意类型的方法,都将织入这段代码,但是从浏览器中请求的可能是某一个具体的方法,如:
joinPoint:execution(String com.dw.controller.HelloController.hello(String)),就是一个连接点
4)通知(Advice): 即织入点执行的具体代码,有五种类型:
a:@Before,调用方法之前执行
b:@After,调用方法之后执行
c:@AfterThrowing,目标方法抛出异常后执行
d:@AfterReturing,对目标方法返回的结果做处理,但是不影响原返回结果
e:@Around,包裹目标方法执行
3、测试代码:(springboot下进行,很多注解都由springboot默认配置)
1)切面:
@Component
@Aspect
public class AspectAdvice {
// private static final Logger logger = LoggerFactory.getLogger(AspectAdvice.class);
private static final Logger logger = LoggerFactory.getLogger("aspect-advice");
@Before("execution(public * com.dw.controller..*(..))")
public void before(JoinPoint joinPoint) {
// do
logger.info("[Before]..." + ", 即将执行" + ", joinPoint:" + joinPoint);
}
@Around("execution(public * com.dw.controller..*(..))")
public Object around(ProceedingJoinPoint pdj) throws Throwable {
// do
logger.info("[Around]...");
return pdj.proceed();
}
@After("execution(* com.dw.controller.HelloController.consumer())")
public void after() {
// do
logger.info("[After]..." + ", 执行完成");
}
@AfterReturning(pointcut = "execution(* com.dw.controller.HelloController.consumer())", returning = "tmp")
public void afterReturning(Object tmp) {
// do
logger.info("[AfterReturning]..." + ", and result is " + tmp);
}
@AfterThrowing(pointcut = "execution(* com.dw.controller.HelloController.throwing())", throwing = "tmp")
public void afterThrowing(Throwable tmp) {
// do
logger.warn("[AfterThrowing]..." + ", 处理目标方法抛出的异常, 异常信息: " + tmp);
}
}
2)Controller.java
@RestController
@RequestMapping("/hello")
public class HelloController {
@Autowired
private HelloService helloService;
@RequestMapping("/{name}")
public String hello(@PathVariable("name") String name) {
return helloService.hello(name);
}
@RequestMapping("/consumer")
public String consumer() {
System.out.println("this is a consumer function");
return "consumer test";
}
@RequestMapping("/throwing")
public String throwing() throws Exception{
System.out.println("this is a throwging funtion");
try {
double tmp = 1 / 0;
} catch (Exception e) {
throw new Exception(e.getMessage());
}
return "throwing test";
}
}
3)日志配置:logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<property name="NORMAL_LOG_PATTERN"
value="%d{yyyy-MM-dd HH:mm:ss.SSS}	%p %20.20F--> %m%n"/>
<!-- appender 格式化输出日志, 控制台输出、文件输出策略-->
<!-- 控制台输出 -->
<appender name="consoleLog" class="ch.qos.logback.core.ConsoleAppender">
<!--展示格式 layout-->
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>%d -1 %msg%n</pattern>
</layout>
</appender>
<!-- 文件输出 -->
<appender name="fileAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
<File>/home/admin/demo1019/logs/demo.log</File>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 活动文件的名字会根据fileNamePattern的值,每隔一段时间改变一次 -->
<!-- 文件名:log/demo.2017-12-05.0.log -->
<fileNamePattern>/home/admin/demo1019/logs/demo.log.%d.%i.log</fileNamePattern>
<!-- 每产生一个日志文件,该日志文件的保存期限为30天 -->
<maxHistory>30</maxHistory>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<!-- maxFileSize:这是活动文件的大小,默认值是10MB,测试时可改成1KB看效果 -->
<maxFileSize>50MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<encoder>
<!-- pattern节点,用来设置日志的输入格式 -->
<pattern>${NORMAL_LOG_PATTERN}</pattern>
<!-- 记录日志的编码:此处设置字符集 - -->
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- root必选节点, 指定最基本的输出级别 -->
<root level="info">
<appender-ref ref="consoleLog"/>
</root>
<!-- 匹配到 LoggerFactory.getLogger("aspect-advice") -->
<logger name="aspect-advice">
<level value="info"/>
<appender-ref ref="fileAppender"/>
</logger>
</configuration>
4)springboot启动类:
@SpringBootApplication
public class ApplicationStart {
public static void main(String[] args) {
SpringApplication.run(ApplicationStart.class, args);
}
}
这里有一点需要注意:该类要和注解类所在包同级,否则一些扫描配置不起作用,
@SpringBootApplication包含三个注解, @ComponentScan, @Configuration, @EnableAutoConfiguration,所以简单的程序不用再写类来加载配置文件了。