Spring框架(二):AOP面向切面编程

SpringAOP


介绍

AOP:面向切面编程( Aspect Oriented Programming ),在不修改原来逻辑代码的前提下,使用切面的方式添加功能(一般是通用功能,例如:日志,事务,校验等)

AOP实现原理:动态代理

好处:少写代码,降低通用功能与业务代码的耦合

注:AOP在几乎每个框架中都有,介绍的是spring的AOP

常见概念

1.通知advice

也就是你想加的功能

2.连接点 joinpoint

就是允许你加入功能的地方,一般是在一个方法的周期中,包括方法调用前,方法调用后,方法抛出异常等情况下,spring允许加功能的所有地方

3.切入点(Pointcut)

不是每个连接点都需要加强,程序员可以根据自己的情况,指定需要加强的方法以及方法运行的某个时间,这就是切入点.由程序员指定哪些方法的什么时候被增强

4.切面(Aspect)

切面=通知+切入点。通知让你知道需要增强的功能,切入点让你知道切入到哪儿,一整合一张的切面就形成了。其实就是你自定义的需要增强的类.

指定哪些方法的什么时候要被增强+增强的内容

就是invoke方法中去掉method.invoke这行代码以外所有的叫切面

5.引介(introduction)

引介是一个过程的概念,就是把上面的切面真正的加在被代理的方法上

6.目标(target)

就是要增强的类,就是被代理类

7.代理(proxy)

动态代理(cglib,jdk)

8.织入(weaving)

就是通过Proxy.newInstance()方法生成代理类的过程,比如jdk代理就是调用类加载器生成的类过程

注解方式完成spring AOP

1.加入jar包
jar包 概述
aspectjweaver 是spring的切入点表达式需要用的包,指定切入点
aopalliance 是AOP联盟的API包,里面包含了针对面向切面的接口。 通常Spring等其它具备动态织入功能的框架依赖此包
aspectjrt 处理事务和AOP所需的包
cglib cglib动态代理需要的包
spring-aop spring对AOP支持的包
<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>5.1.5.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-beans</artifactId>
        <version>5.1.5.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-expression</artifactId>
        <version>5.1.5.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.1.5.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context-support</artifactId>
        <version>5.1.5.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>1.7.26</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.26</version>
    </dependency>

    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.1.5.RELEASE</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>5.1.5.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.9.4</version>
    </dependency>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjrt</artifactId>
        <version>1.9.4</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/aopalliance/aopalliance -->
    <dependency>
        <groupId>aopalliance</groupId>
        <artifactId>aopalliance</artifactId>
        <version>1.0</version>
    </dependency>
    <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>3.2.5</version>
    </dependency>
</dependencies>
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.6.0</version>
            <configuration>
                <source>1.8</source> <!-- 源代码使用jdk1.8支持的特性 -->
                <target>1.8</target> <!-- 使用jvm1.8编译目标代码 -->
                <compilerArgs> <!-- 传递参数 -->
                    <arg>-parameters</arg>
                    <arg>-Xlint:unchecked</arg>
                    <arg>-Xlint:deprecation </arg>
                </compilerArgs>
            </configuration>
        </plugin>
    </plugins>
</build>
2.编写配置文件与测试类
<?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">
    <!--开启AOP注解支持
        spring 的AOP默认支持JDK代理与cglib代理
        当被代理类实现了接口的情况下使用JDK代理,否则使用CGLIB代理
        可以在<aop:aspectj-autoproxy></aop:aspectj-autoproxy>这个配置中加入
        <aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
        告诉spring强制使用cglib代理

    -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
    <context:component-scan base-package="cn.cdqf.aop"></context:component-scan>
</beans>
3.编写被代理类
package cn.cdqf.aop;

import org.springframework.stereotype.Service;

@Service
public class StudentService {
    
    public void show(){
        System.out.println("show方法被调用");
    }
}
4.编写切面(增强类)
package cn.cdqf.aop;

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

@Aspect //该类为增强类:切面
@Component
public class StudentAspect {

    @Pointcut("execution(* cn.cdqf.aop..*.*(..))")//切入点
    public void aa(){}
    //Before:定义前置通知,在方法调用前被执行
    //execution表达式:
    // 第一个*表示任意返回值
    //第一个.. 表示当前包及其子包
    //第二个*表示任意类
    //第三个*表示任意方法
    //第二个..表示任意参数
    @Before("aa()")
    public void before(){
        System.out.println("前置通知执行了:在方法调用前被执行");
    }
    //AfterReturning:后置通知,在方法正常执行结束后执行
    @AfterReturning("execution(* cn.cdqf.aop..*.*(..))")
    public void afterReturning(){
        System.out.println("正常返回通知执行了:在方法正常执行结束后执行");
    }

