最近真是不停地学各种东西,现在发现难怪那么多人用java,成熟的包一套一套的。
我们目标是,在一些特定的方法执行的时候(比如执行前,执行完毕)通过日志输出一些东西。这正符合了《Spring in Action》里头说到的“这些功能需要用到应用程序的多个地方,但是我们又不想在每个点调用他们”。为了顺应时代的潮流(其实是我没学xml定义),这里全部使用基于注释(annotation)的方式实现。
Maven依赖
需要引入AOP相关、log4j相关的依赖包
pom.xml
<!-- AOP --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>4.2.5.RELEASE</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.8.9</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.9</version> </dependency> <!-- log4j--> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency>
目录结构
里面提到的相关文件,目录结构大致如下:
CreativeCrowd |--src/main |--... |--java/edu/edu/inlab |--config |--CreCrowdAppInitializer |--RootConfig |--WebConfig |--service |--LoggingService.java |--resources |--log4j.properties ...
如有需要全·套的,可以直接去我的GitHub项目看看:https://github.com/idailylife/CreativeCrowd
Aspect构建
切面的构建可以声明一个类来并加入注释实现。我做的是一个日志服务,有下面的声明
LoggingService.java
package edu.inlab.service; import edu.inlab.models.json.MTurkIdValidationRequestBody; import org.apache.log4j.Logger; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import javax.servlet.http.HttpServletRequest; @Aspect public class LoggingService { final static Logger logger = Logger.getLogger(LoggingService.class); /* User */ @Pointcut("execution(* edu.inlab.web.UserController.userLogIn(..))") public void userLoginPost(){} @Before("userLoginPost()") public void logUserLoginAttempt(){ logger.debug("user login attempt."); } /* Task */ @Pointcut(value = "execution(* edu.inlab.web.TaskController.checkMturkId(..))" + "&& args(request, body,..)", argNames = "request, body") public void checkMTurkId(HttpServletRequest request, MTurkIdValidationRequestBody body){} @Before(value = "checkMTurkId(request, body)", argNames = "request, body") public void logCheckMtAttempt(HttpServletRequest request, MTurkIdValidationRequestBody body){ logger.debug("Check mturk id, Remote IP= " + getRemoteIP(request) + ", RequestBody= " + body); } String getRemoteIP(HttpServletRequest request){ String ip = request.getHeader("X-FORWARDED-FOR"); if(ip == null){ ip = request.getRemoteAddr(); } return ip; } }
里面的重点是要用@Aspect声明这是一个切面,另外@Pointcut是切点的注释,而@Before这种就是对应的前置通知(当然也可以是@After等等啦)
看一下里面的关于TaskController的操作(/*Task*/下面的),这其实是我控制器里头的一个函数,原函数比较长,函数签名是这样:
public AjaxResponseBody checkMturkId(HttpServletRequest request, @Valid MTurkIdValidationRequestBody body, BindingResult bindingResult)
可以看到里面有3个参数,而在切面定义的时候,我写日志只需要它两个参数就可以了,于是就有了下图这样的定义:
上图这种写法用到了命名切入点,其实还有匿名切入点,这个具体可以看下面给的参考文献,介绍的很详细了。
在execution(* edu.inlab.web.TaskController.checkMturkId(..))这种里面有个*号,这表示接受任何形式的返回参数,当然如果要指定也是可以的,记得要放类型的全名(比如com.blabla.SomeClass)。
配置
声明了切面,也别忘了在Spring里面开启AOP的东西。对应我的SpringMVC项目,我再rootConfig里面写了个Bean。
什么是RootConfig?就是在extend AbstractAnnotationConfigDispatcherServletInitializer的时候Override的那个方法嘛,比如:
CreCrowdAppInitializer.java
public class CreCrowdAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { protected Class<?>[] getRootConfigClasses() { return new Class<?>[]{RootConfig.class}; } ... }
RootConfig.java
开启AOP的关键是在配置文件中声明@EnableAspectJAutoProxy,并且给提供个可以注入的Bean
@Configuration @ComponentScan(basePackages = "edu.inlab", excludeFilters = { @ComponentScan.Filter(type = FilterType.ANNOTATION, value = EnableWebMvc.class) }) @EnableAspectJAutoProxy public class RootConfig { @Bean public LoggingService loggingService(){ return new LoggingService(); } }
Log4j
log4j的使用很方便,而且发现开了log4j以后Hibernate自己也会输出一大堆东西(这个跟hibernate的配置有关)。Log4j在maven里面添加依赖之后,需要声明一个配置文件log4j.properties,放在src目录下。基本的配置可以照着下面画葫芦:
log4j.properties
# Root logger option log4j.rootLogger=DEBUG, stdout, file # Redirect log messages to console log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.Target=System.out log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n # Redirect log messages to a log file, support file rolling. log4j.appender.file=org.apache.log4j.RollingFileAppender log4j.appender.file.File=D:/Code/Java/CreativeCrowd/log/common.log log4j.appender.file.MaxFileSize=5MB log4j.appender.file.MaxBackupIndex=10 log4j.appender.file.layout=org.apache.log4j.PatternLayout log4j.appender.file.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
然后就是使用了。在之前的代码里其实已经写了log4j的一些东西,首先需要拿到log4j的一个静态对象(这个是静态函数给的)
final static Logger logger = Logger.getLogger(LoggingService.class);
里面的参数是你声明的类。
然后就可以直接使用logger啦,比如
logger.debug(...)
然后运行起来,命令行也会看得到输出(真心推荐IntelliJ IDEA,edu邮箱可以有免费优惠!!!)
参考文献:
- 《Spring实战》/《Spring in Action》(第四版)
- AspectJ切入点语法详解
- AOP 之 6.4 基于@AspectJ的AOP ——跟我学spring3
- Java中log4j控制写入日志开关
- 10. Aspect Oriented Programming with Spring
- Spring AOP + AspectJ annotation example