AspectJ详解(作用原理与5大使用语法)

AspectJ详解(作用原理与5大使用语法)-mikechen

什么是AspectJ?

AspectJ是AOP()的Java语言的实现,AspectJ定义了AOP语法,它有一个专门的编译器用来生成遵守Java字节编码规范的Class文件。

在Spring2.0以上版本中,可以使用基于AspectJ注解或基于XML配置的AOP。

 

AspectJ的作用?

AspectJ就是AOP的Java实现,所以要理解AspectJ的作用,重点是要理解AOP。

AOP(Aspect-Oriented Programming)即面向切面编程,是对传统 OOP(Object-Oriented Programming)面向对象编程的补充。

面向切面编程,实现在不修改源代码的情况下给程序动态统一添加额外功能的一种技术,如下图所示:

AspectJ详解(作用原理与5大使用语法)-mikechen

AOP编程操作的主要对象是切面(aspect),AOP可以拦截指定的方法并且对方法增强,而且无需侵入到业务代码中,使业务与非业务处理逻辑分离。

 

AspectJ的核心语法

1)横切关注点

对哪些方法进行拦截,拦截后怎么处理。

2)切面(Aspect)

类是对物体特征的抽象,切面就是对横切关注点的抽象。

3)连接点(JoinPoint)

JPoints就是程序运行时的一些执行点,就是指被拦截到的点,比如:方法、字段、构造器等。

4)切入点(PointCut)

如果连接点相当于数据中的记录,那么切点相当于查询条件,一个切点可以匹配多个连接点。

5)通知(Advice)

增强是织入到目标类连接点上的一段程序代码,比如:Spring提供的增强接口都是带方位名的如:BeforeAdvice、AfterReturningAdvice、ThrowsAdvice等。

它 一般指拦截到 JoinPoint 后要执行的代码,分为 如下五中类型:

  • 前置通知(Before):在目标方法被调用之前调用通知功能;
  • 后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么;
  • 返回通知(AfterReturning):在目标方法成功执行之后调用通知;
  • 异常通知(AfterThrowing):在目标方法抛出异常后调用通知;
  • 环绕通知(Around):通知报过了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。

 

AspectJ的原理

经过前面的简单介绍,我们已初步掌握了AspectJ的一些语法和概念,但这样仍然是不够的,还需要了解代码是如何织入的。

一般分为动态织入和静态织入,动态织入的方式是在运行时动态将要增强的代码织入到目标类中,这样往往是通过动态代理技术完成的,如Java JDK的动态代理(Proxy,底层通过反射实现)或者CGLIB的动态代理(底层通过继承实现)。

Spring AOP采用的就是基于运行时增强的代理技术,而今天我讲到的ApectJ,采用的就是静态织入的方式。

AspectJ详解(作用原理与5大使用语法)-mikechen
ApectJ主要采用的是编译期织入,在这个期间使用AspectJ的acj编译器(类似javac)把aspect类编译成class字节码后,在java目标类编译时织入,即先编译aspect类再编译目标类。

ajc编译器会首先调用javac将Java源代码编译成字节码,然后根据我们在Aspect中定义的pointcut找到相对应的Java Byte Code部分,使用对应的advice动作修改源代码的字节码。

最后得到了经过Aspect织入的Java字节码,然后就可以正常使用这个字节码了。

 

AspectJ的使用示例

AspectJ 的用法非常简单,就像我们使用 JDK 编译、运行 Java 程序一样。

下面通过一个简单的程序来示范 AspectJ 的用法,并分析 AspectJ 如何在编译时进行增强。

1. 创建目标接口和实现类

package com.mikechen.service;

/*接口实现类*/
public class SomeServiceImpl implements SomeService {
    @Override
    public void doSome(String name,Integer age) {
        System.out.println("Hello "+name+"欢迎来到 mikechen的互联网架构");
    }
}

package com.mikechen.service;
/*目标类的接口*/
public interface SomeService {
    void doSome(String name,Integer age);
}

2. 创建切面类

package com.mikechen.Utils;

import jdk.internal.org.objectweb.asm.tree.analysis.Value;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

import java.text.SimpleDateFormat;
import java.util.Date;

/**切面类,实现显示执行时间的方法
* @Aspect:是aspectJ中的注解
* 作用:标识当前类是切面类
* 切面类:是用来给业务方法增加功能的类,在这个类中有切面的功能代码
* 位置:在类定义的上面
* */
@Aspect
public class ShowTime {
    /**
     * 切面方法:
     * 要求公共的方法(public),
     * 无返回值的方法,
     * 方法名称自定义
     * 可以有参数,参数不是自定义的
     */

    /**
     * @Before:前置通知注解
     * 属性:value 指定了切入点的位置
     */
    @Before(value = "execution(public void com.bjpowernode.service.SomeServiceImpl.doSome(String,Integer))")
    public void showCurrentTime(){
        Date date = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss SSS");
        String sdate =sdf.format(date);
        System.out.println("前置执行:执行了切面代码,展示当前时间:"+sdate);
    }
}

3. 使用spring容器管理类的创建

<?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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--目标类对象-->
    <bean id="mySomeServiceImpl" class="com.bjpowernode.service.SomeServiceImpl"/>
    <!--切面类对象-->
    <bean class="com.bjpowernode.Utils.ShowTime"/>
    <!--声明自动代理生成器:使用aspectJ框架内部的功能,创建目标对象的代理对象
    创建代理对象是在内存中实现的,修改目标对象的内存中的结构。创建为代理对象,所以目标对象
    就是被修改后的代理对象   aspectj-autoproxy:会把spring容器中的所有的目标对象,一次性都生成代理对象
  -->
    <aop:aspectj-autoproxy/>
</beans>

4. 创建目标类对象并使用

package com.mikechen;

import com.bjpowernode.service.SomeService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {
    @Test
    public void test01(){
        String conf = "ApplicationContext.xml";
        ApplicationContext ac = new ClassPathXmlApplicationContext(conf);
        SomeService someService = (SomeService) ac.getBean("mySomeServiceImpl");
        someService.doSome("ada",22);

    }
}

5.执行结果

Hello ada欢迎来到mikechen的互联网架构

 

作者简介

陈睿|mikechen,10年+大厂架构经验,BAT资深面试官,就职于阿里巴巴、淘宝、百度等一线互联网大厂。

👇阅读更多mikechen架构文章👇

阿里架构 |双11秒杀 |分布式架构 |负载均衡 |单点登录 |微服务 |云原生 |高并发 |架构师

以上

关注作者「mikechen」公众号,获取更多技术干货!

后台回复架构,即可获取《阿里架构师进阶专题全部合集》,后台回复面试即可获取《史上最全阿里Java面试题总结

评论交流
    说说你的看法