Spring 面向切面编程

一,Spring的AOP简介

  1.1 什么是AOP

  1.2 AOP的优点

  1.3 AOP的底层实现

  1.4 AOP的动态代理技术

    1.4.1 JDK动态代理

    1.4.2 cglib动态代理

  1.5 AOP的相关概念

  1.6 AOP开发注意事项

二,基于XML的AOP开发

  2.1 开发流程

  2.2 快速开发

  2.3 XML配置解析

三,基于注解的AOP开发

  3.1 开发流程

  3.2 快速开发

  3.3 注解配置详情

  

一,Spring的AOP简介

  1.1 什么是AOP

  AOP 为 Aspect Oriented Programming 的缩写,意思为面向切面编程,是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。在程序运行期间,在不修改源码的情况下对方法进行功能增强。AOP 是 OOP 的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。

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

  1.2 AOP的优缺点

  减少代码重复,降低耦合度,提高程序代码可重用性,提高开发效率。

  1.3 AOP的底层实现

  实际上,AOP 的底层是通过 Spring 提供的的动态代理技术实现的。在运行期间,Spring通过动态代理技术动态的生成代理对象,代理对象方法执行时进行增强功能的介入,在去调用目标对象的方法,从而完成功能的增强。

  1.4 AOP的动态代理技术

    前面所说AOP是通过动态代理的方式实现AOP的。在Spring的AOP中用两中动态代理的方式来实现AOP。

    1.4.1 JDK动态代理

    使用JDK动态代理,是一种基于接口的动态代理。通过接口来创建一个动态代理对象。如下是实现方式:

    1.目标类接口:

package com.itcast.proxy.jdk;

public interface TargetInterface {
    public void method()
}

    2.目标类:

package com.itcast.proxy.jdk;

public class Target implements TargetInterface{
    public void method() {
        System.out.println("Target running .......");
    }
}

    3.动态代理对象创建

package com.itcast.proxy.jdk;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class ProxyTest {
    public static void main(String[] args) {
        final Target target = new Target();
        // 创建代理对象
        TargetInterface proxyInstance = (TargetInterface) Proxy.newProxyInstance(
                target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // 前置增强
                System.out.println("前置增强。。。。");
                Object invoke = method.invoke(target, args);
                System.out.println("后置增强。。。。");
                return invoke;
            }
        });
        // 测试代理对象,无需更改target类的代码对其方法进行增强
        proxyInstance.method();
    }
}

    1.4.2 cglib动态代理

    对象为非接口时,使用cglib来创建动态代理对象。

    1.目标类

package com.itcast.proxy.cjlib;

public class Target {
    public void method(){
        System.out.println("target running .....");
    }
}

    2.cglib创建动态代理对象

package com.itcast.proxy.cjlib;

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class CjlibTest {
    public static void main(final String[] args) {
        final Target target = new Target(); // 创建目标对象
        Enhancer enhancer = new Enhancer();  // 创建增强器
        enhancer.setSuperclass(Target.class); // 设置父类
        enhancer.setCallback(new MethodInterceptor() { // 设置回调
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                System.out.println("前置增强");
                Object invoke = method.invoke(target, objects);
                System.out.println("后置增强");
                return invoke;
            }
        });
        Target proxy = (Target) enhancer.create(); // 创建代理对象
        proxy.method();
    }
}

  1.5 AOP的相关概念

  Spring 的 AOP 实现底层就是对上面的动态代理的代码进行了封装,封装后我们只需要对需要关注的部分进行代码编写,并通过配置的方式完成指定目标的方法增强。对于Spring的AOP,有以下概念:

 Target(目标对象):  代理的目标对象
 Proxy (代理):    一个类被 AOP 织入增强后,就产生一个结果代理类
 Joinpoint(连接点): 所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点
 Pointcut(切入点):  所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义
 Advice(通知/ 增强): 所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知
 Aspect(切面):    是切入点和通知(引介)的结合
 Weaving(织入):   是指把增强应用到目标对象来创建新的代理对象的过程。spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入

  1.6 AOP开发注意事项

  1.在 spring 中,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式。

  2.Spring 框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。

二,基于XML的AOP开发

  2.1 开发流程

1.导入 AOP 相关坐标
2.创建目标接口和目标类(内部有切点)
3.创建切面类(内部有增强方法)
4.将目标类和切面类的对象创建权交给 spring
5.在 applicationContext.xml 中配置织入关系
6.测试代码

  2.2 快速开发

  导入坐标:

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.5.RELEASE</version>
        </dependency>

        <!--aspectj织入,即功能增强依赖-->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.13</version>
        </dependency>    

  接口和目标类

package com.itcast.spring_aop;

public interface TargetInterface {
    public void method();
}
package com.itcast.spring_aop;

public class Target implements TargetInterface {
    public void method(){
        System.out.println("target running init .....");
        // 下面的代码用于测试异常时的情况
        int i = 1/0;
        System.out.println("target running ..... ");
    }
}    

  创建切面类:即增强方法类:

package com.itcast.spring_aop;

public class MyAspect {
    public void before(){
        System.out.println("前置增强.....");
    }
    public void afterRunning(){
        System.out.println("后置增强.....");
    }
    public void after(){
        System.out.println("无论如何都增强.....");
    }
    public void throwException(){
        System.out.println("报错增强.....");
    }
}

  配置织入关系:

<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"   // 这里是引入aop的命名空间
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/beans/spring-beans.xsd
    
    http://www.springframework.org/schema/aop // 注意这里是引入aop的约束
    http://www.springframework.org/schema/aop/spring-aop.xsd"
