AspectJ对AOP的支持

AspectJ 对 AOP 的实现

对于 AOP 这种编程思想,很多框架都进行了实现。 Spring 就是其中之一, 可以完成面向切面编程。AspectJ这个框架也实现了 AOP 的功能,且其实现方式更为简捷,使用更为方便,而且还支持注解式开发。所以,Spring2.0版本中将 AspectJ 的对于 AOP 的实现也引入到了自己的框架中,不过织入仍然还是使用的spring的aop完成。
spring 本身也堆AOP这种思想进行了实现,只不过使用起来不太方便,因此在 Spring 中使用 AOP 开发时,一般使用 AspectJ 的实现方式。

基于xml实现AOP的方式

添加依赖:

    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.8.13</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>5.2.4.RELEASE</version>
    </dependency>

代码示例:

创建UserService接口:

public interface UserService {

    void addUser();

    void selectUserById(int id)  throws Exception;

    int updateUser();

    void deleteUser();

    void selectUser();
}

创建UserService接口的实现类:

import com.monkey1024.service.UserService;
import org.springframework.stereotype.Service;


public class UserServiceImpl implements UserService {


    @Override
    public void addUser() {
        System.out.println("执行service中的addUser方法");
    }

    @Override
    public void selectUser() {
        System.out.println("执行service中的selectUser方法");
    }

    @Override
    public void selectUserById(int id) throws Exception{
        System.out.println("执行service中的selectUserById方法");
        if (id == 0){
            throw new Exception();

        }
    }

    @Override
    public int updateUser() {
        System.out.println("执行service中的updateUser方法");

        return 1024;
    }

    @Override
    public void deleteUser() {
        System.out.println("执行service中的deleteUser方法");
    }


}

创建切面类MyAspect:

package com.monkey1024.aspect;

import org.aspectj.lang.ProceedingJoinPoint;


public class MyAspect {

    public void before() {
        System.out.println("========前置通知========");
    }

    public void after() {
        System.out.println("========最终通知========:");
    }

    public void afterThrowing(Exception e) {
        System.out.println("========异常通知========:" + e);
    }

    public void afterReturning(int result) {
        System.out.println("========后置通知========:" + result);
    }

    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("========环绕通知:前========:");
        Object proceed = pjp.proceed();
        System.out.println("========环绕通知:后========:");

        return proceed;
    }
}

spring的配置文件:

<?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:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">


    <!--扫描器-->
    <bean id="userService" class="com.monkey1024.service.impl.UserServiceImpl"/>
    <bean id="myAspect" class="com.monkey1024.aspect.MyAspect"/>


    <!--配置aop-->
    <aop:config>
        <!--定义切入点-->
        <aop:pointcut id="addUserPointcut" expression="execution(* com.monkey1024.service.impl.UserServiceImpl.addUser())"/>
        <aop:pointcut id="selectUserPointcut" expression="execution(* com.monkey1024.service.impl.UserServiceImpl.selectUser())"/>
        <aop:pointcut id="selectUserByIdPointcut" expression="execution(* com.monkey1024.service.impl.UserServiceImpl.selectUserById(..))"/>
        <aop:pointcut id="updateUserPointcut" expression="execution(* com.monkey1024.service.impl.UserServiceImpl.updateUser())"/>
        <aop:pointcut id="deleteUserPointcut" expression="execution(* com.monkey1024.service.impl.UserServiceImpl.deleteUser())"/>

        <!--定义切面-->
        <aop:aspect ref="myAspect">
            <!--前置通知-->
            <aop:before method="before" pointcut-ref="addUserPointcut"/>
            <!--后置通知-->
            <aop:after-returning method="afterReturning" pointcut-ref="updateUserPointcut" returning="result"/>
            <!--异常通知-->
            <aop:after-throwing method="afterThrowing" pointcut-ref="selectUserByIdPointcut" throwing="e"/>
            <!--最终通知-->
            <aop:after method="after" pointcut-ref="selectUserPointcut"/>
            <!--环绕通知-->
            <aop:around method="around" pointcut-ref="deleteUserPointcut"/>
        </aop:aspect>
    </aop:config>

</beans>

创建测试方法:

@Test
public void testDI() {
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    UserService userService = (UserService) context.getBean("userService");
    userService.addUser();
    System.out.println("#####################");

    userService.updateUser();
    System.out.println("#####################");

    try {
        userService.selectUserById(0);
    } catch (Exception e) {
        e.printStackTrace();
    }
    System.out.println("#####################");


    userService.selectUser();
    System.out.println("#####################");

    userService.deleteUser();
}

配置文件中,除了要定义目标类与切面的 Bean 外,最主要的是在 aop:config 中进行aop 的配置。而该标签的底层,会根据其子标签的配置,生成自动代理。

通过其子标签aop:pointcut定义切入点,该标签有两个属性,id 与 expression。分别用于指定该切入点的名称及切入点的值。expression 的值为 execution 表达式。

aop:aspect的 ref 属性用于指定使用哪个切面。
aop:aspect的子标签是各种不同的通知类型。不同的通知所包含的属性是不同的,但也有共同的属性。
method:指定该通知使用的切面中的哪个方法。
pointcut-ref:指定该通知要织入的切入点。
AspectJ 通知的 XML 标签如下:

<aop:before/>:前置通知 
<aop:after-returning/>:  后置通知 
<aop:around/>:环绕通知 
<aop:after-throwing/>:异常通知 
<aop:after/>:最终通知

AspectJ 的切入点表达式

AspectJ 除了提供了六种通知外,还定义了专门的表达式用于指定切入点。表达式的原型是:

execution ( 
    [modifiers-pattern]  访问权限类型
    ret-type-pattern  返回值类型
    [declaring-type-pattern]  全限定性类名
    name-pattern(param-pattern)  方法名(参数名)
    [throws-pattern]  抛出异常类型 
)

切入点表达式要匹配的对象就是目标方法的方法名。所以,execution 表达式中明显就是方法的签名。注意,表达式中加[ ]的部分表示可省略部分,各部分间用空格分开。

举例:

execution(public * *(..)) 
指定切入点为:任意公共方法。

execution(* set*(..)) 
指定切入点为:任何一个以“set”开始的方法。

execution(* com.xyz.service.*.*(..)) 
指定切入点为:定义在 service 包里的任意类的任意方法。

execution(* com.xyz.service..*.*(..))
指定切入点为:定义在 service 包或者子包里的任意类的任意方法。“..”出现在类名中时,后面必须跟“*”,表示包、子包下的所有类。

execution(* *.service.*.*(..))
指定只有一级包下的 serivce 子包下所有类(接口)中所有方法为切入点 

execution(* *..service.*.*(..))
指定所有包下的 serivce 子包下所有类(接口)中所有方法为切入点 

execution(* *.ISomeService.*(..))
指定只有一级包下的 ISomeSerivce 接口中所有方法为切入点 

execution(* *..ISomeService.*(..))
指定所有包下的 ISomeSerivce 接口中所有方法为切入点 

execution(* com.xyz.service.IAccountService.*(..)) 
指定切入点为:  IAccountService  接口中的任意方法。 

execution(* com.xyz.service.IAccountService+.*(..)) 
指定切入点为:  IAccountService  若为接口,则为接口中的任意方法及其所有实现类中的任意方法;若为类,则为该类及其子类中的任意方法。 

execution(* joke(String,int)))
指定切入点为:所有的 joke(String,int)方法,且 joke()方法的第一个参数是 String,第二个参 数是 int。如果方法中的参数类型是 java.lang 包下的类,可以直接使用类名,否则必须使用全限定类名,如 joke( java.util.List, int)。 

execution(* joke(String,*))) 
指定切入点为:所有的 joke()方法,该方法第一个参数为 String,第二个参数可以是任意类型,如 joke(String s1,String s2)和 joke(String s1,double d2)都是,但 joke(String s1,double d2,String s3)不是。

execution(* joke(String,..)))   
指定切入点为:所有的 joke()方法,该方法第  一个参数为 String,后面可以有任意个参数且参数类型不限,如 joke(String s1)、joke(String s1,String s2)和 joke(Strings1,double d2,String s3)都是。

execution(* joke(Object))
指定切入点为:所有的 joke()方法,方法拥有一个参数,且参数是 Object 类型。joke(Object ob)是,但,joke(String s)与 joke(User u)均不是。

execution(* joke(Object+))) 
指定切入点为:所有的 joke()方法,方法拥有一个参数,且参数是 Object 类型或该类的子类。不仅 joke(Object ob)是,joke(String s)和 joke(User u)也是。

基于注解的AOP的实现方式

使用注解首先要确保你的项目中有下面两个依赖:

    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.8.13</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>5.2.4.RELEASE</version>
    </dependency>

在之前定义的切面类上面加上下面两个注解:

@Aspect
@Component

其中@Aspect表示当前类为切面类。

之后在切面类中的方法上面添加响应的通知注解:

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class MyAspect {

    @Before("execution(* *..UserServiceImpl.addUser())")
    public void before() {
        System.out.println("========前置通知========");
    }

    @After("execution(* *..UserServiceImpl.selectUser())")
    public void after() {
        System.out.println("========最终通知========:");
    }

    @AfterThrowing(value = "execution(* *..UserServiceImpl.selectUserById(..))" ,throwing = "e")
    public void afterThrowing(Exception e) {
        System.out.println("========异常通知========:" + e);
    }

    @AfterReturning(value = "execution(* *..UserServiceImpl.updateUser())",returning = "result")
    public void afterReturning(int result) {
        System.out.println("========后置通知========:" + result);
    }

    @Around(value = "execution(* *..UserServiceImpl.deleteUser())")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("========环绕通知:前========:");
        Object proceed = pjp.proceed();
        System.out.println("========环绕通知:后========:");

        return proceed;
    }
}

接下来需要在spring配置文件中添加下面两个标签:

<context:component-scan base-package="com.monkey1024"/>
<!--配置AspectJ自动代理-->
<aop:aspectj-autoproxy/>

然后使用之前的测试方法测试即可。

使用全注解方式,先创建一个配置类

@Configuration
@ComponentScan("com.monkey1024")
@EnableAspectJAutoProxy
public class AopConfiguration {
}

其中的@EnableAspectJAutoProxy注解的作用是开启aop,相当于是配置文件里面的:

<aop:aspectj-autoproxy/>

之后将Test类中的容器获取部分的代码改成通过注解获取即可:

ApplicationContext context = new AnnotationConfigApplicationContext(AopConfiguration.class);

这样就可以不用xml配置文件了