Spring AOP详解

Spring AOP详解

一、AOP—另一种编程思想

1.1 什么是AOP

AOP(Aspect Orient Programming),直译过来就是面向切面编程,AOP是一种思想,是面向对象编程(OOP)的一种补充,面向对象编程将程序抽象为各个层次的对象,而面向切面编程是将程序抽象成各个切面。

在这里插入图片描述

所谓切面,相当于应用对象间的横切点,我们可以将其单独抽象为单独的模块

1.2 为什么需要AOP

OOP引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。不过OOP允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能。日志代码往往横向地散布在所有对象层次中,而与它对应的对象的核心功能毫无关系对于其他类型的代码,如安全性、异常处理和透明的持续性也都是如此,这种散布在各处的无关的代码被称为横切(cross cutting),在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用

AOP技术恰恰相反,它利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。

1.3 AOP实现分类

AOP 要达到的效果是,保证开发者不修改源代码的前提下,去为系统中的业务组件添加某种通用功能。AOP 的本质是由 AOP 框架修改业务组件的多个方法的源代码。

按照 AOP 框架修改源代码的时机,可以将其分为两类:

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

首先,我们创建一个Person接口。这个接口就是学生(被代理类),和班长(代理类)的公共接口,他们都有交作业的行为。这样,学生交作业就可以让班长来代理执行

/**
 * 创建person接口
 */
public interface Person {
    //交作业
    void giveTask();
}

Student类实现Person接口,Student可以具体实施交作业这个行为.

public class Student implements Person {
    private String name;
    public Student(String name) {
        this.name = name;
    }
 
    public void giveTask() {
        System.out.println(name + "交语文作业");
    }
}

StudentsProxy类,这个类也实现了Person接口,但是还另外持有一个学生类对象,那么他可以代理学生类对象执行交作业的行为

/**
 * 学生代理类,也实现了Person接口,保存一个学生实体,这样就可以代理学生产生行为
 */
public class StudentsProxy implements Person{
    //被代理的学生
    Student stu;
 
    public StudentsProxy(Person stu) {
        // 只代理学生对象
        if(stu.getClass() == Student.class) {
            this.stu = (Student)stu;
        }
    }
 
    //代理交作业,调用被代理学生的交作业的行为
    public void giveTask() {
        stu.giveTask();
    }
}

下面测试一下,看代理模式如何使用

public class StaticProxyTest {
    public static void main(String[] args) {
        //被代理的学生林浅,他的作业上交有代理对象monitor完成
        Person linqian = new Student("林浅");
 
        //生成代理对象,并将林浅传给代理对象
        Person monitor = new StudentsProxy(linqian);
 
        //班长代理交作业
        monitor.giveTask();
    }
}

运行结果:

在这里插入图片描述

这里并没有直接通过林浅(被代理对象)来执行交作业的行为,而是通过班长(代理对象)来代理执行了。这就是代理模式。代理模式就是在访问实际对象时引入一定程度的间接性,这里的间接性就是指不直接调用实际对象的方法,那么我们在代理过程中就可以加上一些其他用途。比如班长在帮林浅交作业的时候想告诉老师最近林浅的进步很大,就可以轻松的通过代理模式办到。在代理类的交作业之前加入方法即可。这个优点就可以运用在spring中的AOP,我们能在一个切点之前执行一些操作,在一个切点之后执行一些操作,这个切点就是一个个方法。这些方法所在类肯定就是被代理了,在代理过程中切入了一些其他操作。

1.3.2 动态代理

动态代理和静态代理的区别是,静态代理的的代理类是我们自己定义好的,在程序运行之前就已经编译完成,但是动态代理的代理类是在程序运行时创建的。相比于静态代理,动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。比如我们想在每个代理方法之前都加一个处理方法,我们上面的例子中只有一个代理方法,如果还有很多的代理方法,就太麻烦了,我们来看下动态代理是怎么去实现的。

首先还是定义一个Person接口:

public interface Person {
//交作业
void giveTask();
    
}

接下来是创建需要被代理的实际类,也就是学生类:

public class Student implements Person {
private String name;
public Student(String name) {
    this.name = name;
}

public void giveTask() {
    System.out.println(name + "交语文作业");
}
}