> <!--配置目标类--> <bean id="target" class="com.itcast.spring_aop.Target"></bean> <!--配置切面类--> <bean id="myAspect" class="com.itcast.spring_aop.MyAspect"></bean> <!--配置织入关系--> <aop:config>
    
     <!--引用myAspect的Bean为切面对象--> <aop:aspect ref="myAspect"> // ref 表示的是切面类的ID
       <!--配置Target的method方法执行时要进行myAspect的before方法前置增强:即通知的方法名称, pointCut是切点表达式,表示那些地方要增强--> <aop:before method="before" pointcut="execution( * com.itcast.spring_aop.*.*(..))"></aop:before>

<aop:after-returning method="afterRunning" pointcut="execution( * com.itcast.spring_aop.*.*(..))"></aop:after-returning> <aop:after-throwing method="throwException" pointcut="execution( * com.itcast.spring_aop.*.*(..))"></aop:after-throwing> <aop:after method="after" pointcut="execution( * com.itcast.spring_aop.*.*(..))"></aop:after> </aop:aspect> </aop:config> </beans>

  测试:

import com.itcast.spring_aop.Target;
import com.itcast.spring_aop.TargetInterface;
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;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationConfiguration.xml")
public class AopTest {
//    @Autowired
//    private Target target;
//    UnsatisfiedDependencyException 注意这里的目标对象需要用接口来接收,否则回报错。
//   因为底层的动态代理proxy对象是用jdk来实现的
//    .BeanNotOfRequiredTypeException: Bean named 'target' is expected to be of type
//    'com.itcast.spring_aop.Target' but was actually of type 'com.sun.proxy.$Proxy14'
    @Autowired
    private TargetInterface target;
    @Test
    public void test(){
        target.method();
    }
    
}

  2.3 XML配置解析

  2.3.1 切点表达式的写法

execution([修饰符] 返回值类型 包名.类名.方法名(参数))

  访问修饰符可以省略

  返回值类型、包名、类名、方法名可以使用星号* 代表任意

  包名与类名之间一个点 . 代表当前包下的类,两个点 .. 表示当前包及其子包下的类

  参数列表可以使用两个点 .. 表示任意个数,任意类型的参数列表

  如下实例:

execution(public void com.itcast.aop.Target.method())    
execution(void com.itcast.aop.Target.*(..))
execution(* com.itcast.aop.*.*(..))
execution(* com.itcast.aop..*.*(..))
execution(* *..*.*(..))

  2.3.2 通知的类型

  通知类型语法:

<aop:通知类型 method=“切面类中方法名” pointcut=“切点表达式"></aop:通知类型>

  

   2.3.3 切点表达式的抽取

  当多个增强的切点表达式相同时,可以将切点表达式进行抽取,在增强中使用 pointcut-ref 属性代替 pointcut 属性来引用抽取后的切点表达式。

<aop:config>
  <!--引用myAspect的Bean为切面对象-->
  <aop:aspect ref="myAspect"> 
    <aop:pointcut id="myPointcut" expression="execution(* com.itcast.aop.*.*(..))"/>
    <aop:before method="before" pointcut-ref="myPointcut"></aop:before>
  </aop:aspect>
</aop:config>

三,基于注解的AOP开发

  配置的方法过于繁琐,可以考虑注解的方式。

  3.1 开发流程

创建目标接口和目标类(内部有切点)
创建切面类(内部有增强方法)
将目标类和切面类的对象创建权交给 spring
在切面类中使用注解配置织入关系
在配置文件中开启组件扫描和 AOP 的自动代理
测试

  3.2 快速开发

  接口类和目标类:

package com.itcast.spring_aop_anno;

public interface TargetInterface {
    public void method();
}
package com.itcast.spring_aop_anno;

import org.springframework.stereotype.Component;

// 将对象创建交给spring容器
@Component("target")
public class Target implements TargetInterface {
    public void method() {
        System.out.println("Target running .......");
    }

}

  切面类:

package com.itcast.spring_aop_anno;


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

@Component("myAspect")
@Aspect // 表示该类是切面类
public class MyAspect {
    @Before("MyAspect.myPoint()") // 使用抽取方式进行切点表达式获取
    public void before(){
        System.out.println("前置增强.....");
    }
    
    // 直接在注解上以参数的形式
    @AfterReturning("execution(* com.itcast.spring_aop_anno.*.*(..))")
    public void afterRunning(){
        System.out.println("后置增强.....");
    }

    @After("execution(* com.itcast.spring_aop_anno.*.*(..))")
    public void after(){
        System.out.println("无论如何都增强.....");
    }

    @AfterThrowing("execution(* com.itcast.spring_aop_anno.*.*(..))")
    public void throwException(){
        System.out.println("报错增强.....");
    }

    @Pointcut("execution(* com.itcast.spring_aop_anno.*.*(..))")
    public void myPoint(){}
}

  配置组件扫描和自动代理启动:

    <!--组件扫描-->
    <context:component-scan base-package="com.itcast.spring_aop_anno"></context:component-scan>

    <!--aop自动代理-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

  测试代码:

import com.itcast.spring_aop_anno.TargetInterface;
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;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:application.xml")
public class AopAnnoTest {
    @Autowired
    private TargetInterface target;
    
    @Test
    public void test(){
        target.method();
    }
}

  3.3 注解配置详情

  语法:

通知的配置语法:@通知注解(“切点表达式")

  通知类型:

  

------------恢复内容结束------------

原文地址:https://www.cnblogs.com/tashanzhishi/p/12017538.html