运用AspectJ进行面向切面编程(AOP)

运用AspectJ进行面向切面编程(AOP)

第1章 导言

大家好,我是小黑,业务开发中,我们常常会遇到这样的情况:有些代码几乎在每个办法里都要用到,比方日志记载、权限校验、或许功用监测。假如每次都手动加入这些代码,不只功率低下,而且一旦需求修正,那便是一个巨大的噩梦。这时分,面向切面编程(AOP)能够协助我们处理这个问题。

AOP答应我们将这些横切关注点(比方日志、安全等)从业务逻辑中分离出来,经过预界说的办法插入到代码的要害途径中,这样一来,就大大进步了代码的复用性和可保护性。AspectJ,作为AOP的一种完成,它经过供给言语级的支撑,让这全部变得愈加简略和强壮。

那么,AspectJ是什么呢?简略来说,AspectJ是一个基于Java的面向切面编程框架,它扩展了Java言语,引入了切面(Aspect)、织入(Weaving)等新的概念。运用AspectJ,我们能够清晰地界说在何处、何时以及怎么将横切关注点运用到业务逻辑中,而不需求修正实践的业务逻辑代码。

第2章 AOP根底概念

要深化了解AspectJ,我们首要得弄清楚AOP的一些根底概念。AOP的核心便是将运用逻辑从横切关注点中分离出来,以进步代码的模块化。这儿有几个要害词我们需求了解一下:

  • 切面(Aspect):一个关注点的模块化,这个关注点或许会横切多个方针。比方日志,它或许会被运用到整个运用的多个部分。
  • 连接点(Join Point):程序履行的某个特定位置,比方办法的调用或许反常的抛出。在AspectJ中,一个连接点总是代表一个办法的履行。
  • 告诉(Advice):切面在特定连接点履行的动作。告诉类型包括“前置告诉”(在办法履行之前运转的代码),“后置告诉”(在办法履行之后运转的代码),和“盘绕告诉”(在办法履行前后都运转的代码)。
  • 织入(Weaving):将切面运用到方针方针以创立新的署理方针的过程。这能够在编译时(运用AspectJ编译器)、加载时或运转时经过署理完成。

举个简略的比方来说,假定我们想要在每个服务办法履行前后都打印日志。在不运用AOP的情况下,小黑或许需求在每个办法中手动增加日志代码。而经过AOP,只需求界说一个切面,指定“前置告诉”和“后置告诉”来主动完结这个任务。

// 界说一个切面
@Aspect
public class LoggingAspect {
    // 界说前置告诉
    @Before("execution(* com.example.service.*.*(..))")
    public void logBefore(JoinPoint joinPoint) {
        System.out.println("办法履行前: " + joinPoint.getSignature().getName());
    }
    // 界说后置告诉
    @After("execution(* com.example.service.*.*(..))")
    public void logAfter(JoinPoint joinPoint) {
        System.out.println("办法履行后: " + joinPoint.getSignature().getName());
    }
}

运用AspectJ进行面向切面编程(AOP)

小黑悄悄告诉你一个买会员廉价的网站: 小黑整的视頻会园优惠站

第3章 AspectJ环境建立

好,我们现已了解了AOP和AspectJ的根底常识,现在让我们进入下一个阶段:建立AspectJ环境。不管小黑是运用Eclipse、IntelliJ IDEA还是其他IDE,我们都需求保证能顺畅地运转AspectJ程序。

在Eclipse中建立AspectJ

假如小黑运用的是Eclipse,那么建立AspectJ环境相对来说是比较简略的。Eclipse有一个名为”AspectJ Development Tools”(AJDT)的插件,能够让小黑轻松地开端AspectJ的冒险。

  1. 首要,翻开Eclipse,然后导航到“Help” > “Eclipse Marketplace…”。
  2. 在查找框中,输入“AJDT”然后查找。
  3. 找到”AspectJ Development Tools”插件,点击“Install”按钮进行装置。
  4. 装置完结后,重启Eclipse。

在IntelliJ IDEA中建立AspectJ