    @AfterThrowing("execution(* cn.cdqf.aop..*.*(..))")
    public void afterThrowing(){
        System.out.println("异常通知执行了:在方法抛出异常后执行");
    }
    @After("execution(* cn.cdqf.aop..*.*(..))")
    public void after(){
        System.out.println("后置通知执行了:不管抛没抛出异常都会在方法运行后执行,相当于finally");
    }

}
5.测试
package cn.cdqf.aop;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import static org.junit.Assert.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath*:applicationAop.xml"})
public class StudentServiceTest {
    @Autowired
    private StudentService studentService;
    @Test
    public void show() {
        studentService.show();
    }
}
6.环绕通知

切面代码:invoke的所有代码都可以在这里写

package cn.cdqf.aop;

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

@Aspect //该类为增强类:切面
@Component
public class StudentAspect {
    //Around:就相当于我们实现动态代理的invoke方法
    //比其它通知厉害的地方:
    //1.可以选择是否执行目标方法
    //2.可以改变返回值与参数等等
    @Around("execution(* cn.cdqf.aop..*.*(..))")
    public Object around(ProceedingJoinPoint joinPoint){
        //获得方法参数 既然能获得就能改变
        Object[] args = joinPoint.getArgs();
        //获得方法签名
        MethodSignature signature = (MethodSignature)joinPoint.getSignature();
        //获取所有参数的名称
        String[] parameterNames = signature.getParameterNames();
        //获取方法参数类型数组,配合前面的参数 可以秀几波
        Class[] paramTypeArray = signature.getParameterTypes();

        try {
            //1.如果在方法前面加内容 就相当于前置通知
            //调用方法
            Object proceed = joinPoint.proceed(args);
            //2.如果在方法后面加内容就相当于正常返回通知
            //正常返回方法的返回值
            return proceed;
        } catch (Throwable throwable) {
            //3.如果在异常出现后加内容 就相当于异常通知
            throwable.printStackTrace();
        }finally {
            //4.如果在这里加内容就相当于后置通知
        }
        return null;
    }
}

配置方式实现实现AOP

<!-- 定义目标对象 -->
    <bean name="productDao" class="com.zejian.spring.springAop.dao.daoimp.ProductDaoImpl" />

    <!-- 定义切面 -->
    <bean name="myAspectXML" class="com.zejian.spring.springAop.AspectJ.MyAspectXML" />
    <!-- 配置AOP 切面 -->
    <aop:config>
        <!-- 定义切点函数 -->
        <aop:pointcut id="pointcut" expression="execution(* com.zejian.spring.springAop.dao.ProductDao.add(..))" />

        <!-- 定义其他切点函数 -->
        <aop:pointcut id="delPointcut" expression="execution(* com.zejian.spring.springAop.dao.ProductDao.delete(..))" />

        <!-- 定义通知 order 定义优先级,值越小优先级越大-->
        <aop:aspect ref="myAspectXML" order="0">
            <!-- 定义通知
            method 指定通知方法名,必须与MyAspectXML中的相同
            pointcut 指定切点函数
            -->
            <aop:before method="before" pointcut-ref="pointcut" />

            <!-- 后置通知  returning="returnVal" 定义返回值 必须与类中声明的名称一样-->
            <aop:after-returning method="afterReturn" pointcut-ref="pointcut"  returning="returnVal" />

            <!-- 环绕通知 -->
            <aop:around method="around" pointcut-ref="pointcut"  />

            <!--异常通知 throwing="throwable" 指定异常通知错误信息变量,必须与类中声明的名称一样-->
            <aop:after-throwing method="afterThrowing" pointcut-ref="pointcut" throwing="throwable"/>

            <!--
                 method : 通知的方法(最终通知)
                 pointcut-ref : 通知应用到的切点方法
                -->
            <aop:after method="after" pointcut-ref="pointcut"/>
        </aop:aspect>
    </aop:config>

AOP总结

AOP是面向切面编程思想,强调不影响正常代码的情况下切入通用逻辑,核心实现原理是动态代理,最大好处是减少代码量与解耦合

可以想象AOP的功能巨大

在各大框架中也常常使用AOP,例如mybatis的插件,spring的事务控制,springmvc的拦截器等

在实际开发中常常使用到AOP

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章