创建StuInvocationHandler类,实现InvocationHandler接口,这个类中持有一个被代理对象的实例target。InvocationHandler中有一个invoke方法,所有执行代理对象的方法都会被替换成执行invoke方法。

public class StuInvocationHandler<T> implements InvocationHandler {
    //invocationHandler持有的被代理对象
    T target;
 
    public StuInvocationHandler(T target) {
        this.target = target;
    }
 
    /**
     * proxy:代表动态代理对象
     * method:代表正在执行的方法
     * args:代表调用目标方法时传入的实参
     */
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("代理执行" +method.getName() + "方法");
        Object result = method.invoke(target, args);
        return result;
    }
}	

那么接下来我们就可以具体的创建代理对象了。

/**
 * 代理类
 */
public class ProxyTest {
    public static void main(String[] args) {
 
        //创建一个实例对象,这个对象是被代理的对象
        Person linqian = new Student("林浅");
 
        //创建一个与代理对象相关联的InvocationHandler
        InvocationHandler stuHandler = new StuInvocationHandler<Person>(linqian);
 
        //创建一个代理对象stuProxy来代理linqian,代理对象的每个执行方法都会替换执行Invocation中的invoke方法
        Person stuProxy = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, stuHandler);
 
        //代理执行交作业的方法
        stuProxy.giveTask();
    }
}

我们执行代理测试类,首先我们创建了一个需要被代理的学生林浅,将林浅传入stuHandler中,我们在创建代理对象stuProxy时,将stuHandler作为参数,那么所有执行代理对象的方法都会被替换成执行invoke方法,也就是说,最后执行的是StuInvocationHandler中的invoke方法。所以在看到下面的运行结果也就理所当然了。

在这里插入图片描述

二、AOP 术语

  • 切面(Aspect):一个关注点的模块化,这个关注点可能会横切多个对象。事务管理是J2EE应用中一个关于横切关注点的很好的例子。在Spring AOP中,切面可以使用基于模式或者基于@Aspect注解的方式来实现。
  • 连接点(Joinpoint): 在程序执行过程中某个特定的点,比如某方法调用的时候或者处理异常的时候。在Spring AOP中,一个连接点总是表示一个方法的执行。
  • 通知/增强(Advice): 在特定的连接点,AOP框架执行的动作。各种类型的通知包括“around”、“before”和“throws”通知。通知类型将在下面讨论。许多AOP框架包括Spring都是以拦截器做通知模型,维护一个“围绕”连接点的拦截器链。spring aop advice的类型:
    1、前置通知(before advice),在目标方法执行之前执行。
    2、返回后通知(after returning advice),在方法正常执行结束之后的通知,可以访问到方法的返回值。
    3、抛出异常后通知(after throwing advice),在目标方法出现异常时执行的代码,可以访问到异常对象,且可以指定出现特定异常执行此方法。
    4、后置通知:(after[finally] advice),在目标方法执行之后执行(无论是否发生异常)。
    5、环绕通知:(around advice),可以实现上述所有功能。
  • 切入点(Pointcut): 指定一个通知将被引发的一系列连接点的集合。AOP框架必须允许开发者指定切入点:例如,使用正则表达式。 Spring定义了Pointcut接口,用来组合MethodMatcher和ClassFilter,可以通过名字很清楚的理解, MethodMatcher是用来检查目标类的方法是否可以被应用此通知,而ClassFilter是用来检查Pointcut是否应该应用到目标类上
  • 引入(Introduction): 添加方法或字段到被通知的类。 Spring允许引入新的接口到任何被通知的对象。例如,你可以使用一个引入使任何对象实现 IsModified接口,来简化缓存。Spring中要使用Introduction, 可有通过DelegatingIntroductionInterceptor来实现通知,通过DefaultIntroductionAdvisor来配置Advice和代理类要实现的接口
  • 目标对象(Target Object): 包含连接点的对象。也被称作被通知或被代理对象。POJO
  • AOP代理(AOP Proxy): AOP框架创建的对象,包含通知。 在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。
  • 织入(Weaving): 组装方面来创建一个被通知对象。这可以在编译时完成(例如使用AspectJ编译器),也可以在运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入。

在这里插入图片描述

三、AOP的实现步骤

公式:AOP=(切面)=通知方法(5种)+切入点表达式(4种)

通知方法:

