抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

面向切面(AOP)

AOP的本质也是为了解耦,它是一种设计思想

AOP(Aspect Oriented Programming),意为面向切面编程。最早由AOP联盟组织提出的,指定的一套规范,Spring将AOP的思想引入到预编译方式和运行期间东岱代理实现程序的统一维护的一种技术。

从要点实例分析,类似于添加日志这种解耦类的问题,如果没有Spring框架, 我们需要在每个service的方法中添加记录日志的方法;由Spring之后,通过@Aspect注解定义切面,这个切面中定义了拦截所有service中的方法,并记录日志,从代码角度而言,记录日志与业务代码之间已经解耦

1
2
3
4
5
6
7
8
9
10
11
12
@Aspect
public class LogAspect {
/**
* set aspect to get the name of the service method being done
*/
@Around("execution(* x.service.*.*(..))")
public Object businessService(ProceedingJoinPoint pjp) throws Throwable{
Method method = ((MethodSignature) pjp.getSignature()).getMethod();
System.out.println("execute method:"+method.getName());
return pjp.proceed();
}
}

将记录日志功能解耦为日志切面,它的目标是解耦。进而引出AOP的理念:将分散在业务逻辑代码中相同的代码通过横向切割的方式抽取到一个独立的模块中。

OOP面向对象编程,针对业务处理过程的实体及其属性和行为进行抽象封装,以获得更加清晰高效的逻辑单元划分。而AOP则是针对业务处理过程中的切面进行提取,它所面对的是处理过程的某个步骤或阶段,以获得逻辑过程中的各种部分之间低耦合的隔离效果。两种设计思想在目标上有本质的区别。

术语

首先明确,AOP的概念和术语并非Spring特有的。

  • 连接点(Jointpoint):表示需要在程序中插入横切关注点的拓展点,连接点可能是类初始化、方法执行、方法调用、字段调用和处理异常等等,Spring只支持方法执行连接点,在AOP中表示在哪里干

  • 切入点(Pointcut):选择一组相关连接点的模式,即可以任务连接点的集合,Spring支持perl5正则表达式和AspectJ切入点模式,Spring默认使用AspectJ语法,在AOP中表示在哪里干的集合

  • 通知(Advice):在连接点上执行的代码,通知提供了在AOP中需要在切入点所选择的连接点出进行拓展现有行为的手段;包括前置通知、后置通知、环绕通知,在Spring中通过代理模式实现AOP,并通过连接器模式以环绕连接点的拦截器链织入通知;在AOP中表示干什么

    💡 通知的类型

    • 前置通知(before advice):在某个连接点之前执行的通知,但这个通知不能阻止连接点之前执行流程(除非它抛出一个异常)。

    • 后置通知(after returning advice):在某连接点正常执行完成后执行的通知。

    • 异常通知(after throwing advice): 在方法抛出异常退出时执行的通知。

    • 最终通知(after [finally] advice): 当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。

    • 环绕通知(around advice): 包围一个连接点的通知,如方法调用。这是最强大的一种通知类型。环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它自己的返回值或抛出异常来结束执行。

      环绕通知是最常用的通知类型。和AspectJ一样,Spring提供所有类型的通知,我们推荐你使用尽可能简单的通知类型来实现需要的功能。例如,如果你只是需要一个方法的返回值来更新缓存,最好使用后置通知而不是环绕通知,尽管环绕通知也能完成同样的事情。用最合适的通知类型可以使得编程模型变得简单,并且能够避免很多潜在的错误。比如,你不需要在JoinPoint上调用用于环绕通知的proceed()方法,就不会有调用的问题。

  • 切面(Aspect):横切关注点的模块化,比如上述代码中的日志组件。可以认为i是通知、引入和切入点的组合;在Spring中可以使用Schema和@AspectJ方式进行组织实现;在AOP中表示在哪干了什么

  • 引入(inter-type declaration):也成为内部类声明,为已有的类添加额外新的字段或者方法,Spring允许引入新的接口(必须对应一个实现)到所有被代理对象(目标对象),在AOP中表示为引入什么

  • 目标对象(Target Object):需要被织入横切关注点的对象,即该对象是切入点选择的对象,需要被通知的对象,从而也可以称为被通知对象。由于Spring AOP通过代理实现,从而这个对象永远是被代理对象,在AOP中表示对谁干

  • 织入(Weaving):把切面连接到其他的应用程序类型或者对象上,并创建一个被通知的对象。这些可以在编译时(例如使用AspectJ编译器),类加载时和运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入。在AOP中表示如何实现

  • AOP代理(AOP Proxy):AOP框架使用代理模式创建的兑现给,从而实现在连接带你处插入通知(即应用切面),就是通过代理来对目标对象应用切面。在Spring中,AOP代理可以用JDK动态代理或者CGLIB代理实现,而通过连接器模型应用切面。在AOP中表示为怎么实现的一种典型方式

