前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >AOP切面编程

AOP切面编程

作者头像
用户3467126
发布2019-08-12 19:36:43
5810
发布2019-08-12 19:36:43
举报
文章被收录于专栏:爱编码爱编码

简介

如果说 IoC 是 Spring 的核心,那么面向切面编程就是 Spring 最为重要的功能之一了,在数据库事务中切面编程被广泛使用。

面向切面编程,通过预编译和动态代理实现程序功能的统一维护的一种技术,主要功能:日志记录,性能统计,安全控制,事务处理,异常处理等

AOP 的目的

AOP能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码降低模块间的耦合度,并有利于未来的可拓展性和可维护性

核心概念:

实际使用SpringAOP之前,了解他的概念是必不可少的一个过程,

SpringAOP主要围绕以下概念展开:

切面[Aspect]:一个关注点的模块化,这个关注点可能会横切多个对象

连接点[Joinpoint]:程序执行过程中某个特定的连接点

通知[Advice]:在切面的某个特的连接点上执行的动作

切入点[Pointcut]:匹配连接的断言,在Aop中通知和一个切入点表达式关联

引入[Intruduction]:在不修改类代码的前提下,为类添加新的方法和属性

目标对象[Target Object]:被一个或者多个切面所通知的对象

Aop代理[AOP Proxy]:AOP框架创建的对象,用来实现切面契约(aspect contract)(包括方法执行等)

织入[Weaving]:把切面连接到其他的应用程序类型或者对象上,并创建一个被通知的对象,氛围:编译时织入,类加载时织入,执行时织入

通知类型Advice:

前置通知[before advice]在某个连接点(jion point)之前执行的通知,但不能阻止连接点前的执行(除非抛出一个异常)

正常返回后通知[after returning advice]在某个连接点(jion point)正常执行完后执行通知

抛出异常通知[after throwing advice] 在方法异常退出时执行的通知

返回通知[after(finally) advice]在方法抛出异常退出时候的执行通知(不管正常返回还是异常退出)

环绕通知[around advice]包围一个连接点(jion point)的通知

切入点表达式

例如定义切入点表达式

代码语言:javascript
复制
execution(* com.sample.service.impl..*.*(..))

execution()是最常用的切点函数,其语法如下所示:

整个表达式可以分为五个部分:

1、execution(): 表达式主体。 2、第一个 * 号:表示返回类型,* 号表示所有的类型。 3、包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包,com.sample.service.impl包、子孙包下所有类的方法。 4、第二个 * 号:表示类名,* 号表示所有的类。 5、*(..):最后这个星号表示方法名,* 号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数。

代码语言:javascript
复制
execution(public * *(..))   //切入点为执行所有的public方式时execution(* set*(..))   //切入点执行所有的set开始的方法时execution(* com.xyz.service.Account.*(..))   //切入点执行Account类的所有方法时execution(* com.xyz.service.*.*(..))  //切入点执行com.xyz.service包下的所有方法时execution(* com.xyz.service..*.*(..))   //切入点执行com.xyz.service包以及其子包的所有的方法时

上边这种方式aspectj和springaop通用的,其他方式可以自己查找资料

实例讲解

为了更好的说明 AOP 的概念,我们来举一个实际中的例子来说明:

在上面的例子中,包租婆的核心业务就是签合同,收房租,那么这就够了,灰色框起来的部分都是重复且边缘的事,交给中介商就好了,这就是 AOP 的一个思想:让关注点代码与业务代码分离!

实际的代码

1.业务主体类,就是现在对应的包猪婆的业务逻辑。

