什么是面向切面编程(AOP)?

SpringAOP机制详解

1、 什么是 AOP

AOP 为 Aspect Oriented Programming 的缩写,意思为面向切面编程,是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。

  • 静态 AOP 实现:AOP 框架在编译阶段对程序源代码进行修改,生成了静态的 AOP 代理类(生成的 *.class 文件已经被改掉了,需要使用特定的编译器),比如 AspectJ。
  • 动态 AOP 实现:AOP 框架在运行阶段动态生成代理对象(在内存中以 JDK 动态代理,或 CGlib 动态地生成 AOP 代理类),如 SpringAOP。

2、 AOP编程思想

  • AOP 面向切面编程是一种编程思想,是 OOP 的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。

  • 利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

3、Spring中的常用术语

  • Joinpoint(连接点):所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点

  • Pointcut(切入点):所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义

  • Advice(通知/ 增强):所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。

    通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。

    Introduction(引介): 引介是一种特殊的通知在不修改类代码的前提下, Introduction 可以在运行期为类动态地添加一些方 法或 Field。

  • Target(目标对象):代理的目标对象

  • Proxy (代理):一个类被 AOP 织入增强后,就产生一个结果代理类

  • Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程。spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入

  • Aspect(切面):是切入点和通知(引介)的结合

4、AOP 的作用及优势

**作用:**在程序运行期间,在不修改源码的情况下对方法进行功能增强

**优势:**减少重复代码,提高开发效率,并且便于维护

5、AOP切入点配置

切入点:对目标哪些方法拦截定义

// com.chenshuang.dao下任何子包的任何实现类中的 任何返回值 任何方法(任何参数) 都会拦截
@Pointcut("execution(* com.chenshuang.dao..*.*(..))")
public void addMethod(){
    
    
}

切入点表达式注解

//PersonDaoImpl实现类中的 返回值为void的 任何方法(任何参数) 都会拦截
@Pointcut("execution(void com.chenshuang.dao.impl.PersonDaoImpl.*(..))")

//PersonDaoImpl实现类中的 返回值不为void的 任何方法(任何参数) 都会拦截
@Pointcut("execution(!void com.chenshuang.dao.impl.PersonDaoImpl.*(..))")

//PersonDaoImpl实现类中的 任何返回值 以get名字开头的任何方法(任何参数) 都会拦截
@Pointcut("execution(* com.chenshuang.dao.impl.PersonDaoImpl.get*(..))")

//PersonDaoImpl实现类中的 任何返回值 任何方法(返回参数为String类型的) 都会拦截
@Pointcut("execution(* com.chenshuang.dao.impl.PersonDaoImpl.*(String))")

//PersonDaoImpl实现类中的 任何返回值 任何方法(返回参数为String 和 int类型的) 都会拦截
@Pointcut("execution(* com.chenshuang.dao.impl.PersonDaoImpl.*(String,int))")

// com.chenshuang.dao下任何子包的任何实现类中的 任何返回值 任何方法(任何参数) 都会拦截
@Pointcut("execution(* com.chenshuang.dao..*.*(..))")

6、AOP中通知类型

在这里插入图片描述

**@Before 前置通知:**在目标方法前调用执行

//    前置通知
    @Before(value= "addMethod()")
    public void before(){
    
    
        System.out.println("----切面1-------前置通知---------");
    }

@AfterReturning 后置通知:在目标方法后调用执行

//    后置通知
    @AfterReturning(pointcut = "addMethod()",returning = "result")
    public void afterReturning(String result){
    
    
        System.out.println("----切面1-------后置通知---------:"+result);
    }

@AfterThrowing 异常通知 :在方法抛出异常是执行

//    异常通知
    @AfterThrowing(value = "addMethod()")
    public void AfterThrowing(){
    
    
        System.out.println("----切面1-------异常通知---------");
    }

**@After最终通知:**再方法执行执行完或者异常之后调用

//    最终通知
    @After(value = "addMethod()")
    public void after(){
    
    
        System.out.println("----切面1-------最终通知---------");
    }

**@Around 环绕通知:**将目标方法封装起来, 需要手动的调用目标方法

ProceedingJoinPoint 处理进程的连接点 可以用他的proceed()方法调用 目标方法

@Around(value = "addMethod()")
public Object around(ProceedingJoinPoint pjp){
    
    

    Object result =null;
    try {
    
    
        System.out.println("--切面1------进入-环绕通知--方法");
        // 可以再调用前判断有没有权限,有权限再调用目标方法
        result= pjp.proceed();// 回调目标方法--相当于动态代理中的 method.invoke
        System.out.println("--切面1------退出-环绕通知--方法");
    } catch (Throwable e) {
    
    
        e.printStackTrace();
        System.out.println("--切面1------异常-环绕通知--方法");
    } finally {
    
    
        System.out.println("--切面1------最终-环绕通知--方法");
    }
    return result;
}

7、配置AOP演示案例

(1)导入依赖

<properties>
        <spring.version>5.2.5.RELEASE</spring.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>${spring.version}</version>
    </dependency>

    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.8.7</version>
    </dependency>