Spring AOP和AspectJ是什么关系

  • 首先AspectJ是什么

    AspectJ是一个java实现的AOP框架,它能够对java代码进行AOP编译(一般在编译期进行),让java代码具有AspectJ的AOP功能(当然需要特殊的编译器)。可以这样说AspectJ是目前实现AOP框架中最成熟,功能最丰富的语言,而且AspectJ与java程序完全兼容,几乎是无缝关联。

  • 其次,为什么需要理清楚Spring AOP和AspectJ的关系

  • Spring AOP和AspectJ是什么关系

    1. AspectJ是更强的AOP框架,是实际意义的AOP标准
    2. Spring为何不写类似AspectJ的框架? Spring AOP使用纯Java实现, 它不需要专门的编译过程, 它一个重要的原则就是无侵入性(non-invasiveness); Spring 小组完全有能力写类似的框架,只是Spring AOP从来没有打算通过提供一种全面的AOP解决方案来与AspectJ竞争。Spring的开发小组相信无论是基于代理(proxy-based)的框架如Spring AOP或者是成熟的框架如AspectJ都是很有价值的,他们之间应该是互补而不是竞争的关系
    3. Spring小组喜欢@AspectJ注解风格更胜于Spring XML配置; 所以在Spring 2.0使用了和AspectJ 5一样的注解,并使用AspectJ来做切入点解析和匹配但是,AOP在运行时仍旧是纯的Spring AOP,并不依赖于AspectJ的编译器或者织入器(weaver)
    4. Spring 2.5对AspectJ的支持:在一些环境下,增加了对AspectJ的装载时编织支持,同时提供了一个新的bean切入点。
  • 更多关于AspectJ

    了解AspectJ应用到java代码的过程(这个过程称为织入),对于织入这个概念,可以简单理解为aspect(切面)应用到目标函数(类)的过程。

    对于这个过程,一般分为动态织入静态织入

    1. 动态织入的方式是在运行时动态将要增强的代码织入到目标类中,这样往往是通过动态代理技术完成的,如Java JDK的动态代理(Proxy,底层通过反射实现)或者CGLIB的动态代理(底层通过继承实现),Spring AOP采用的就是基于运行时增强的代理技术

    2. ApectJ采用的就是静态织入的方式。ApectJ主要采用的是编译期织入,在这个期间使用AspectJ的acj编译器(类似javac)把aspect类编译成class字节码后,在java目标类编译时织入,即先编译aspect类再编译目标类。

AOP的配置方式

Spring AOP支持对XML模式和基于@AspectJ注解的两种配置方式。