关于IntelliJ IDEA的用户,装备AspectJ也不会太杂乱,但需求手动增加AspectJ的库到项目中。

  1. 首要,翻开IntelliJ IDEA并创立或翻开一个项目。
  2. 点击File > Project Structure > Libraries,然后点击“+”按钮增加新的库。
  3. 选择从Maven增加库,查找“org.aspectj:aspectjrt”(这是AspectJ的运转时库),选择最新版本并增加到项目中。
  4. 相同,小黑或许还需求增加AspectJ的编译器,查找“org.aspectj:aspectjtools”并增加。

装备AspectJ项目

不论是在Eclipse还是IntelliJ IDEA中,接下来小黑需求装备项目以运用AspectJ编译器。这一般意味着要更改项目的构建装备,以便运用AspectJ编译器来编译Java代码和Aspect代码。

  • 关于Eclipse,AJDT插件会主动处理大部分设置。但小黑需求保证项目的“Project Facets”中启用了AspectJ,并且在“Java Compiler”设置中,AspectJ编译器被选为项目的编译器。

  • 关于IntelliJ IDEA,小黑需求在“Build, Execution, Deployment” > “Compiler” > “Annotation Processors”中启用注解处理器,并保证AspectJ的相关途径被正确设置。

验证装置

为了验证AspectJ环境现已正确建立,小黑能够尝试编写一个简略的AspectJ程序。比方,创立一个简略的切面来在办法履行前打印一条消息:

package com.example.aspect;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class SimpleAspect {
    @Before("execution(* com.example.service.*.*(..))")
    public void beforeAdvice() {
        System.out.println("办法履行前:预备调用服务办法...");
    }
}

接下来,小黑需求创立一个简略的Java类来作为方针,看看我们的切面是否能正确工作:

package com.example.service;
public class ExampleService {
    public void performAction() {
        System.out.println("履行服务办法...");
    }
}

运转ExampleServiceperformAction办法,假如全部装备正确,小黑应该会看到切面界说的消息被打印出来,证明AspectJ环境现已建立成功。

第4章 第一个AspectJ程序

走到这一步,我们现已成功建立了AspectJ的开发环境。现在,让我们一起来编写第一个AspectJ程序,经过这个实践的比方,我们将学习怎么界说切面和告诉,以及怎么将它们运用到Java代码中。

界说一个切面

在AspectJ中,切面是经过运用@Aspect注解来界说的。切面能够包括多种类型的告诉,这些告诉界说了切面在方针方针的生命周期中的不同切入点。为了展现这一点,我们将创立一个简略的日志记载切面,它在办法履行之前和之后打印日志信息。

package com.example.aspect;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.JoinPoint;
@Aspect
public class LoggingAspect {
    // 在办法履行之前打印日志
    @Before("execution(* com.example.service.*.*(..))")
    public void logBefore(JoinPoint joinPoint) {
        System.out.println("预备履行办法:" + joinPoint.getSignature().getName());
    }
    // 在办法履行之后打印日志
    @After("execution(* com.example.service.*.*(..))")
    public void logAfter(JoinPoint joinPoint) {
        System.out.println("办法履行完结:" + joinPoint.getSignature().getName());
    }
}

在这个比方中,@Before@After注解界说了前置告诉和后置告诉。这些告诉经过execution表达式指定了它们应该在哪些办法上履行。这儿,它们被装备为在com.example.service包下的全部类的全部办法上履行。

创立方针类

接下来,让我们创立一个方针类,以便我们能够看到切面是怎么运用到这个类上的。我们将创立一个简略的服务类,其中包括一个办法performAction,这个办法便是我们的切面将要织入告诉的当地。

package com.example.service;
public class ExampleService {
    public void performAction() {
        System.out.println("正在履行服务办法...");
    }
}

运转和验证

现在,让我们运转ExampleServiceperformAction办法,并观察输出。假如全部装备正确,我们应该能看到在办法履行之前和之后,我们的日志记载切面正确地打印了日志信息。

要运转这个比方,我们或许需求创立一个简略的Java运用程序的主类,然后在其中调用ExampleServiceperformAction办法。

