资讯专栏INFORMATION COLUMN

猫头鹰的深夜翻译:使用SpringBoot和AspectJ实现AOP

meislzhua / 1704人阅读

摘要:我们会写切面来拦截对这些业务类和类的调用。切面定义何时拦截一个方法以及做什么和在一起成为切面连接点当代码开始执行,并且切点的条件满足时,通知被调用。

前言

这篇文章会帮助你使用Spring Boot Starter AOP实现AOP。我们会使用AspectJ实现四个不同的通知(advice),并且新建一个自定义的注解来追踪方法的执行时间。

你将会了解

什么是交叉分割关注点(cross-cutting concern)?

在应用中你如何实现交叉分割关注点?

   - 如果你想要将对web应用所有的访问请求记入日志,你能想到什么方法?
   - 如果你想追踪每个请求的性能,你能想到什么方法?

AOP中的切面(Aspects)和切点(Pointcut)是什么?

有哪些不同类型的AOP通知(advice)?

如何使用Spring Boot实现AOP?

如何使用Spring AOP和AspectJ实现切面?

有哪些AOP最佳实践?

项目代码结构

下图是我们即将创建的项目结构的截图:

一些细节:

SpringBootTutorialBasicsApplication.java: 由Spring Initializer初始化生成的Spring Boot应用类。这个类是应用启动类。

pom.xml: 创建项目所需的全部依赖。我们将会使用Spring Boot Starter AOP依赖。

Business1.java, Business2.java, Dao1.java, Dao2.java: 业务类依赖于Dao类。我们会写切面来拦截对这些业务类和DAO类的调用。

AfterAopAspect.java: 实现一些After通知。

UserAccessAspect.java: 实现一个Before通知,用来做访问权限检查

BusinessAopSpringBootTest.java:对业务方法进行单元测试

Maven3.0+:编译工具

Eclipse: 开发工具

JDK1.8+

源码Github地址
介绍AOP

应用通常划分为多个层进行开发,一个经典的JAVA应用有:

网络层:用REST或是应用的形式将服务暴露给外部使用

业务层:业务逻辑

数据层:数据持久化逻辑

虽然各个层的职责不同,但是每个层之间也有一些共通的地方

日志

安全

这些共通的切面成为交叉分割关注点(cross-cutting-concerns)

实现交叉分割关注点的一个方法是在每一个层分贝进行实现。但是,这样会使得代码难以维护。

面向切面编程为实现交叉分割关注点提供了一个解决方案:

将交叉分割切入点实现为一个切面

定义切点,说明这些切面在何时调用

这样确保了交叉分割关注点定义在一个内聚的代码组件中,并且能够在需要的时候使用。

初始化Spring Boot AOP项目

使用Spring Initializer新建一个Spring AOP项目非常的方法。

Spring Initializer是创建Spring Boot项目的超级棒的工具。

备注:

启动Spring Initializer并且选择一下内容

选择com.in28minutes.springboot.tutorial.basics.example为Group

选择 spring-boot-tutorial-basics为Artifact

选择AOP依赖

点击Generate Project

将项目导入Eclipse

Spring Boot AOP starter

Spring Boot AOP Starter的关键依赖有:

Spring AOP提供的基本的AOP功能

AspectJ提供的完整的AOP框架


    org.springframework
    spring-aop
    5.0.1.RELEASE
    compile


    org.aspectj
    aspectjweaver
    1.8.12
    compile
配置AOP

让我们添加一些业务逻辑类 - Business1和Business2。这些业务逻辑类依赖于一组数据类 - Data1和Data2。

@Service
public class Business1 {
    private Logger logger = LoggerFactory.getLogger(this.getClass());
    @Autowired
    private Dao1 dao1;
    public String calculateSomething() {
        String value = dao1.retrieveSomething();
        logger.info("In Business - {}", value);
        return value;
    }
}
@Service
public class Business2 {
    @Autowired
    private Dao2 dao2;
    public String calculateSomething() {
        //Business Logic
        return dao2.retrieveSomething();
    }
}
@Repository
public class Dao1 {
    public String retrieveSomething() {
        return "Dao1";
    }
}
@Repository
public class Dao2 {
    public String retrieveSomething() {
        return "Dao2";
    }
}

备注:

@Autowired private Dao1 dao1: DAO作为依赖注入业务类中

public String calculateSomething(): 每个业务类包含一个简单的calculate方法

一个简单的AOP单元测试

让我们写一个简单的单元测试来调用刚刚创建的业务类:

@RunWith(SpringRunner.class)
@SpringBootTest
public class BusinessAopSpringBootTest {
    private Logger logger = LoggerFactory.getLogger(this.getClass());
    @Autowired
    private Business1 business1;
    @Autowired
    private Business2 business2;
    @Test
    public void invokeAOPStuff() {
        logger.info(business1.calculateSomething());
        logger.info(business2.calculateSomething());
    }
}

备注:

@RunWith(SpringRunner.class) @SpringBootTest public class BusinessAopSpringBootTest:: 我们将在单元测试用启动一个完整的Spring Boot应用

