Spring Aop

相关概念

AOP是面向切面编程的一种设计思想,Sring的IOc容器不需要依赖于SpringAOP,主要有一下几个相关术语

  • 连接点(Joinpoint):指能够被拦截的点,在spring中,这些点指的都是方法,因为spring只支持方法类型的连接点,相当于目标对象类中的所有方法(可以被切入的点)
  • 切入点(Pointcut):指我们要对哪些连接点进行拦截的定义(已经切入的点)
  • 通知/增强(Advice):指拦截到连接点之后要做的事情就是通知,可以分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能)
  • 引介(Introduction):引介是一种特殊的通知,在不修改类代码的情况下,Introduction可以在运行期为类动态的添加一些方法和Field
  • 切面(Aspect):切入点和通知(引介)的结合
  • 目标对象(Target):代理的目标对象
  • 代理(Proxy):一个类被AOP织入增强后,就产生一个结果代理类
  • 织入(Weaving):把增强的功能添加到目标对象来创建新的代理对象的过程,spring采用动态代理织入

通知主要有一下几个类型

  • 前置通知:在目标方法之前调用
  • 后置通知:在目标方法之后调用,有两种,一种是如果出现异常就不调用,一种是无论是否出现异常都调用
  • 环绕通知:在目标方法之前、之后调用
  • 异常通知:出现异常则通知

半自动方式开发

第一步:创建通知类

/**
 * 通过继承通知类进行通知类型控制
 */
public class BeforeAdvice implements MethodBeforeAdvice {

    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println("前置通知");
    }
}

public class AfterAdvice implements AfterReturningAdvice {
    /**
     *
     * @param returnValue 目标方法返回的内容
     * @param method
     * @param args
     * @param target
     * @throws Throwable
     */
    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("后置通知" + returnValue);
    }
}

public class ArroundAdvice implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("环绕前");
        //手动执行目标方法
        Object proceed = invocation.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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- 1.创建通知类的对象   -->
    <bean id="beforeAdvice" class="com.xm.advice.LogAdvice"/>
    <bean id="afterAdvice" class="com.xm.advice.AfterAdvice"/>
    <bean id="arroundAdvice" class="com.xm.advice.ArroundAdvice"/>
    <!-- 2.创建目标类的对象   -->
    <bean id="studentServiceTarget" class="com.xm.service.StudentServiceImpl"/>
    <!-- 3.创建代理类的对象   -->
    <bean id="studentServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <!-- 3.1 整合目标类 -->
        <property name="target" ref="studentServiceTarget"></property>
        <!-- 3.2 整合通知类 -->
        <property name="interceptorNames">
            <list>
                <!--<value>logAdvice</value> -->
                <value>afterAdvice</value>
                <!--<value>arroundAdvice</value>-->
            </list>
        </property>
        <!-- 3.3 关联接口 -->
        <property name="proxyInterfaces">
            <array>
                <value>com.xm.service.StudentService</value>
            </array>
        </property>
    </bean>
</beans>

测试:

/**
 * Hello world!
 *
 */
public class App 
{
    public static void main( String[] args )
    {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

        //获取代理类
        StudentService studentService = (StudentService) context.getBean("studentServiceProxy");
        studentService.study();

        //测试后置通知
        studentService.afterAdvice();
    }
}

除了使用配置代理类的方式,还可以使用配置切入点和切入面的方式

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- 1.创建通知类的对象   -->
    <bean id="beforeAdvice" class="com.xm.advice.LogAdvice"/>
    <bean id="afterAdvice" class="com.xm.advice.AfterAdvice"/>
    <bean id="arroundAdvice" class="com.xm.advice.ArroundAdvice"/>
    <!-- 2.创建目标类的对象   -->
    <bean id="studentServiceTarget" class="com.xm.service.StudentServiceImpl"/>
    <!-- 3. 使用切面配置 -->
    <aop:config>
        <aop:pointcut id="myPoint" expression="execution(* com.xm.service.*.*(..))"/>
        <aop:advisor advice-ref="beforeAdvice" pointcut-ref="myPoint"/>
    </aop:config>
</beans>

使用AspectJ的方式进行编程

xml方式的AOP开发

第一步:准备目标对象
创建接口和实现类

public interface UserService {
    void save();
    void delete();
    void update();
    void select();
}

public class UserServiceImpl implements UserService {
    @Override
    public void save() {
        System.out.println("保存用户");
    }

    @Override
    public void delete() {
        System.out.println("删除用户");
    }