package com.example;
import com.example.service.ExampleService;
public class Application {
    public static void main(String[] args) {
        ExampleService service = new ExampleService();
        service.performAction();
    }
}

假如全部顺畅,操控台的输出应该类似于这样:

预备履行办法:performAction
正在履行服务办法...
办法履行完结:performAction

恭喜我们,现在你们现已成功地编写并运转了第一个AspectJ程序!经过这个简略的比方,我们不只学会了怎么界说切面和告诉,还亲手验证了AspectJ怎么将这些告诉织入到方针方针的办法履行流程中。

第5章 深化切面和告诉

走到这一步,我们现已成功运转了第一个AspectJ程序,并对怎么界说切面和告诉有了开端的了解。现在,我们要深化探究切面和告诉,了解不同类型的告诉以及它们在实践开发中的运用。

不同类型的告诉

在AspectJ中,告诉界说了切面在连接点(即程序履行的特定点)上要履行的操作。有五种根本类型的告诉,每种都有其特定的用途:

  1. 前置告诉(Before advice):在方针办法履行之前履行,用于预备资源或查看前提条件。
  2. 后置告诉(After returning advice):在方针办法成功履行后履行,常用于整理资源。
  3. 反常告诉(After throwing advice):在方针办法抛出反常后履行,用于反常处理或回滚操作。
  4. 最终告诉(After (finally) advice):无论方针办法怎么结束(正常回来或抛出反常),都会履行的告诉,常用于释放资源。
  5. 盘绕告诉(Around advice):围住方针办法履行,能够自界说在办法履行前后的操作,最为灵敏但运用杂乱。

深化前置告诉和后置告诉

前置告诉和后置告诉是两种最常用的告诉类型,下面经过一个比方深化了解它们的运用。

假定我们需求在用户拜访某些资源前进行权限查看,并在拜访后记载拜访日志。这是一个典型的运用前置告诉和后置告诉的场景。

首要,界说一个切面,包括前置告诉和后置告诉:

package com.example.aspect;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.JoinPoint;
@Aspect
public class SecurityAspect {
    @Before("execution(* com.example.service.SecureService.*(..))")
    public void checkPermission(JoinPoint joinPoint) {
        // 这儿完成权限查看的逻辑
        System.out.println("权限查看:" + joinPoint.getSignature().getName());
    }
    @After("execution(* com.example.service.SecureService.*(..))")
    public void logAccess(JoinPoint joinPoint) {
        // 这儿完成拜访日志记载的逻辑
        System.out.println("记载拜访日志:" + joinPoint.getSignature().getName());
    }
}

运用AspectJ进行面向切面编程(AOP)

在这个比方中,checkPermission办法作为前置告诉,它在SecureService类的任何办法履行前进行权限查看。logAccess办法作为后置告诉,在办法履行后记载拜访日志。

运用盘绕告诉进行功用监控

盘绕告诉是一种特殊的告诉,它答应我们在办法履行前后履行自界说操作,十分合适用于功用监控。

下面是一个运用盘绕告诉进行功用监控的比方:

package com.example.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
@Aspect
public class PerformanceAspect {
    @Around("execution(* com.example.service.*.*(..))")
    public Object measureMethodExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        Object returnValue = joinPoint.proceed(); // 继续履行方针办法
        long end = System.currentTimeMillis();
        System.out.println(joinPoint.getSignature().getName() + " 办法履行时间:" + (end - start) + "ms");
        return returnValue;
    }
}

在这个比方中,measureMethodExecutionTime办法环绕方针办法履行,记载并打印出办法的履行时间。经过joinPoint.proceed()调用方针办法,并计算履行前后的时间差。

第6章 Pointcut表达式深度探究

经过前面几章的学习,我们现已把握了怎么运用不同类型的告诉来增强程序的功用。现在,让我们深化探讨Pointcut表达式,这是AspectJ中一个极其强壮的特性,它决定了告诉应该在哪里被运用。

Pointcut表达式根底