@Autowired private Business1 business1@Autowiredprivate Business2 business2: 将业务类注入启动测试的Spring上下文中

@Test public void invokeAOPStuff(){...}: 调用业务层的方法

这时,我们没有实现任何的AOP逻辑,因此,测试的输出应该就是从DAO类和业务类中返回的简单的信息:

c.i.s.t.b.e.a.BusinessAopSpringBootTest  : In Business - Dao1
c.i.s.t.b.e.a.BusinessAopSpringBootTest  : Dao1
实现@Before通知

通常来讲,当我们使用AOP来实现安全时,我们会想要拦截对方法的调用并进行检查。这可以直接通过@Before通知实现。

下面给出了一种实现:

@Aspect
@Configuration
public class UserAccessAspect {
    private Logger logger = LoggerFactory.getLogger(this.getClass());
    //What kind of method calls I would intercept
    //execution(* PACKAGE.*.*(..))
    //Weaving & Weaver
    @Before("execution(* com.in28minutes.springboot.tutorial.basics.example.aop.data.*.*(..))")
    public void before(JoinPoint joinPoint) {
        //Advice
        logger.info(" Check for user access ");
        logger.info(" Allowed execution for {}", joinPoint);
    }
}

备注:

@Aspect: 说明这是一个切面

@Configuration: 说明这是一个对切面的Spring Bean配置

@Before: 我们想要在方法执行前执行切面