XML Schema配置方式

  • 目标类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public class AopDemoServiceImpl {

    public void doMethod1() {
    System.out.println("AopDemoServiceImpl.doMethod1()");
    }

    public String doMethod2() {
    System.out.println("AopDemoServiceImpl.doMethod2()");
    return "hello world";
    }

    public String doMethod3() throws Exception {
    System.out.println("AopDemoServiceImpl.doMethod3()");
    throw new Exception("some exception");
    }
    }
  • 切面类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    public class LogAspect {

    /**
    * 环绕通知.
    *
    * @param pjp pjp
    * @return obj
    * @throws Throwable exception
    */
    public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
    System.out.println("-----------------------");
    System.out.println("环绕通知: 进入方法");
    Object o = pjp.proceed();
    System.out.println("环绕通知: 退出方法");
    return o;
    }

    /**
    * 前置通知.
    */
    public void doBefore() {
    System.out.println("前置通知");
    }

    /**
    * 后置通知.
    *
    * @param result return val
    */
    public void doAfterReturning(String result) {
    System.out.println("后置通知, 返回值: " + result);
    }

    /**
    * 异常通知.
    *
    * @param e exception
    */
    public void doAfterThrowing(Exception e) {
    System.out.println("异常通知, 异常: " + e.getMessage());
    }

    /**
    * 最终通知.
    */
    public void doAfter() {
    System.out.println("最终通知");
    }
    }
  • XML配置AOP

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    <?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"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd
    ">

    <context:component-scan base-package="x" />

    <aop:aspectj-autoproxy/>

    <!-- 目标类 -->
    <bean id="demoService" class="x.service.AopDemoServiceImpl">
    <!-- configure prop</bean>

    <!-- 切面 -->
    <bean id="logAspect" class="x.aspect.LogAspect">
    xfig>
    <!-- 配置切面 -->
    <aop:aspect ref="logAspect">
    <!-- 配置切入点 -->
    <aop:pointcut id="pointCutMethod" expression="execution(* x.service.*.*(..))"/>
    <!-- 环绕通知 -->x method="doAround" pointcut-ref="pointCutMethod"/>
    <!-- 前置通知 -->
    <aop:before method="doBefore" pointcut-ref="pointCutMethod"/>
    <!-- 后置通知;returning属性:用于设置后置通知的第二个参数的名称,类型是Object -->
    <aop:after-returning method="doAfterReturning" pointcut-ref="pointCutMethod" returning="result"/>
    <!-- 异常通知:如果没有异常,将不会执行增强;throwing属性:用于设置通知第二个参数的的名称、类型-->
    <aop:after-throwing method="doAfterThrowing" pointcut-ref="pointCutMethod" throwing="e"/>
    <aop:after method="doAfter" pointcut-ref="pointCutMethod"/>
    </aop:aspect>
    </aop:config>
    </beans>
  • 测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public static void main(String[] args) {
    ApplicationContext context = new ClassPathXmlApplicationContext("aspects.xml");
    AopDemoServiceImpl service = context.getBean("demoService", AopDemoServiceImpl.class);
    service.doMethod1();
    service.doMethod2();
    try {
    service.doMethod3();
    } catch (Exception e) {
    // e.printStackTrace();
    }
    }

AspectJ注解方式

注解方式配置aop

  • 切面

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    @Aspect
    @Component
    public class LogAspect {

    /**
    * 环绕通知.
    */
    @Around("execution(* x.service.*.*(..))")
    public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
    System.out.println("-----------------------");
    System.out.println("环绕通知: 进入方法");
    Object o = pjp.proceed();
    System.out.println("环绕通知: 退出方法");
    return o;
    }

    /**
    * 前置通知.
    */
    @Before("execution(* x.service.*.*(..))")
    public void doBefore() {
    System.out.println("前置通知");
    }

    /**
    * 后置通知.
    */
    @AfterReturning(value = "execution(* x.service.*.*(..))",returning = "result")
    public void doAfterReturning(String result) {
    System.out.println("后置通知, 返回值: " + result);
    }

    /**
    * 异常通知.
    */
    @AfterThrowing(value = "execution(* x.service.*.*(..))",throwing = "e")
    public void doAfterThrowing(Exception e) {
    System.out.println("异常通知, 异常: " + e.getMessage());
    }

    /**
    * 最终通知.
    */
    @After("execution(* x.service.*.*(..))")
    public void doAfter() {
    System.out.println("最终通知");
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    @EnableAspectJAutoProxy
    @Configuration
    public class App {
    public static void main(String[] args) {

    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext("x");
    AopServiceImpl service = context.getBean(AopServiceImpl.class);
    service.doMethod1();
    service.doMethod2();
    try {
    service.doMethod3();
    } catch (Exception e) {
    // e.printStackTrace();
    }
    }
    }

    🌟execution语法:

    关于 @After("execution()")execution()的作用是去找到与之匹配的切点

    语法:

    execution(<修饰符模式>?<返回类型模式><方法名模式>(<参数模式>)<异常模式>?) ,例如下↓:

    1
    // execution(* com.service.impl..*.*(..))

    含义:

    1. *,代表任意
    2. com.service.impl,代表AOP的切入点包位置
    3. com.service.impl..,表示当前包以及子包
    4. com.service.impl..*,表示当前包以及子包下的所有的类
    5. com.service.impl..*(..),表示表示当前包以及子包下的所有的类的任何方法,(..)中的点点表示任意参数类型

❗AOP切不到static方法

🔗:获取到方法的参数,返回值

评论