详解什么是Spring AOP

前言

本篇博客将会从概念,官网以及实例出发,详细的讲解什么是AOP,什么是Spring AOP,并且用一个Spring AspectJ的小例子简单的展示Spring AOP编程的语法以及实现。更多Spring内容进入【Spring解读系列目录】

什么是AOP

在讲什么是AOP(Aspect Oriented Programming)前,我们得先介绍OOP(Object Oriented Programming)面向对象编程,说清楚了才能讲明白他俩的区别,画一个典型的OOP流程图。假设有一个浏览器,发送一个http请求到servlet/Controller上接收参数做验证,然后调用逻辑Service层,再到后面调用dao层取数据做事务等等。
在这里插入图片描述
从图中我们可以看到OOP开发中的代码逻辑是自上而下的十分严谨的流程,任何一环出问题,都会影响到下一环的结果,因而没有办法再继续整个流程。在这个自上而下的过程中,就会不可避免地产生一些横切的问题。

什么是横切呢?其实横切的部分就是那些不会影响到我们的主业务逻辑的部分。比如我们有一个完整的请求逻辑,如下图:
在这里插入图片描述
在controller这里要记录一些日志,比如要记录servlet在什么时间被哪个用户调用的等等。再比如在service这里,我还需要一个东西去认证这个用户有没有权限去做要做的这个操作,然后我还要做性能检查。到dao层,还有事务要去处理等等,可以说横向的需求想要多少就有多少。但是这些记录log,检查性能之类的操作,有没有成功,是不是正确执行了,和我要查的订单完全没有关系,就算删了这些功能,这个目标订单还是能查询出来,和业务逻辑没有关系,也不会影响业务逻辑的执行。这部分就叫做横切需求,所以AOP的应用场景一般也就是日志记录,权限认证,性能检查,事务管理,异常处理等等。

AOP的编程思想

所以AOP的编程思想就是把这些横切问题和主业务逻辑分开,达到与主业务逻辑解耦的目的,使代码的重用性和开发效率更高。就比如刚才图上所示,现在只有一个controller要记录log。如果按照OOP的思想,每一个controller都会有一个切面需求,那么如果未来需求变换了,那要修改的代码就难以计量了。而AOP主要关注的就是这些切面需求,把这些AOP需求抽象出来成为一个切面进行变成。以上就是OOP和AOP的区别。

Spring AOP中的重要术语

Spring官网把这些术语描述的非常的清楚就在【AOP Concepts】 我们可以简单做一个解读,以下英文部分摘录自官网文档。

Aspect(切面)

• Aspect: A modularization of a concern that cuts across multiple classes. Transaction management is a good example of a crosscutting concern in enterprise Java applications. In Spring AOP, aspects are implemented by using regular classes (the schema-based approach) or regular classes annotated with the @Aspect annotation (the @AspectJ style).

切面:一个横跨多个类的切点的模块化的概念。通俗说就是通知、切点、连接点把这些都抽象出来形成一个概念就叫做切面,也就是他们的一个载体。具体来说通知、切点、连接点等等在我们的程序里必须要有一个位置去发挥他们的作用,而这个位置就是切面。按理说这是一个概念没有什么实现,但是在AspcetJ中是以一个类表示,在Spring中是以一个xml的配置表现得。

Join point(连接点)

• Join point: A point during the execution of a program, such as the execution of a method or the handling of an exception. In Spring AOP, a join point always represents a method execution.

连接点:在程序执行过程中,比如一个方法的执行或者一个异常的捕获,在SpringAOP总是一个方法的执行。也就是说连接点就是目标对象中的方法,比如我们有个登陆login方法,里面有个记录日志的需求。那么这个login方法就是连接点。

Advice(通知)

• Advice: Action taken by an aspect at a particular join point. Different types of advice include “around”, “before” and “after” advice. (Advice types are discussed later.) Many AOP frameworks, including Spring, model an advice as an interceptor and maintain a chain of interceptors around the join point.

通知:在一个特殊的连接点被一个切面执行的一个动作。有很多不同的动作“around”, “before” and “after”等等。很多AOP框架都把这个Advice作为一个拦截器并且在把这些拦截器作为一个链围住连接点。其实所谓的通知有两个最明显的特点:什么时间通知,以及通知到哪里去?所谓的通知其实就是前置或者后置方法嘛。最后在下面看下具体的都有哪些通知。

扫描二维码关注公众号,回复: 11921187 查看本文章
Pointcut(切点)

• Pointcut: A predicate that matches join points. Advice is associated with a pointcut expression and runs at any join point matched by the pointcut (for example, the execution of a method with a certain name). The concept of join points as matched by pointcut expressions is central to AOP, and Spring uses the AspectJ pointcut expression language by default.