("execution(* com.in28minutes.springboot.tutorial.basics.example.aop.data.*.*(..))": 定义了切点。我们想要拦截com.in28minutes.springboot.tutorial.basics.example.aop.data包中的所有方法。

当我们运行单元测试时,你会看见,在执行DAO方法之前,会执行用户权限检查:

Check for user access 
Allowed execution for execution(String com.in28minutes.springboot.tutorial.basics.example.aop.data.Dao1.retrieveSomething())
c.i.s.t.b.e.a.BusinessAopSpringBootTest  : In Business - Dao1
c.i.s.t.b.e.a.BusinessAopSpringBootTest  : Dao1
Check for user access 
Allowed execution for execution(String com.in28minutes.springboot.tutorial.basics.example.aop.data.Dao2.retrieveSomething())
c.i.s.t.b.e.a.BusinessAopSpringBootTest  : Dao2
理解AOP术语: Pointcut, Advice, Aspect,Join Point

让我们花点时间来了解一下AOP术语:

切点(Pointcut):该表达式用来定义何时方法应当被拦截。在上例中,切点为`execution(* com.in28minutes.springboot.tutorial.basics.

example.aop.data..(..))`。

通知(Advice):你想要做什么?一个通知是你在拦截方法时想要调用的逻辑。在上例中,通知为before(JoinPoint joinPoint)方法中的代码。

切面(Aspect):定义何时拦截一个方法(Pointcut)以及做什么(Advice)和在一起成为切面

连接点(Join Point):当代码开始执行,并且切点的条件满足时,通知被调用。连接点是一个通知运行的特定实例。

织如(Weaver):实现AOP的框架 - AspectJ或Spring AOP

使用@After, @AfterReturning和@AfterThrowing通知

让我们现在来看看AOP提供的别的拦截选项:

@After: 在两种场景下执行 - 当一个方法成功执行或是抛出异常

@AfterReturning: 只有在方法成功执行后运行

@AfterThrowing: 只有在方法抛出异常后运行

让我们创建一个包含这些元素的简单的切面:

@Aspect
@Configuration
public class AfterAopAspect {
    private Logger logger = LoggerFactory.getLogger(this.getClass());
    @AfterReturning(value = "execution(* com.in28minutes.springboot.tutorial.basics.example.aop.business.*.*(..))",
        returning = "result")
    public void afterReturning(JoinPoint joinPoint, Object result) {
        logger.info("{} returned with value {}", joinPoint, result);
    }
    @After(value = "execution(* com.in28minutes.springboot.tutorial.basics.example.aop.business.*.*(..))")
    public void after(JoinPoint joinPoint) {
        logger.info("after execution of {}", joinPoint);
    }
}

执行后运行结果如下所示:

Check for user access 
Allowed execution for execution(String com.in28minutes.springboot.tutorial.basics.example.aop.data.Dao1.retrieveSomething())
In Business - Dao1
after execution of execution(String com.in28minutes.springboot.tutorial.basics.example.aop.business.Business1.calculateSomething())
execution(String com.in28minutes.springboot.tutorial.basics.example.aop.business.Business1.calculateSomething()) returned with value Dao1
c.i.s.t.b.e.a.BusinessAopSpringBootTest  : Dao1
Check for user access 
Allowed execution for execution(String com.in28minutes.springboot.tutorial.basics.example.aop.data.Dao2.retrieveSomething())
after execution of execution(String com.in28minutes.springboot.tutorial.basics.example.aop.business.Business2.calculateSomething())
execution(String com.in28minutes.springboot.tutorial.basics.example.aop.business.Business2.calculateSomething()) returned with value Dao2
c.i.s.t.b.e.a.BusinessAopSpringBootTest  : Dao2

可以看到,就在将值返回给调用的业务逻辑之前,after通知被执行了。

其它AOP功能:@Around和注解

能够使用AOP实现的功能之一是通过自定义注释来解析方法调用。

下面的例子展示了一个简单的TrackTiem注释:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TrackTime {

我们可以添加一个切面来定义当添加TrackTime注解以后执行的逻辑。MethodExecutionCalculationAspect实现了一个简单的时间追踪功能。

@Aspect
@Configuration
public class MethodExecutionCalculationAspect {
    private Logger logger = LoggerFactory.getLogger(this.getClass());
    @Around("@annotation(com.in28minutes.springboot.tutorial.basics.example.aop.TrackTime)")
    public void around(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        joinPoint.proceed();
        long timeTaken = System.currentTimeMillis() - startTime;
        logger.info("Time Taken by {} is {}", joinPoint, timeTaken);
    }
}

备注:

@Around: 是一个环绕型通知。它拦截方法调用后使用joinPoint.proceed()来执行方法

@annotation(com.in28minutes.springboot.tutorial.basics.example.aop.TrackTime): 基于注解进行拦截的切点 - @annotation紧跟着完整的注解的名称

定义了注解和通知之后,我们可以将注解运用到想要跟踪的方法上,如下所示:

@Service
public class Business1 {
    @TrackTime
    public String calculateSomething(){
AOP最贱实践

AOP最佳实践之一是将所有的切点定义在一个类中。这样有利于在一个地方维护所有的切点。

public class CommonJoinPointConfig {
    @Pointcut("execution(* com.in28minutes.spring.aop.springaop.data.*.*(..))")
    public void dataLayerExecution() {}
    @Pointcut("execution(* com.in28minutes.spring.aop.springaop.business.*.*(..))")
    public void businessLayerExecution() {}
}

在定义其它切面的切入点时,可以这样调用上面的定义:

@Around("com.in28minutes.spring.aop.springaop.aspect.CommonJoinPointConfig.businessLayerExecution()")

完整的代码请前往GITHUB浏览


想要了解更多开发技术,面试教程以及互联网公司内推,欢迎关注我的微信公众号!将会不定期的发放福利哦~

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/76317.html

相关文章

  • 头鹰深夜翻译:为什么要使用Spring Boot?

    摘要:初次使用的人往往会困惑,不知道该使用哪种方法。目前来说,团队推荐使用基于的方法来提供更高的灵活性。配置,从而在应用启动时执行脚本来初始化数据库。目前为止我们没有任何消息需要配置,所以只在文件夹中创建一个空的文件。将配置为,它包含的上下文。 前言 spring是一个用于创建web和企业应用的一个很流行的框架。和别的只关注于一点的框架不同,Spring框架通过投资并组合项目提供了大量的功能...

    Jaden 评论0 收藏0
  • 头鹰深夜翻译:在JVM上根据合约编程

    摘要:前言这周我准备介绍一个有趣的但是很少使用的方法按照合约编程,又称为合约编程,是一种软件设计的方法。这些规则被称为合约,可以比拟为商业合同中的条件和义务。通过将检查和异常抛出指令包装到方法中,人们可以很容易地实现合约式编程。 前言 这周我准备介绍一个有趣的但是很少使用的方法 按照合约编程,又称为合约编程,是一种软件设计的方法。它规定了软件设计师应该为软件组件定义正式,精确和可验证的接口规...

    whatsns 评论0 收藏0
  • 头鹰深夜翻译:Java WeakHashMap

    摘要:本文简介类概览类构造器总结类构造方法类使用举例类概览是一个实现了接口,并且键为型的哈希表。中的条目不再被正常使用时,会被自动删除。它的键值均支持。和绝大多数的集合类一样,这个类不是同步的。 本文简介 WeakHashMap类概览 WeakHashMap类构造器总结 WeakHashMap类构造方法 WeakHasjMap类使用举例 1. WeakHashMap类概览 Wea...

    BothEyes1993 评论0 收藏0
  • 头鹰深夜翻译:JDK Vs. JRE Vs. JVM之间区别

    摘要:什么是为执行字节码提供一个运行环境。它的实现主要包含三个部分,描述实现规格的文档,具体实现和满足要求的计算机程序以及实例具体执行字节码。该类先被转化为一组字节码并放入文件中。字节码校验器通过字节码校验器检查格式并找出非法代码。 什么是Java Development Kit (JDK)? JDK通常用来开发Java应用和插件。基本上可以认为是一个软件开发环境。JDK包含Java Run...

    blair 评论0 收藏0
  • 头鹰深夜翻译:Java中CAS(Compare And Swap)

    摘要:否则它就会用新的值替代当前值。在这种情况下,锁可能会优于原子变量,但在实际的争用级别中,原子变量的性能优于锁。在中引入了另外一个构件。 题目要求 在我们深入了解CAS(Compare And Swap)策略以及它是如何在AtomicInteger这样的原子构造器中使用的,首先来看一下这段代码: public class MyApp { private volatile int ...

    hosition 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<