spring aop基本操作

     Spring AOP作为spring的核心之一,是非常好用的。我们可以将其用在很多地方,例如记录日志,权限验证,数据校验,异常处理等等。自己经常会用到,但是总是过了一段时间就忘记了,这里记录下如何集成及基本操作,例如基本配置,切点定义,通知定义等等。

1、maven依赖

<!--spring aop-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aop</artifactId>
      <version>4.2.3.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.8.7</version>
    </dependency>
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjrt</artifactId>
      <version>1.8.6</version>
    </dependency>
    <dependency>
      <groupId>cglib</groupId>
      <artifactId>cglib-nodep</artifactId>
      <version>3.1</version>
    </dependency>

其中spring-aop是进行spring切面编程必备的,aspectweaver和aspectjrt是为了支持AspectJ风格编程(例如@Aspect注解,@PointCut注解等等)的必须的。cglib-nodep暂时不太确定是干嘛的,应该是为了支持静态代理需要的吧,我没有引入也可以跑。如果有知道的朋友,麻烦告知下。

2、aop配置

<!--激活自动代理-->
    <aop:aspectj-autoproxy/>

这里主要介绍基于注解的配置方式,也就是AspectJ风格的配置方式,基于xml的配置方式太过于繁琐了,因此略过。要启用AspectJ风格的配置方式,需要在xml文件里配置自动代理,如上所示。

3、PointCut

PointCut说白了就是一组连接点的集合,它用简单的表达式包含了一组我们关心的连接点,我们往往需要在这些连接点上进行相同的操作。比如,我们会在所有的数据库操作前打印SQL参数,记录日志等等。在Spring aop中,PointCut只能关联执行的方法,而且bean必须是spring的bean,否则是不会起作用的,这点要牢记。

先来看一看一个最简单的PointCut定义:

 @Pointcut("execution(String testBeforeAdvice(..))")//PointCut定义
    public void pt2() {}//PointCut名称

pt2定义了一组连接点(在spring aop中是一组方法),方法名称是testBeforeAdvice,返回String类型,参数任意。在后面的通知部分,我们就可以使用pt2来代表定义的这个PointCut。

以execution类型切点为例,一个完整的PointCut语法结构如下:

语法结构:execution(【修饰符】 返回值类型 方法名(参数) 【异常模式】

其中【】部分是可选的。下面列出一些例子,自己可以依葫芦画瓢:

 execution(public *.*(..))  所有的public方法。
 execution(* hello(..))            所有的hello()方法
 execution(String hello(..))   所有返回值为String的hello方法。
 execution(* hello(String))    所有参数为String类型的hello()
 execution(* hello(String..))      至少有一个参数,且第一个参数类型为String的hello方法
 execution(* com.aspect..*(..))   所有com.aspect包,以及子孙包下的所有方法
 execution(* com..*.*Dao.find*(..))  com包下的所有一Dao结尾的类的一find开头的方法

如果能理解..和*两个通配符,基本上就没啥问题了。..放在包定义里面,表示所有子孙包,放在参数里面,表示参数不固定。*号表示通配符,通配任意字符串。

PointCut类型有很多,最常用的就是我们刚才用到的execution类型PointCut,它表示方法执行。其他部分PointCut类型如下所示:

//@annotation类型切点,含有该annotation的方法
    @Pointcut("@annotation(com.gameloft9.demo.annotaion.METHODDESC)")
    public void pt3() {}

    //target类型切点
    //实现IHelloService接口的目标对象的任意连接点 (在Spring AOP中只是方法执行):
    @Pointcut("target(com.gameloft9.demo.service.api.IHelloService)")
    public void pt4() {}

    //within类型切点
    //在service包中的任意连接点(在Spring AOP中只是方法执行):
    @Pointcut("within(com.gameloft9.demo.service.impl.*)")
    public void pt5() {}

如果我们想组合不同类型的PointCut怎么办,很简单,直接使用&&链接起来就好了:

//组合切点,可以用&&,||和!逻辑连接符链接不同的切点
    @Pointcut("pt3()&&pt4()")
    public void pt6(){}
是不是很简单。

3、advice

通知(advice)必须和一组PointCut关联,它会在关联的方法执行前,后或者抛出异常时执行。不同的执行时间点,决定了通知的类型。目前有的通知类型有@Before,@After,@AfterReturning,@AfterThrowing,@Around这几种,下面是一些简单的例子:

/**
     * 前置通知,目标方法执行前会走到这里
     * 注意:通知和方法只能一对一,不能放多个通知
     * */
    @Before("pt1()")
    public void beforeAdvice(){
        log.info("进入前置通知了");
    }

    /**
     * 后置通知,不管是正常返回,还是异常返回,均会走到这里
     * */
    @After("pt2()")
    public void afterAdvice(){
        log.info("进入后置通知");
    }

    /**
     * 正常返回通知
     * */
    @AfterReturning(pointcut = "pt3()" ,returning = "res")
    public void afterReturningAdvice(Object res){
        log.info("进入返回通知,返回结果{}",res);
    }

    /**
     * 异常通知,如果目标方法抛出异常,会进入到这里
     * */
    @AfterThrowing(pointcut = "pt4()",throwing = "ex")
    public void afterThrowingAdvice(Throwable ex){
        log.error("进入异常通知",ex);
    }

除了@Around通知,其他几个通知都很简单,都是在方法执行的前后做一些操作,不涉及参数处理。如果我们要处理参数,而且在方法前后都要做一些额外处理,那么@Around就很有用了。

 /**
     * 环绕通知切点
     * */
    @Pointcut("execution(public String testAroundAdvice(String,javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse))")
    public void ptTestAroundAdvice() {}


    /**
     * 环绕通知,执行前进入,执行后返回。
     * 注意:
     * 1、参数获取的话,所有参数都必须列进来,要跟方法的参数顺序一致,名字可以不一致。
     * 2、pjp参数必须放在第一位。
     * */
    @Around("ptTestAroundAdvice()&&args(number,request,response)")
    public String testAroundAdvice(ProceedingJoinPoint pjp, String number, HttpServletRequest request, HttpServletResponse response){
        log.info("进入环绕通知,请求参数,number:{}",number);
        String res = "";

        try{
            res = (String)pjp.proceed();
        }catch(Throwable e){
            log.error("处理异常",e);
        }

        log.info("处理结果{}",res);
        return res;
    }

在定义@Around通知时,通过args将切点方法的参数名称定义好,然后通知处理方法里面就可以截获方法的参数并处理了。这里要注意的是ProceedingJoinPoint必须放在第一位,我们要通过它来调用目标方法并获取返回结果的。

pjp除了prceed()方法外,还有一个prceed(Object[] params)方法,通过这个方法可以替换原有参数:

 /**
     *环绕通知替换传入参数
     * */
    @Around("ptTestAroundAdviceParam()&&args(number,request,response)")
    public String testAroundAdviceParam(ProceedingJoinPoint pjp, String number, HttpServletRequest request, HttpServletResponse response){
        log.info("进入环绕通知,请求参数,number:{}",number);

        String replace = "5";
        Object[] param = {replace,request,response};//替换掉参数
        log.info("替换number为:{}",replace);

        String res = "";
        try{
            res = (String)pjp.proceed(param);
        }catch(Throwable e){
            log.error("处理异常",e);
        }

        log.info("处理结果{}",res);
        return res;
    }

4、引入

所谓引入就是为目标对象添加新的方法,而这个方法本来是属于另外的类。如果熟悉js的话,这个有点类似于apply操作。在spring aop中,我们通过@DeclareParents这个注解来实现。例如:

@Aspect
@Service
@Slf4j
public class IntroductionDemoAop {

    // ““+” 号,表示只要是Person及其子类都可以添加新的方法
    @DeclareParents(value = "com.gameloft9.demo.aspect.introduction.Person+", defaultImpl = PersonRun.class)
    public Run personRun;

}

通过上述操作,我们为Person及其子类劫持了Run接口,那么之后尽管Person及其子类没有实现Runj接口,我们也可以使用Run接口的方法。默认的实现都交给了PersunRun这个类。

public interface Run {
    public void run();
}
@Slf4j
public class PersonRun implements Run {

    public void run(){
      log.info("Person is running");
    }
}
public class Person {

}
@Service
@Slf4j
@Data
public class ChinesePerson extends Person {

    public void sayName(String name){
        log.info("我的名字是{}",name);
    }
}

调用代码示例:

 //测试引入
        Run run = (Run)chinesePerson;
        run.run();//新方法
        chinesePerson.sayName("gameloft9");

5、运行截图



好了,以上就是spring aop的基本点,更多内容请参考官方文档:

https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#aop-introductions


源码地址:https://download.csdn.net/download/gameloft9/10343785


猜你喜欢

转载自blog.csdn.net/gameloft9/article/details/79897563