    @Override
    public void update() {
        System.out.println("更新用户");
    }

    @Override
    public void select() {
        System.out.println("查询用户");
    }
}

第二步:编写通知

import org.aspectj.lang.ProceedingJoinPoint;

public class TransactionAdvice {

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

    //后置通知
    public void after(){
        System.out.println("后置通知(无论是否出现异常)");
    }

    //后置通知
    public void afterReturning(){
        System.out.println("后置通知(出现异常不调用");
    }

    //异常通知
    public void afterException(){
        System.out.println("异常通知");
    }

    //环绕通知
    public Object around(ProceedingJoinPoint point) throws Throwable {
        System.out.println("环绕:调用目标方法之前");
        Object proceed = point.proceed();
        System.out.println("环绕:调用目标方法之后");
        return proceed;
    }
}

第三步:配置织入,将通知织入到目标对象

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

    <!-- 目标对象 -->
    <bean name="userService" class="com.xm.service.UserServiceImpl"/>

    <!-- 通知对象 -->
    <bean name="transactionAdvice" class="com.xm.advice.TransactionAdvice"/>

    <!-- 将通知对象织入目标对象 -->
    <aop:config>
        <!-- 选择切入点 -->
        <aop:pointcut id="pointcut"
                      expression="execution(public void com.xm.service.UserServiceImpl.update())"/>
        <!-- 选择切入面 -->
        <aop:aspect ref="transactionAdvice">
            <!-- 选择切入方法 -->
            <!-- 前置通知 -->
            <aop:before method="before" pointcut-ref="pointcut"/>
            <!-- 后置通知 出现异常不调用 -->
            <!-- 如果存在返回参数returnValue,可以进行配置显示 -->
            <aop:after-returning method="afterReturning" pointcut-ref="pointcut" returning="returnValue"/>
            <!-- 后置通知 无论是否出现异常 -->
            <aop:after method="after" pointcut-ref="pointcut"/>
            <!-- 环绕通知 -->
            <aop:around method="around" pointcut-ref="pointcut"/>
            <!-- 异常通知 -->
            <aop:after-throwing method="afterException" pointcut-ref="pointcut"/>
        </aop:aspect>
    </aop:config>
</beans>

关于切入点的相关配置:

  • 表示服务层中任何业务服务执行的切入点可以定义如下(切入所有方法):

    第一个*表示返回值可以是任意类型,第二个*表示在该包下的任意类,第三个*表示任意方法
    若使用service..*.*(..)作则表示该包及子包的任意方法,..表示任意参数(有参或无参)

<aop:config>

    <aop:pointcut id="businessService"
        expression="execution(* com.xm.service.*.*(..))"/>

</aop:config>

第四步:测试

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.annotation.Resource;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AopTest {

    @Resource(name = "userService")
    private UserService userService;

    @Test
    public void testUpdate(){
        userService.update();
    }

    @Test
    public void saveTest(){
        userService.save();
    }
}

注解方式开发

  1. 开启织入注解
<aop:aspectj-autoproxy/>
  1. 对通知类进行注解
  • @Aspect:声明切入面
  • @Pointcut:声明切入点
  • @Before:声明前置通知
  • @AfterReturning:声明后置通知(无异常时执行)
  • @After:声明后置通知(无论是否异常)
  • @AfterThrowing:异常通知
  • @Around:环绕通知
package com.xm.advice;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;

@Aspect
public class TransactionAdvice {

    //声明一个切入点,方便后面的调用
    @Pointcut("execution(* com.xm.service.*.*(..))")
    public void pointcut(){}

    //前置通知;
    // @Before("execution(* com.xm.service.UserServiceImpl.*(..))")
    @Before("TransactionAdvice.pointcut()")
    public void before(){
        System.out.println("前置通知");
    }

    //后置通知
    @After("TransactionAdvice.pointcut()")
    public void after(){
        System.out.println("后置通知(无论是否出现异常)");
    }

    //后置通知
    @AfterReturning("TransactionAdvice.pointcut()")
    public void afterReturning(){
        System.out.println("后置通知(出现异常不调用)");
    }

    //异常通知
    @AfterThrowing("TransactionAdvice.pointcut()")
    public void afterException(){
        System.out.println("异常通知");
    }

    //环绕通知
    @Around("TransactionAdvice.pointcut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        System.out.println("环绕:调用目标方法之前");
        Object proceed = point.proceed();
        System.out.println("环绕:调用目标方法之后");
        return proceed;
    }
}