天天看点

使用@AspectJ注解开发SpringAOP

现在使用@AspectJ注解的方式已经成为了主流。所以我用一个简单的例子来了解一下他的整个过程。

选择连接点

Spring是方法级别的AOP框架,我们主要是以某个类的某个方法作为连接点,用动态代理的理论来说,就是要拦截哪个方法织入对应AOP通知。

首先我们建一个接口:

package aop.service;

import game.bigLiZi.game.pojo.Role;

public interface RoleService {
    public void printRole(Role role);
}

           

这个接口很简单,接下来提供一个实现类:

package aop.service.impl;

import aop.service.RoleService;
import game.bigLiZi.game.pojo.Role;
import org.springframework.stereotype.Component;

@Component
public class RoleServiceImpl implements RoleService {
    @Override
    public void printRole(Role role) {
        System.out.println("{ id:"+role.getId()+"roleName:"+role.getRoleName()+
                                "note:"+role.getNote()+"}");
    }
}

           

这个时候如果把printRole作为AOP的连接点,那么用动态代理的语言就是要为类RoleServiceImpl生成代理对象,然后拦截printRole方法,于是可以产生各种AOP通知方法。

创建切面

选择好了连接点就可以创建切面了,在Spring中只要使用@Aspect注解一个类,那么Spring IoC容器就会认为这是一个切面了。

package aop.aspect;

import org.aspectj.lang.annotation.*;

@Aspect
public class RoleAspect {

    @Before("execution(* aop.service.impl.RoleServiceImpl.printRole(..))")
    public void before(){
        System.out.println("before..........");
    }

    @After("execution(* aop.service.impl.RoleServiceImpl.printRole(..))")
    public void after(){
        System.out.println("after..........");
    }

    @AfterReturning("execution(* aop.service.impl.RoleServiceImpl.printRole(..))")
    public void afterReturning(){
        System.out.println("afterReturning..........");
    }

    @AfterThrowing("execution(* aop.service.impl.RoleServiceImpl.printRole(..))")
    public void afterThrowing(){
        System.out.println("afterThrowing..........");
    }
}

           

这里我们就要说一说AspectJ注解了:

  • @Before:在被代理对象的方法前调用,前置通知。
  • @Around:将被代理对象的方法封装起来,并用环绕通知取代它。 它将覆盖原有方法,但是允许你通过反射调用原有的方法。
  • @After:在被代理对象的方法后调用, 后置通知。
  • @AfterReturning:在被代理对象的方法正常返回后调用,返回通知,要求被代理对象的方法执行过程中没有发生异常。
  • @AfterThrowing:在被代理对象的方法抛出异常后调用,异常通知,要求被代理对象的方法执行过程中返回异常。

上面那段代码的注解使用了对应的正则表达式,这些正则表达式是切点的问题,也就是要告诉Spring AOP,需要拦截什么对象的什么方法。

定义切点

上面代码在注解中定义了execution的正则表达式,Spring是通过这个正则表达式判断是否需要拦截你的方法,这个表达式是:

对这个表达式分析一下:

  • execution:代表执行方法的时候会触发
  • *:代表任意返回类型的方法
  • aop.service.impl.RoleServiceImpl:代表类的全限定名
  • printRole:被拦截方法名称
  • (…):任意的参数

上面的表达式还有些简单,AspectJ的指示器还有很多,下面我列举几个:

  • arg():限制连接点匹配参数为指定类型的方法
  • execution:用于匹配连接点的执行方法
  • this():限制连接点匹配AOP代理的Bean,引用为指定类型的类
  • target:限制连接点匹配被代理对象为指定的类型
  • within():限制连接点匹配的包

此外,上面的正则表达式需要重复书写多次,比较麻烦,这里我们可以使用@Pointcut来避免这个麻烦。代码如下:

package aop.aspect;

import org.aspectj.lang.annotation.*;

public class anotherAspect {
    @Pointcut("execution(* aop.service.impl.RoleServiceImpl.printRole(..))")
    public void print(){
        
    }
    
    @Before("print()")
    public void before(){
        System.out.println("before..........");
    }

    @After("print()")
    public void after(){
        System.out.println("after..........");
    }

    @AfterReturning("print()")
    public void afterReturning(){
        System.out.println("afterReturning..........");
    }

    @AfterThrowing("print()")
    public void afterThrowing(){
        System.out.println("afterThrowing..........");
    }
}

           

测试AOP

连接点、切面以及切点都有了,这个时候就可以编写测试代码来测试AOP的内容。

首先要对Spring的Bean进行配置,采用Java配置,代码如下:

package aop.config;

import aop.aspect.RoleAspect;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@EnableAspectJAutoProxy
@ComponentScan("aop")
public class AopConfig {

    @Bean
    public RoleAspect getRoleAspect(){
        return new RoleAspect();
    }
}

           

测试AOP流程

package aop.main;

import aop.config.AopConfig;
import aop.service.RoleService;
import game.bigLiZi.game.pojo.Role;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Main {
    public static void main(String[] args){
        ApplicationContext ctx=new AnnotationConfigApplicationContext(AopConfig.class);
        RoleService roleService=(RoleService)ctx.getBean(RoleService.class);
        Role role=new Role();
        role.setId(1L);
        role.setRoleName("role_name_1");
        role.setNote("role_note_1");
        roleService.printRole(role);
        System.out.println("########################");
        role=null;
        roleService.printRole(role);
    }
}

           

在第二次打印之前,将role设置为null,这样是为了测试异常返回通知。

before..........
{ id:1roleName:role_name_1note:role_note_1}
after..........
afterReturning..........
########################
before..........
after..........
afterThrowing..........
Exception in thread "main" java.lang.NullPointerException