切点:用来匹配多个连接点。Advice会联合一个切点,并且运行被切点所匹配的所有连接点。上面罗嗦了一堆,其实切点就是连接点的集合。通知和切点一起就能运行切点内的所有方法(连接点)。打个比方login方法是个连接点,logout也是个连接点,如果我要用连接点做,那我要维护至少两行去做这个事情。如果我有1千个,1万个方法呢?那岂不是要累死了。切点就是拯救大家的,一个切点配置就可以搞定这1千个,1万个连接点。

Introduction(引入)

• Introduction: Declaring additional methods or fields on behalf of a type. Spring AOP lets you introduce new interfaces (and a corresponding implementation) to any advised object. For example, you could use an introduction to make a bean implement an IsModified interface, to simplify caching. (An introduction is known as an inter-type declaration in the AspectJ community.)

引入:声明额外的方法或者字段表示为一种类型。Spring AOP允许向任何被通知的对象引入新的接口(以及相应的实现)。例如,你可以使用一个介绍使一个bean实施一个IsModified 的借口以简化缓存。这个不太好解释,有机会我们用代码说事儿。参考【Spring AOP Introductions】

Target object(目标对象)

• Target object: An object being advised by one or more aspects. Also referred to as the “advised object”. Since Spring AOP is implemented by using runtime proxies, this object is always a proxied object.

目标对象:被一个或者多个切面通知的对象就是目标对象,也叫advised object。而且由于Spring使用动态代理实施了这个东西,于是在Spring框架里目标对象总是代理对象。看完以后就一个念头:[手动宝强]啥,啥,啥,这写嘞都是啥。其实不能怨官网,这里确实不好解释,还是举个例子。有一个原对象叫object,里面有login和logout方法,此时这里面还没有用AOP。那么经过AOP框架以后,这个元对象里面的方法都被加了日志log切点,于是业务逻辑就被增强了就变成了login2和logout2,那么这个原对象就被增强为一个新的对象。这个新的对象就是代理对象,而那个原对象就是目标对象。解释完自己也晕了,画个图吧:
在这里插入图片描述
这里有个小考点:问login+logout叫啥?答:Pointcut切点对吧,连接点的集合嘛。

AOP proxy(AOP代理)

• AOP proxy: An object created by the AOP framework in order to implement the aspect contracts (advise method executions and so on). In the Spring Framework, an AOP proxy is a JDK dynamic proxy or a CGLIB proxy.

AOP代理对象:被AOP框架产生为了实施切面这一构想的对象,就是上图中的Proxy Object。其中包含了原始对象的代码和增加后的代码的那个对象。

Weaving(织入)

• Weaving: linking aspects with other application types or objects to create an advised object. This can be done at compile time (using the AspectJ compiler, for example), load time, or at runtime. Spring AOP, like other pure Java AOP frameworks, performs weaving at runtime.

织入:通过其应用类型或者对象连接切面去创建一个通知过的对象。这TM又是说的啥?每个字都认识拼一起就不认得了。那么什么是织入呢?简单来说是一个过程。还看上面的那个图,我们说target变成新对象的过程叫做代理,那么织入就是login变成login2的过程。而且这个过程可以在编译的时候(AspectJ Compiler),加载的时候,运行时(纯JavaAOP)去完成。

Spring AOP includes the following types of advice:

这里就是advice的几种类型,同样以下英文摘自官网。详细的例子参考【SpringAOP Advice 通知例子】

Before advice

• Before advice: Advice that runs before a join point but that does not have the ability to prevent execution flow proceeding to the join point (unless it throws an exception).

预先通知:在一个连接点前运行但是并不能阻止执行流继续执行到连接点,除非报错了。简单来讲就是在before方法里可劲儿折腾,只要不报错,就一定会执行连接点方法。

After returning advice

• After returning advice: Advice to be run after a join point completes normally (for example, if a method returns without throwing an exception).

后置返回通知:当一个连接点正常执行一个返回以后才执行的通知。

After throwing advice

• After throwing advice: Advice to be executed if a method exits by throwing an exception.

后置抛出通知:当一个方法退出了或者抛出一个异常了,才会执行的通知。

After (finally) advice

• After (finally) advice: Advice to be executed regardless of the means by which a join point exits (normal or exceptional return).

后置最终通知:当一个连接点退出了,不管是不是正常退出都执行的通知。

Around advice

• Around advice: Advice that surrounds a join point such as a method invocation. This is the most powerful kind of advice. Around advice can perform custom behavior before and after the method invocation. It is also responsible for choosing whether to proceed to the join point or to shortcut the advised method execution by returning its own return value or throwing an exception.

环绕通知:就是环绕在一个连接点周围的通知,也就是一个方法调用。也是最强大的一种通知。环绕通知可以在方法调用前后执行自定义行为。它也可以选择是否执行连接点,或者通过返回其返回值或者抛出的异常来简化被通知过的方法的执行。就是说这个环绕通知,强大到可以决定是不是执行连接点方法(这点before就做不到),而且通过返回值或者报错来简化被通知过的方法的执行(相当于综合了after的类型)。

AOP Proxies