1.@before通知:在执行目标方法之前执行
2.@after通知:在执行目标方法之后执行
3.@afterReturning通知:无论什么时候程序执行完成之后都要执行的通知
4.@afterThrowing通知:在目标方法执行之后报错时执行
(上述四大类型通知,不能控制目标方法是否执行。一般用来记录程序的执行状态。一般应用与监控的操作。(不改变程序运行的轨迹)
5.@around通知:在目标方法执行前后执行(环绕通知可控制目标方法是否执行,控制程序的执行的轨迹)

切入点表达式

1.@bean(“beanId”):bean:交给spring容器管理的对象,粒度:粗粒度 按bean匹配 当前bean中的方法都会执行通知
2.@within(“包名.类名”):粒度:粗粒度 可以匹配多个类
3.@execution("返回值类型 包名.类名.方法名(参数列表: 细粒度:方法参数级别
4.@annotation(“包名.类名”):细粒度:按照注解匹配

package com.jt.aop;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Aspect
@Component//交给spring容器管理
public class CacheAOP {
    //@Pointcut("bean(itemCatServiceImpl)")
    //@Pointcut("within(com.jt.service.ItemCatServiceImpl)")
    //@Pointcut("within(com.jt.service.*)")//.*一级包目录,..*所有子孙后代
    @Pointcut("execution(* com.jt.service..*.add*(..))")
    public void pointCut(){

    }

    @Before("pointCut()")
    public void before(){
        System.out.println("我是before通知");
    }
}

四、AOP 实例

4.1 XML中声明AOP的常用元素

AOP元素用途
aop:advisor定义aop通知器
aop:after定义一个后置通知(不管目标方法是否执行成功)
aop:after-returning定义aop的返回通知
aop:after-throwing定义aop异常通知
aop:around定义环绕通知
aop:aspect定义一个切面
aop:aspectj-autoproxy启动@aspectj注解驱动的切面
aop:before定义一个aop前置通知
aop:config顶层aop配置元素,大多数的aop:*都必须包含在aop:config元素内
aop:declare-parents以透明的方式为被通知的对象引入额外的接口
aop:pointcut定义一个切点

4.2 基于xml的配置方式

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"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">
  <!-- 将类交给容器去管理 -->
  <bean id="userService" class="cn.com.demo.service.UserService"/>
   <!-- 将类交给容器去管理 -->
  <bean id="logAopDemo" class="cn.com.demo.aop.LogAop"/>
  
  <!-- 面向切面编程 -->
  <aop:config>
    <aop:aspect ref="logAopDemo">
      <!-- 定义切点 -->
      <aop:pointcut expression="execution(* *..*(..))" id="pointCut"/>
      <!-- 声明前置通知 (在切点方法被执行前调用)-->
      <aop:before method="beforeAdvice" pointcut-ref="pointCut"/>
      <!-- 声明后置通知 (在切点方法被执行后调用)-->
      <aop:after method="afterAdvice" pointcut-ref="pointCut"/>
    </aop:aspect>
  </aop:config>
</beans>

注:aop:aspect 子节点还可配置

<aop:config>
    <aop:aspect ref="logAopDemo">
      <!-- 定义切点 -->
      <aop:pointcut expression="execution(* *..*(..))" id="pointCut"/>
      <!-- 声明前置通知 (在切点方法被执行前调用)-->
      <aop:before method="beforeAdvice" pointcut-ref="pointCut"/>
      <!-- 声明后置通知 (在切点方法被执行后调用)-->
      <aop:after method="afterAdvice" pointcut-ref="pointCut"/>
      <aop:after-returning method="aferReturning" pointcut-ref="pointCut"/>
      <aop:after-throwing method="aferThrowing" pointcut-ref="pointCut"/>
      <aop:around method="around" pointcut-ref="pointCut"/>
    </aop:aspect>
  </aop:config>

4.3 基于注解的配置方式

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

  <bean id="userService" class="cn.com.demo.service.UserService"/>
  <!-- 启用注释驱动自动注入 -->
  <context:annotation-config/> 
  <!-- 配置自动扫描的包 -->
  <context:component-scan base-package="cn.com.demo"></context:component-scan>
  <!-- 开启aop注解方式,此步骤不能少,这样java类中的aop注解才会生效 -->
  <aop:aspectj-autoproxy/>
</beans>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值