Spring AOP——从Logger拦截说起

来源:互联网 发布:碳纤维自行车知乎 编辑:程序博客网 时间:2024/06/07 03:50

先用500字以内的大白话说明什么是AOP

引用zhihu上的一个回答:
作者:欲眼熊猫
链接:https://www.zhihu.com/question/24863332/answer/48376158
来源:知乎
著作权归作者所有,转载请联系作者获得授权。

面向切面编程(Aspect Oriented Program的首字母缩写) 。面向对象的特点是继承、多态和封装。而封装就要求将功能分散到不同的对象中去。实际上也就是说,让不同的类设计不同的方法。这样代码就分散到一个个的类中去了。这样做的好处是降低了代码的复杂程度,使类可重用。

但是在分散代码的同时,也增加了代码的重复性。什么意思呢?比如说,在两个类中,需要在每个方法中各自写日志。按面向对象的设计方法,我们就必须在两个类的方法中都加入完全相同的写日志的代码,因为面向对象的封装让类与类之间无法共享代码,而不能将这些重复的代码统一起来。

也许有人会说,那好办啊,我们可以将这段代码写在第三个类的独立的方法里,然后再在这两个类中调用。但是,这样一来,这两个类跟我们上面提到的独立的类就有耦合了,它的改变会影响这两个类。那么,有没有什么办法,能让我们在需要的时候,随意地加入代码呢?这种在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。

一般而言,我们管切入到指定类指定方法的代码片段称为切面。有了AOP,我们就可以把几个类共有的代码,抽取到一个切片中,等到需要时再切入对象中去,从而改变其原有的行为。

这样看来,AOP其实只是OOP的补充而已。OOP从横向上区分出一个个的类来,而AOP则从纵向上向对象中加入特定的代码。有了AOP,OOP变得立体了。如果加上时间维度,AOP使OOP由原来的二维变为三维了,由平面变成立体了。从技术上来说,AOP基本上是通过代理机制实现的。

AOP在编程历史上可以说是里程碑式的,对OOP编程是一种十分有益的补充。

个人理解,Spring中的事务管理是一个很好的AOP的例子,各个DAO(Data Access Object)类只管增删改查,事务统一用Spring的事务管理。

此外,遍布代码各个角落的大量log4j日志也可以通过AOP方式集中在一个AOP类中,统一管理。

再用一个Spring Logger拦截的例子说明AOP

这里的全部用法参考spring官网4.2.9文档的第10章《Aspect Oriented Programming with Spring》,不保证和最新版本一致

需要用到的jar包

Spring 4.x一共有20个jar包,推荐使用Maven或Gradle配置方式下载。如果公司局域网不能使用Maven只能去官网下载jar包:
官方地址1
官方地址2
官方地址3

此外,依赖AOP标准相关的包必须额外下载,推荐使用Maven网页版下载,搜索框输入aspectj,结果点进去就能下载jar包
下载网页

(千万别漏了3个aop相关的jar,需要额外下载!)

aspectj-1.8.9.jaraopalliance-1.0.jaraspectjweaver-1.6.9.jarspring-aop-4.2.3.RELEASE.jarspring-aspects-4.2.3.RELEASE.jarspring-beans-4.2.3.RELEASE.jarspring-context-4.2.3.RELEASE.jarspring-context-support-4.2.3.RELEASE.jarspring-core-4.2.3.RELEASE.jarspring-expression-4.2.3.RELEASE.jarspring-jdbc-4.2.3.RELEASE.jarspring-jms-4.2.3.RELEASE.jarspring-messaging-4.2.3.RELEASE.jarspring-orm-4.2.3.RELEASE.jarspring-test-4.2.3.RELEASE.jarspring-tx-4.2.3.RELEASE.jarspring-web-4.2.3.RELEASE.jarspring-webmvc-4.2.3.RELEASE.jar

spring配置文件

Spring提供了4种实现AOP的方式:

  1. 经典的基于代理的AOP
  2. @Aspect注解驱动的切面
  3. 纯POJO切面
  4. 注入式AspectJ切面

这里我们使用的是第二种,因为这种方式和第一种相比较简单。而且spring 4对注解的支持已经非常完善了。

Web App项目的spring配置文件路径是项目/WebContent/x-servlet.xml(文件名中的”x“是同目录下web.xml中配置的服务名 <servlet-name>x</servlet-name>),配置完spring AOP后,x-servlet.xml 如下:

<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"       xmlns:context="http://www.springframework.org/schema/context"       xmlns:task="http://www.springframework.org/schema/task"       xmlns:mvc="http://www.springframework.org/schema/mvc"       xmlns:aop="http://www.springframework.org/schema/aop"       xsi:schemaLocation="http://www.springframework.org/schema/beans        http://www.springframework.org/schema/beans/spring-beans.xsd        http://www.springframework.org/schema/context        http://www.springframework.org/schema/context/spring-context.xsd        http://www.springframework.org/schema/mvc        http://www.springframework.org/schema/mvc/spring-mvc.xsd        http://www.springframework.org/schema/websocket        http://www.springframework.org/schema/websocket/spring-websocket.xsd       http://www.springframework.org/schema/aop        http://www.springframework.org/schema/aop/spring-aop-3.0.xsd       http://www.springframework.org/schema/task        http://www.springframework.org/schema/task/spring-task-3.0.xsd">    <context:component-scan base-package="com.my.demo" >    </context:component-scan>    <!--有了这个Spring就能够自动扫描被@Aspect标注的切面了-->    <aop:aspectj-autoproxy /></beans>
import org.apache.log4j.Logger;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;import org.springframework.stereotype.Component;import cn.paic.rep.pare.annotation.SysLogger;import cn.paic.rep.pare.modal.EbdUserAccountEntity;import cn.paic.rep.pare.sessionManager.SessionManager;/** * 利用Spring AOP拦截日志 */@Aspect //说明这个类是AOP概念中的切面@Component //说明这个类是使用Spring的注解方式生成(单例)实例public class SysLoggerAspect {    private static Logger logger = Logger.getLogger(SysLoggerAspect.class); // 使用log4j类记录日志    /*    (1)括号里是国际标准AspectJ 5规定的语法,具体请看下面介绍    (2)方法返回值必须void    */    @Before("execution(* *(..))")    public void checkBefore(String sessionKey,SysLogger sysLogger) {        System.out.println("写日志");      }}