</dependencies>

(2)配置applicationContext.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: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/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">
     <!--  添加扫描器 [默认开启注解开关]  -->
    <context:component-scan base-package="com.chenshuang"></context:component-scan>

    <!--    打开AOP开关  -->
     <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>

bean配置,图下配置了两个切面

<?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: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/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">
    
        <!--  把 PersonDaoImpl 交给spring容器管理  -->
    <bean id="PersonDao" class="com.chenshuang.dao.impl.PersonDaoImpl"></bean>
    <!--  把PersonAspect 交给spring 容器管理  -->
    <bean id="PersonAspect1" class="com.chenshuang.service.PersonAspect"></bean>
    <!--  把PersonAspect2 交给spring 容器管理  -->
    <bean id="PersonAspect2" class="com.chenshuang.service.PersonAspect2"></bean>

    <aop:config>
        <aop:aspect id="myAspect1" ref="PersonAspect1" order="3">
            <aop:pointcut id="addMethod" expression="execution(* com.chenshuang.dao..*.*(..))"/>
            <!--  前置通知  -->
            <aop:before method="before" pointcut-ref="addMethod"></aop:before>
            <!--  后置通知  带返回值-->
            <aop:after-returning method="afterReturning" pointcut-ref="addMethod" returning="result"></aop:after-returning>
            <!--  最终通知  -->
            <aop:after method="after" pointcut-ref="addMethod"></aop:after>
            <!--  环绕通知  -->
            <aop:around method="around" pointcut-ref="addMethod" ></aop:around>
        </aop:aspect>

        <!--     配置aop order 小序号先执行   -->
        <aop:aspect id="myAspect2" ref="PersonAspect2" order="1">
            <aop:pointcut id="addMethod" expression="execution(* com.chenshuang.dao..*.*(..))"/>
            <!--  前置通知  -->
            <aop:before method="before" pointcut-ref="addMethod"></aop:before>
            <!--  后置通知  带返回值-->
            <aop:after-returning method="afterReturning" pointcut-ref="addMethod" returning="result"></aop:after-returning>
            <!--  最终通知  -->
            <aop:after method="after" pointcut-ref="addMethod"></aop:after>
            <!--  环绕通知  -->
            <aop:around method="around" pointcut-ref="addMethod" ></aop:around>
        </aop:aspect>
    </aop:config>
</beans>

(3)配置切面类

使用@Aspect注解 声明当前类是一个切面类

可以配置多个切面

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

@Service
@Aspect  //声明当前类是一个切面类
public class PersonAspect {
    
    

//    切入点:对目标哪些方法拦截定义

    // com.chenshuang.dao下任何子包的任何实现类中的 任何返回值 任何方法(任何参数) 都会拦截
    @Pointcut("execution(* com.chenshuang.dao..*.*(..))")
    public void addMethod(){
    
    
    }

//    前置通知
    @Before(value= "addMethod()")
    public void before(){
    
    
        System.out.println("----切面1-------前置通知---------");
    }

//    后置通知
    @AfterReturning(pointcut = "addMethod()",returning = "result")
    public void afterReturning(String result){
    
    
        System.out.println("----切面1-------后置通知---------:"+result);
    }


//    最终通知
    @After(value = "addMethod()")
    public void after(){
    
    
        System.out.println("----切面1-------最终通知---------");
    }

//    异常通知
    @AfterThrowing(value = "addMethod()")
    public void AfterThrowing(){
    
    
        System.out.println("----切面1-------异常通知---------");
    }
    /*
        环绕通知: 需要手动的 调用目标方法
            ProceedingJoinPoint 处理进程的连接点 可以用他的proceed()方法调用 目标方法
    * */
    @Around(value = "addMethod()")
    public Object around(ProceedingJoinPoint pjp){
    
    

        Object result =null;
        try {
    
    
            System.out.println("--切面1------进入-环绕通知--方法");
            // 可以再调用前判断有没有权限,有权限再调用目标方法
            result= pjp.proceed();// 回调目标方法--相当于动态代理中的 method.invoke
            System.out.println("--切面1------退出-环绕通知--方法");
        } catch (Throwable e) {
    
    
            e.printStackTrace();
            System.out.println("--切面1------异常-环绕通知--方法");
        } finally {
    
    
            System.out.println("--切面1------最终-环绕通知--方法");
        }
        return result;
    }
}

(4)编写测试类

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {
    
    "classpath:applicationContext.xml"})
public class Test01 {
    
    
    @Resource
    private PersonDao personDao = new PersonDaoImpl();
    
    @Test
    public void test(){
    
    
        personDao.queryPerson(1);
    }
 
}

因为log日志问题,所以顺序不准确

在这里插入图片描述

8、执行顺序

1、进入环绕通知

2、前置通知

3、执行方法===

4、退出环绕通知

5、后置通知

6、最终环绕通知

猜你喜欢

转载自blog.csdn.net/PIKapikaaaa/article/details/125626784
今日推荐