我们刚才说到了代理,那么【Spring AOP】的代理,使用的是什么呢?这点官网有说的,就是Java标准的动态代理。Spring AOP defaults to using standard JDK dynamic proxies for AOP proxies. 官网这里并没有介绍很多,但是下一个章节AspectJ却用了大篇幅去介绍,其实就是侧面说明了Spring官方其实是想让大家都是用AspectJ作为Spring AOP去使用的。那么就有一个问题了,AOP和SpringAop是什么关系呢,又和AspectJ有什么区别呢?

AOP,SpringAOP和AspectJ的关系

通过上面的介绍,我们知道AOP是一种概念一个思想,是我们编程要达到的一个目标。而SpringAOP、AspectJ都是AOP的实现,这点通IOC和DI的关系类似,详见【什么是SpringIOC以及和DI的关系】。虽然SpringAOP有自己的语法,但是语法复杂,所以SpringAOP借助了AspectJ的注解和语法,但是底层实现还是自己的。所以他俩也就只有这么点关系了,就像java和javascript没有半毛钱关系,只是语法相似,于是也叫做javascript。
于是Spring AOP就有了两种编程风格:

@AspectJ support         ------------>  利用aspectj的注解
Schema-based AOP support ------------>  xml <aop:config> 命名空间

那么下面我们就看下Spring是怎么支持AspectJ的语法的。

@AspectJ Support

首先官网就介绍了Java config编程方式,然后才是基于xml的,说明官网那些大牛人们也确实觉得xml太不好用了。开启方式也很简单,只要在配置类上加一个@EnableAspectJAutoProxy就代表:我们的程序已经开启了AspectJ的语法支持。

Enabling @AspectJ Support with Java Configuration
To enable @AspectJ support with Java @Configuration, add the @EnableAspectJAutoProxy annotation, as the following example shows:

@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
    
    
}

Enabling @AspectJ Support with XML Configuration
To enable @AspectJ support with XML-based configuration, use the aop:aspectj-autoproxy element, as the following example shows:

<aop:aspectj-autoproxy/>

实现切面编程小例子

想要实现切面编程,首先要声明一个AspectJ,这里官网上说的也很明白,就在【aop-at-aspectj】往下,这里就不贴官网内容了。第一步就是要声明一个@Aspect注释类,并且定义成一个bean交给Spring管理。然后要声明一个切点,在切点上配置好要切入的地方。最后在声明一个advice,告诉容器切入的位置和内容,就可以完成一个简单的AOP切面编程了。

首先加入依赖:

<dependencies>
  <!-- Spring上下文是一个配置,向Spring框架提供上下文信息。 -->
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.8.RELEASE</version>
  </dependency>
  <!--aspectj语法支持-->
  <dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.6</version>
  </dependency>
</dependencies>

然后我们要构建一个配置类Appconfig

@Configuration
@EnableAspectJAutoProxy
@ComponentScan("com")
public class Appconfig {
    
    
}

接着,需要有一个主逻辑类,这里就直接简化为一个类DemoDao

@Repository
public class DemoDao {
    
    
    public void print(){
    
    
        System.out.println("print");
    }
}

最后就是我们代码的重点切面类了DemoAspect。实现这个类,首先要把类作为一个bean交给Spring容器,也就是说作为切面类之前首先要注册为一个Bean。因此我们要加上@Component或者其子类,笔者习惯加@Repository就直接加了。但是这里加上@Component更合适,因为这个类既不是service,也不是dao,更不是controller,懒得改过来了。那么就可以提出一个小问题:@Component什么时候用呢?答案就是你不知道把这个类归为什么分类的时候用。然后加上@Aspect把这个类变成切面。

添加一个切点方法,名字无所谓,但是不要带参数也不要有内容。加上@Pointcut("")注解,然后在里面输入要执行的位置。这里"execution(* com.demo.dao.*.*(..))" 就代表着我要执行com.demo.dao包下的所有类的所有方法。第一个*代表返回值,所以这里就是所有返回值,有没有都要加上切点。

然后创建一个通知方法,名字无所谓,通知就是通知的位置和内容。我们以在执行前为例,加入@Before()注解,这个注解里配置的就是我们的切点方法的名字。到这里就配置完了。

@Repository
@Aspect //声明一个切面
public class DemoAspect {
    
    
    //然后声明一个方法作为切点
    @Pointcut("execution(* com.demo.dao.*.*(..))")
    public void myPointcut(){
    
    
    }
    @Before("myPointcut()") //通知的位置
    public void beforeDao(){
    
    
        System.out.println("before print");
    }
}

最后再来一个测试类DemoTest。

public class DemoTest {
    
    
    public static void main(String[] args) {
    
    
        AnnotationConfigApplicationContext anno=new AnnotationConfigApplicationContext(Appconfig.class);
        DemoDao demoDao=anno.getBean(DemoDao.class);
        demoDao.print();
    }
}

运行,我们的结果就是:before printprint。这里就完成了一个AOP的小例子。

猜你喜欢

转载自blog.csdn.net/Smallc0de/article/details/108063630