Spring核心之AOP

AOP是什么?

AOP(Aspect Oriented Programming)是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象(OOP)的一种补充和完善。它以通过预编译方式和运行期动态代理方式,实现在不修改源码的情况下给程序动态统一添加额外功能的一种技术。
如图所示:
image.png

AOP和OOP字面意思很相近,但其实两者完全是面向不同的领域的设计思想,实际项目中我们通常将面向对象理解为一个静态过程(例如一个系统有多少个模块,一个模块有哪些对象,对象有哪些属性)。面向切面的运行期代理方式,理解为一个动态过程,可以在对象运行时动态织入一些扩展功能或控制对象执行。

Spring AOP 应用原理

Spring AOP底层基于代理机制(动态代理)实现功能扩展
动态代理:不改变源码的基础上对已有方法增强。
1)JDK动态代理:假如目标对象(被代理对象)实现接口,则AOP底层可以采用JDK动态代理机制为目标对象创建代理对象(目标对象和代理对象会实现共同接口)。
2)CGLIB动态代理:假如目标对象(被代理对象)没有实现接口,则AOP底层会采用CGLIB代理机制为目标对象创建代理对象(默认创建的代理类会继承目标对象类型),注意:要继承的目标类不能是final修饰的。

JDK动态代理:

要求目标对象至少要实现一个接口
涉及的类是:Proxy
创建动态代理的方法是:newProxyInstance(ClassLoader,Class[],InvocationHandler)
User user=new UserImpl();
        /**
         * 参数含义:
         *     ClassLoader:类加载器,和被代理对象使用相同的类加载器;
         *     Class[]:字节码数组,被代理类实现的接口(要求代理对象和被代理对象具有相同的行为);
         *     InvocationHandler:是一个接口,就是用于提供增强代码的,一般都是写一个该接口的实现类,该实现类可以是匿名内部类;它的含义就是如何代理;
         */
        被代理对象接口 jdkProxy=(需要强转)Proxy.newProxyInstance(user.getClass().getClassLoader(),
            user.getClass().getInterfaces(),
            new InvocationHandler() {
                Object result=null
            /**
             * 执行被代理对象的任何方法都要经过该方法,该方法具有拦截的功能;
             * 方法的参数:
             *         Object proxy:代理对象的引用,不一定每次都用
             *         Method method:当前执行的方法
             *         Object[] args:当前执行方法所需的参数
             * 返回值:当前执行方法的返回值
             */
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    //增强业务
                    result=method.invoke(被代理对象,使用的参数);//执行被代理对象的方法
                    //增强业务
                    return result;
                }
            });
            jdkProxy.test();//通过代理对象调用被代理对象的方法实现业务增强

CGLIB动态代理:

要求:被代理类不能是最终类;不能被final修饰;
涉及的类:Enhancer
创建对象的方法:create(Class,Callback);

UserImpl user=new UserImpl();
        /**
         *     参数含义:
         *         Class:被代理对象字节码
         *         Callback:如何代理,和InvocationHandler的作用是一样的,它也是一个接口,
         *                     一般使用该接口的子类MethodInterceptor;在使用的时候也是创建接口的匿名内部类;
         */
        
        被代理对象 cglibProxy=(需要强转)Enhancer.create(user.getClass(),new MethodInterceptor() {
            Object result=null;
            /**
             * 执行被代理对象的任何方法,它和JDK动态代理的invoke方法的作用一样
             * 方法参数:
             *         Object proxy, Method method, Object[] args和invoke的方法参数一样;
             *         MethodProxy:当前执行方法的代理对象,也就是参数里面Method method的代理对象;一般不用
             */
            @Override
            public Object intercept(Object proxy, Method method, Object[] args,MethodProxy methoddProxy) throws Throwable {
                    //业务增强
                    result=method.invoke(user, args);//执行被代理对象的方法
                    //业务增强
                    return result;
                }
            });
        cglibProxy.test();//通过代理对象调用被代理对象的方法实现业务增强

如图所示:
image.png

Spring AOP 相关术语:

  • 切面(aspect):横切面对象,一般为一个具体类对象(可以借助@Aspect声明),是切入点和通知的结合。
  • 通知/增强(Advice):在切面的某个特定连接点上执行的动作(扩展功能),其实就是增强的代码。例如:around,before,after等。
  • 连接点(joinpoint):程序执行过程中某个特定的点,一般指向被拦截到的目标方法,其实就是业务类接口中所有的方法。
  • 切入点(pointcut):对多个连接点(Joinpoint)一种定义,一般可以理解为多个连接点的集合,其实就是被增强的方法。
  • 引入(Introduction): 添加方法或字段到被通知的类。 Spring允许引入新的接口到任何被通知的对象。例如,你可以使用一个引入使任何对象实现 IsModified接口,来简化缓存。Spring中要使用Introduction, 可有通过DelegatingIntroductionInterceptor来实现通知,通过DefaultIntroductionAdvisor来配置Advice和代理类要实现的接。一般不用。
  • 目标对象(Target Object): 包含连接点的对象。也被称作被通知或被代理对象。
  • 织入(Weaving): 组装方面来创建一个被通知对象。这可以在编译时完成(例如使用AspectJ编译器),也可以在运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入。
  • 代理(Proxy):一个类被AOP织入增强后,产生一个结果代理类;