代码语言:javascript
复制
package pojo;
import org.springframework.stereotype.Component;
@Component("landlord")public class Landlord {
    public void service() {        // 仅仅只是实现了核心的业务功能        System.out.println("签合同");        System.out.println("收房租");    }}

2.Broker 主要是上面所对应的中间商业务处理类,它任务就是带租客看房以及谈价格,其中@Before和@After是在包猪婆业务的前后分别做好相应的售前和售后的工作(其实这里可以用一个环绕通知来弄,看后面的代码)。

代码语言:javascript
复制
package aspect;
import org.aspectj.lang.annotation.After;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;import org.springframework.stereotype.Component;
@Component@Aspectclass Broker {
    @Before("execution(* pojo.Landlord.service())")    public void before(){        System.out.println("带租客看房");        System.out.println("谈价格");    }
    @After("execution(* pojo.Landlord.service())")    public void after(){        System.out.println("交钥匙");    }}

3.在 applicationContext.xml 中配置自动注入,并告诉 Spring IoC 容器去哪里扫描这两个 Bean:

代码语言:javascript
复制
<?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"       xsi:schemaLocation="http://www.springframework.org/schema/beans       http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
    <context:component-scan base-package="aspect" />    <context:component-scan base-package="pojo" />
    <aop:aspectj-autoproxy/></beans>

4.测试代码,运行包猪婆的主体服务类,可以看到日志会将中间商以及包猪婆的所有工作都打印了出来组成了一个完成的租房流程。

代码语言:javascript
复制
package test;
import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;import pojo.Landlord;
public class TestSpring {
    public static void main(String[] args) {
        ApplicationContext context =                new ClassPathXmlApplicationContext("applicationContext.xml");        Landlord landlord = (Landlord) context.getBean("landlord", Landlord.class);        landlord.service();
    }}

5.执行看到效果:

这个例子使用了一些注解,现在看不懂没有关系,但我们可以从上面可以看到我们在 Landlord 的 service() 方法中仅仅实现了核心的业务代码,其余的关注点功能是根据我们设置的切面自动补全的。

使用注解

其实跟上面的实际代码是一样,不过这里有点不同的话,你可以直接用@ComponentScan注解能扫描指定路径下的标识了Spring Bean注解(@Component或者是@Component参与合成的注解。

下面就再次啰嗦一下分点说明如何使用注解实现AOP

1、连接点

Spring 是方法级别的 AOP 框架,我们主要也是以某个类额某个方法作为连接点,另一种说法就是:选择哪一个类的哪一方法用以增强功能。

代码语言:javascript
复制
    ....    public void service() {        // 仅仅只是实现了核心的业务功能        System.out.println("签合同");        System.out.println("收房租");    }    ....

我们在这里就选择上述 Landlord 类中的 service() 方法作为连接点。

2、创建切面

选择好了连接点就可以创建切面了,我们可以把切面理解为一个拦截器,当程序运行到连接点的时候,被拦截下来,在开头加入了初始化的方法,在结尾也加入了销毁的方法而已,在 Spring 中只要使用 @Aspect 注解一个类,那么 Spring IoC 容器就会认为这是一个切面了:

代码语言:javascript
复制
package aspect;
import org.aspectj.lang.annotation.After;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;import org.springframework.stereotype.Component;
@Component@Aspectclass Broker {
    @Before("execution(* pojo.Landlord.service())")    public void before(){        System.out.println("带租客看房");        System.out.println("谈价格");    }
    @After("execution(* pojo.Landlord.service())")    public void after(){        System.out.println("交钥匙");    }}

注意:被定义为切面的类仍然是一个 Bean ,需要 @Component 注解标注,注入到Spring容器中。

代码部分中在方法上面的注解看名字也能猜出个大概,下面来列举一下 Spring 中的 AspectJ 注解:

注解

说明

@Before

前置通知,在连接点方法前调用

@Around

环绕通知,它将覆盖原有方法,但是允许你通过反射调用原有方法,后面会讲

@After

后置通知,在连接点方法后调用

@AfterReturning

返回通知,在连接点方法执行并正常返回后调用,要求连接点方法在执行过程中没有发生异常

@AfterThrowing

异常通知,当连接点方法异常时调用

3、定义切点

在上面的注解中定义了 execution 的正则表达式,Spring 通过这个正则表达式判断具体要拦截的是哪一个类的哪一个方法:

代码语言:javascript
复制
execution(* pojo.Landlord.service())

依次对这个表达式作出分析:

execution:代表执行方法的时候会触发 * :代表任意返回类型的方法 pojo.Landlord:代表类的全限定名 service():被拦截的方法名称

通过上面的表达式,Spring 就会知道应该拦截 pojo.Lnadlord 类下的 service() 方法。上面的演示类还好,如果多出都需要写这样的表达式难免会有些复杂,我们可以通过使用 @Pointcut 注解来定义一个切点来避免这样的麻烦:

代码语言:javascript
复制
package aspect;
import org.aspectj.lang.annotation.After;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;import org.aspectj.lang.annotation.Pointcut;import org.springframework.stereotype.Component;
@Component@Aspectclass Broker {
    @Pointcut("execution(* pojo.Landlord.service())")    public void lService() {    }
    @Before("lService()")    public void before() {        System.out.println("带租客看房");        System.out.println("谈价格");    }
    @After("lService()")    public void after() {        System.out.println("交钥匙");    }}

4、环绕通知

我们来探讨一下环绕通知,这是 Spring AOP 中最强大的通知,因为它集成了前置通知和后置通知,它保留了连接点原有的方法的功能,所以它及强大又灵活,让我们来看看:

代码语言:javascript
复制
package aspect;
import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.springframework.stereotype.Component;
@Component@Aspectclass Broker {
//  注释掉之前的 @Before 和 @After 注解以及对应的方法//  @Before("execution(* pojo.Landlord.service())")//  public void before() {//      System.out.println("带租客看房");//      System.out.println("谈价格");//  }////  @After("execution(* pojo.Landlord.service())")//  public void after() {//      System.out.println("交钥匙");//  }
    //  使用 @Around 注解来同时完成前置和后置通知    @Around("execution(* pojo.Landlord.service())")    public void around(ProceedingJoinPoint joinPoint) {        System.out.println("带租客看房");        System.out.println("谈价格");
        try {            joinPoint.proceed();        } catch (Throwable throwable) {            throwable.printStackTrace();        }
        System.out.println("交钥匙");    }}

运行测试代码,结果仍然正确:

使用 XML 配置

注解是很强大的东西,但基于 XML 的开发我们仍然需要了解,我们先来了解一下 AOP 中可以配置的元素:

AOP 配置元素

用途

备注

aop:advisor

定义 AOP 的通知其

一种很古老的方式,很很少使用

aop:aspect

定义一个切面

——

aop:before

定义前置通知

——

aop:after

定义后置通知

——

aop:around

定义环绕通知

——

aop:after-returning

定义返回通知

——

aop:after-throwing

定义异常通知

——

aop:config

顶层的 AOP 配置元素

AOP 的配置是以它为开始的

aop:declare-parents

给通知引入新的额外接口,增强功能

——

aop:pointcut

定义切点

——

有了之前通过注解来编写的经验,并且有了上面的表,我们将上面的例子改写成 XML 配置很容易(去掉所有的注解):

代码语言:javascript
复制
<!-- 装配 Bean--><bean name="landlord" class="pojo.Landlord"/><bean id="broker" class="aspect.Broker"/>
<!-- 配置AOP --><aop:config>    <!-- where:在哪些地方(包.类.方法)做增加 -->    <aop:pointcut id="landlordPoint"                  expression="execution(* pojo.Landlord.service())"/>    <!-- what:做什么增强 -->    <aop:aspect id="logAspect" ref="broker">        <!-- when:在什么时机(方法前/后/前后) -->        <aop:around pointcut-ref="landlordPoint" method="around"/>    </aop:aspect></aop:config>

运行测试程序,看到正确结果:

参考文章

https://www.cnblogs.com/xuyatao/p/8485851.html https://www.cnblogs.com/wmyskxz/p/8835243.html

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-08-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 爱编码 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 简介
  • AOP 的目的
  • 核心概念:
    • 通知类型Advice:
      • 切入点表达式
        • 实例讲解
          • 实际的代码
          • 使用注解
            • 1、连接点
              • 2、创建切面
                • 3、定义切点
                • 4、环绕通知
                相关产品与服务
                容器服务
                腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档