Pointcut(切入点)表达式用于指定哪些类和办法需求被切面所增强。它们的语法十分灵敏,能够精确到办法的回来类型、参数类型以及办法名称等。了解和把握Pointcut表达式关于编写高效的AspectJ代码来说是至关重要的。

  • 根本语法execution(修饰符 回来类型 类途径.办法名(参数)),不是全部部分都必需。

让我们先来看一个简略的比方,它匹配全部回来类型为void且名称为perform的办法:

@Before("execution(void perform(..))")
public void simpleBeforeAdvice() {
    System.out.println("履行前的告诉");
}

参数匹配

在Pointcut表达式中,我们能够经过指定参数类型来进一步限制匹配的办法。比方:

  • 匹配恣意参数:运用..表明办法能够有恣意类型和数量的参数。
  • 匹配无参数:运用()表明办法不该该有任何参数。
  • 匹配特定参数:直接指定参数类型,比方(String)匹配全部接受单个字符串参数的办法。

例如,只匹配接受一个String类型参数的perform办法:

@Before("execution(* perform(String))")
public void beforeWithStringArg() {
    System.out.println("办法有一个String类型参数");
}

类和包的匹配

Pointcut表达式不只能够匹配办法名和参数,还能够依据类名和包名来进行匹配。

  • 匹配特定类的全部办法execution(* com.example.ClassName.*(..))
  • 匹配特定包下全部类的全部办法execution(* com.example..*.*(..)),其中..表明包及其子包。

比方,匹配com.example.service包下全部类的全部办法:

@Before("execution(* com.example.service..*.*(..))")
public void beforeServiceMethods() {
    System.out.println("在service包下的办法履行前");
}

组合运用Pointcut表达式

AspectJ还答应我们经过逻辑运算符(&&、||、!)组合多个Pointcut表达式,以完成更杂乱的匹配逻辑。

例如,匹配com.example.service包下全部回来类型为void的办法,但不包括perform办法:

@Before("execution(* com.example.service..*.*(..)) && " +
        "execution(void *.*(..)) && " +
        "!execution(* perform(..))")
public void complexPointcut() {
    System.out.println("杂乱的Pointcut表达式匹配");
}

小结

经过深化学习和探究Pointcut表达式,我们能够更精确地操控切面的运用范围,这关于编写高效和可保护的AspectJ代码十分重要。经过灵敏运用Pointcut表达式,我们能够完成杂乱的逻辑匹配,让代码的增强愈加契合我们的需求。

第7章 AspectJ的高档特性

随着我们对AspectJ的深化探究,现在是时分了解一些更高档的特性了。这些特功用够协助我们构建更杂乱、更强壮的面向切面的运用程序。

切面的优先级

在实践运用中,常常会有多个切面一起作用于同一个连接点。这时,切面的履行次序就变得十分重要。AspectJ经过@Order注解或完成Ordered接口来指定切面的优先级。

较低的值具有较高的优先级。默认情况下,假如没有指定优先级,AspectJ会随机运用切面。

@Aspect
@Order(1)
public class HighPriorityAspect {
    // 这个切面会优先于其他切面履行
}
@Aspect
@Order(2)
public class LowPriorityAspect {
    // 这个切面会在HighPriorityAspect之后履行
}

引介(Introduction)

引介(也称为类型声明)答应我们向现有类增加新的办法和属性。这是经过在切面中声明额外的接口,并将其运用于方针方针来完成的。

public interface UsageTracked {
    void incrementUseCount();
}
@Aspect
public class UsageTrackingAspect {
    @DeclareParents(value="com.example.service.*+", defaultImpl=DefaultUsageTracked.class)
    public static UsageTracked mixin;
    @Before("execution(* com.example.service.*.*(..)) && this(usageTracked)")
    public void recordUsage(UsageTracked usageTracked) {
        usageTracked.incrementUseCount();
    }
    public static class DefaultUsageTracked implements UsageTracked {
        private int useCount = 0;
        public void incrementUseCount() {
            useCount++;
            System.out.println("当时运用次数:" + useCount);
        }
    }
}