Spring中常用的通知类型:

before(前置通知):永远在切入点方法执行之前执行;
afterReturning(后置通知):切入点方法正常执行之后执行;
afterThrowing(异常通知):切入点方法执行产生异常之后执行;他和后置通知永远只能执行一个;
after(最终通知):无论切入点方法是否正常执行,都会在其后面执行。
around(环绕通知):spring框架提供的一种可以在代码中手动控制通知方法什么时候执行的方式;

在spring中为我们提供了一个接口:ProceedingJoinPoint;
该接口可以作为环绕通知的参数来使用。
该接口中有一个方法:proceed(),它的作用就等同于method.invoke方法,
                    是明确调用业务层核心方法(切入点方法);
例如(xml配置方式):
    public Object aroundTest(ProcesdingJoinPoint pjp) {
        Object result=null;
        try {
            System.out.println("前置通知");
            result=pjp.proceed();//调用业务层核心方法
            System.out.println("后置通知");
        
        } catch (Exception e) {
            System.out.println("异常通知");
        }finally{
            System.out.println("最终通知");
        }
        return result;
    }

AOP中切入点表达式:

    切入点表达式:
            关键字:execution(表达式)
            表达式写法:访问修饰符 返回值 包名.包名...类名.方法名(参数列表)
            全匹配方式:execution(public void com.service.impl.xxxServiceImpl.save())
            全通配方式:execution(* *..*.*(..));
                       ".." 写在包的位置表示当前包和子包
                       ".." 写在参数里表示有无参数均可,有参数的话可以是任意类型
            在实际开发中,一般情况下都是对业务层方法进行增强,所以一般的写法是:
                       * com.service.impl.*.*(..)

通用的切入点表达式:

<!-- 定义通用的切入点表达式:
            如果是写在了aop:aspect标签内部,则表示只有当前切面可用
            如果写在了aop:aspect标签外部,则表示所有切面可用;注意:如果写在aop:aspect标签外部必须写在aop:aspect标签的上面
 -->
    <aop:pointcut expression="execution(* com.service.impl.*.*(..))" id="pt"/>
    <!-- 使用通用的切入点表达式 -->
    <aop:before method="增强的方法名称" pointcut-ref="通用切入点表达式的id值(pt)"/>

基于XML方式的AOP配置:

<?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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
    <!-- 配置service -->
    <bean id="xxxService" class="类全限定名"></bean>
    <!-- 基于xml方式的AOP配置步骤 :注意如果要使用spring的aop必须导入aop的jar包-->
    <!-- 第一步:把通知类交给spring管理    以日志类为例 -->
    <bean id="logger" class="类全限定名"></bean>
    <!-- 第二步:导入aop命名空间,并且使用aop:config开始aop配置 -->
        <aop:config>
            <!-- 第三步:使用aop:aspect配置切面。
                        id属性:用于给切面提供一个唯一标识
                        ref属性:用于应用通知bean的id 
            -->
            <aop:aspect id="logAdvice" ref="logger">
                <!-- 第四步:配置通知的类型,指定增强的方法何时执行.
                        method属性:用于指定增强的方法名称;
                        pointcut属性:用于指定切入点表达式
                 -->
                <aop:before method="增强的方法名称" pointcut="execution(public void com.service.impl.xxxServiceImpl.save())"
            </aop:aspect>
        </aop:config>
</beans>

基于注解方式的AOP配置:

引入context命名空间:

<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-3.0.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

配置Spring注册容器时要扫描的包:

<!-- 配置spring创建容器时要扫描的包 -->
<context:component-scan base-package="包路径"></context:component-scan>

开启spring对注解AOP的支持:

<aop:aspect-autoproxy/>

AOP业务增强的实现:

  1. 在业务层(目标对象)加@Service注解将其交给spring管理;
  2. 通知类需要用@Component注解标识;
  3. 使用@Aspect注解表示该通知类配置了切面
  4. 通知类方法上配置通知类型:@Before("切入点表达式")
  5. 在该类中定义一个切入点表达式:
@Pointcut("execution(* com.service.impl.*.*(..))")
private void pt(){}
//引用切入点表达式
@Before("pt()")
public void beforeTest(){
    System.out.println("前置通知业务增强");
}

小_L
10 声望6 粉丝