@Before注解的括号中各个元素分别表示

  • (可选)修饰符匹配 modifier-pattern
  • 返回值匹配 ret-type-pattern
  • (可选)类路径匹配 declaring-type-pattern
  • 方法名和参数匹配 name-pattern(param-pattern)
  • (可选)异常类型匹配 throws-pattern

在各个pattern中可以使用“*”来表示匹配所有。在参数匹配 param-pattern 中,遵循由ApsectJ 5定义的一系列规则1 :

  • 可以指定具体的参数类型,如(String)表示匹配一个String参数的方法
  • 多个参数间用“,”隔开
  • 可以用“*”来表示匹配任意类型的参数;(*,String)表示匹配有两个参数的方法,第一个参数可以是任意类型,而第二个参数是String类型;
  • 可以用(..)表示零个或多个任意参数。
  • 在类路径匹配中,”.*”表示包下所有类,”..*”表示包以及子包下的所有类。如com.savage.service.* 表示 com.savage.service包下的所有类。

现在来看看几个例子:

  1. execution(* *(..))
    表示匹配所有方法
  2. execution(public * com. savage.service.UserService.*(..))
    表示匹配com.savage.server.UserService中所有的公有方法
  3. execution(* com.savage.server...(..))
    表示匹配com.savage.server包及其子包下的所有方法

一个Pointcut定义由 AspectJ 表示式和 函数签名组成,例如:

//Pointcut表示式@Pointcut("execution(* com.savage.aop.MessageSender.*(..))")//Pointcut签名private void log(){}    

其中,具体有三种Pointcut
@Before(一般用来记录入参),
@After(一般用来记录返回值),
@Around(包含了@Before和@After的功能,此外,一般用来计算方法执行的时间)

@Before,@After,@Around可以指定已经用@Pointcut修饰好的函数签名,如

//因为已经在log()方法上声明了Pointcut表示式,//所以这里等价于@Before("execution(* com.savage.aop.MessageSender.*(..))")@Before("log()")

除了execution表示式外,还有within、this、target、args等 AspectJ 表示式。多种表示式可以通过逻辑运算符号 &&、||、! 链接起来

@Pointcut("execution(* com.savage.aop.MessageSender.*(..))")private void logSender(){}@Pointcut("execution(* com.savage.aop.MessageReceiver.*(..))")private void logReceiver(){}@Pointcut("logSender() || logReceiver()")private void logMessage(){}

在AOP中获得调用方信息的办法

获得当前调用方方法

获取到接口的方法

@Around("execution(* *(..))")public Object doAround(ProceedingJoinPoint pjp) throws Throwable{    Signature s = pjp.getSignature();    MethodSignature ms = (MethodSignature)s;    Method m = ms.getMethod();}

获取到实现类的方法

//ProceedingJoinPoint 作为参数时,只能用@Around注解,不然报错@Around("execution(* *(..))")public Object doAround(ProceedingJoinPoint pjp) throws Throwable{    Signature sig = pjp.getSignature();    MethodSignature msig = null;    if (!(sig instanceof MethodSignature)) {        throw new IllegalArgumentException("该注解只能用于方法");    }    msig = (MethodSignature) sig;    Object target = pjp.getTarget();    Method currentMethod = target.getClass()        .getMethod(msig.getName(), msig.getParameterTypes());    //获得方法入参    Object[] args = joinPonit.getArgs();        String[] strs = new String[args.length];        for (int i=0; i<args.length; i++) {            if (args[i] == null) {                continue;            }            strs[i] = args[i].toString();//当url不带某参数时,spring会令它为null,这里就会NPE        }}

附录 术语中英文对照

Aspect - 切面
Join point - 切入点
Advice - 增强2(个人认为翻译为”通知“更好)
Pointcut - 切点。AOP通过“切点”定位特定的连接点。
Introduction - 声明额外的methods or fields on behalf of a type.
Target object - 需要织入通知的目标类。
AOP proxy - 为bean代理
Weaving - 织入。将Advice(通知)添加到目标类具体Joinpoint(连接点)上的过程。
Joinpoint - 连接点。Spring支持方法调用前、方法调用后、方法抛出异常时以及Around(方法调用前后)
AspectJ - 一种描述AOP的国际标准,Spring支持部分的ApsectJ


  1. 陈雄华, 林开雄. Spring 3.x 企业应用开发实战[M]. 北京:电子工业出版社, 2012: 233~234 ↩
  2. 陈雄华, 林开雄. Spring 3.x 企业应用开发实战[M]. 北京:电子工业出版社, 2012: 187~188 ↩
0 0
原创粉丝点击