在这个比方中,@DeclareParents引介了一个新的接口UsageTrackedcom.example.service包下的全部类,使得这些类实例都具有incrementUseCount办法。这答应我们在不修正原有类代码的情况下,为方针动态增加新的行为。

切面的继承

切面也能够继承自其他切面,这答应我们复用切面逻辑或依据特定需求对切面进行扩展。

@Aspect
public class BaseLoggingAspect {
    @Before("execution(* com.example..*.*(..))")
    public void doAccessCheck() {
        // 根底日志记载逻辑
    }
}
@Aspect
public class ExtendedLoggingAspect extends BaseLoggingAspect {
    // 这个切面继承了BaseLoggingAspect的行为,并能够增加额外的告诉或覆盖父类的告诉
}

小结

经过把握AspectJ的这些高档特性,我们能够在面向切面编程中做得更多、更深化。切面的优先级让我们能够精细操控多个切面的运用次序;引介使得为现有类动态增加新行为成为或许;而切面的继承则供给了一种强壮的办法来复用和扩展切面逻辑。把握了这些高档特性,我们就能在AspectJ的世界中自如地驰骋了。

第8章 实战事例:运用AspectJ处理实践问题

事例1:通用日志记载

在任何运用程序中,日志记载都是一个常见且重要的需求。运用AspectJ,我们能够轻松完成一个通用的日志记载切面,而不需求在每个办法中手动增加日志记载代码。

@Aspect
public class LoggingAspect {
    private Logger logger = LoggerFactory.getLogger(this.getClass());
    @Before("within(com.example.service..*)")
    public void logMethodCall(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        logger.info("开端履行办法: " + methodName);
    }
    @AfterReturning(pointcut = "within(com.example.service..*)", returning = "result")
    public void logMethodReturn(JoinPoint joinPoint, Object result) {
        String methodName = joinPoint.getSignature().getName();
        logger.info("办法: " + methodName + " 履行完结,回来值: " + result);
    }
}

这个切面会主动记载任何com.example.service包下类的办法调用和回来,极大地简化了日志记载工作。

事例2:功用监控

关于功用敏感的运用,监控办法履行时间是一个常见需求。经过AspectJ,我们能够创立一个切面来主动监控任何办法的履行时间。

@Aspect
public class PerformanceMonitoringAspect {
    private Logger logger = LoggerFactory.getLogger(this.getClass());
    @Around("execution(* com.example..*(..))")
    public Object monitorExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.nanoTime();
        Object result = joinPoint.proceed();
        long end = System.nanoTime();
        logger.info(joinPoint.getSignature() + " 履行时间: " + (end - start) / 1_000_000 + "ms");
        return result;
    }
}

这个切面能够运用到任何办法上,主动记载并打印出该办法的履行时间,协助开发者发现功用瓶颈。

事例3:事务管理

在企业级运用中,事务管理是一个杂乱但要害的功用。经过AspectJ,我们能够完成声明式事务管理,简化事务的编程模型。

@Aspect
public class TransactionAspect {
    private TransactionManager txManager;
    @Around("@annotation(org.springframework.transaction.annotation.Transactional)")
    public Object manageTransaction(ProceedingJoinPoint joinPoint) throws Throwable {
        try {
            txManager.beginTransaction();
            Object result = joinPoint.proceed();
            txManager.commit();
            return result;
        } catch (Exception e) {
            txManager.rollback();
            throw e;
        }
    }
}

这个切面利用@Transactional注解来标识需求进行事务管理的办法,主动处理事务的开端、提交和回滚,极大地简化了事务管理逻辑。

小结

经过这些实战事例,我们应该能看到,AspectJ不只能协助我们以更干净、更模块化的办法完成跨越运用程序多个部分的横切关注点,还能大幅提高开发功率和代码质量。无论是进行日志记载、功用监控还是事务管理,AspectJ都能供给强壮的支撑。期望我们能将这些常识运用到实践开发中,处理更多杂乱的编程问题。


更多引荐

详解SpringCloud之长途办法调用神器Fegin

把握Java Future形式及其灵敏运用

小黑整的视頻会园优惠站

小黑整的生财材料站

运用Apache Commons Chain完成指令形式