第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());
}
}
小黑悄悄告诉你一个买会员廉价的网站: 小黑整的视頻会园优惠站
第3章 AspectJ环境建立
好,我们现已了解了AOP和AspectJ的根底常识,现在让我们进入下一个阶段:建立AspectJ环境。不管小黑是运用Eclipse、IntelliJ IDEA还是其他IDE,我们都需求保证能顺畅地运转AspectJ程序。
在Eclipse中建立AspectJ
假如小黑运用的是Eclipse,那么建立AspectJ环境相对来说是比较简略的。Eclipse有一个名为”AspectJ Development Tools”(AJDT)的插件,能够让小黑轻松地开端AspectJ的冒险。
- 首要,翻开Eclipse,然后导航到“Help” > “Eclipse Marketplace…”。
- 在查找框中,输入“AJDT”然后查找。
- 找到”AspectJ Development Tools”插件,点击“Install”按钮进行装置。
- 装置完结后,重启Eclipse。
在IntelliJ IDEA中建立AspectJ
关于IntelliJ IDEA的用户,装备AspectJ也不会太杂乱,但需求手动增加AspectJ的库到项目中。
- 首要,翻开IntelliJ IDEA并创立或翻开一个项目。
- 点击File > Project Structure > Libraries,然后点击“+”按钮增加新的库。
- 选择从Maven增加库,查找“org.aspectj:aspectjrt”(这是AspectJ的运转时库),选择最新版本并增加到项目中。
- 相同,小黑或许还需求增加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("履行服务办法...");
}
}
运转ExampleService
的performAction
办法,假如全部装备正确,小黑应该会看到切面界说的消息被打印出来,证明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("正在履行服务办法...");
}
}
运转和验证
现在,让我们运转ExampleService
的performAction
办法,并观察输出。假如全部装备正确,我们应该能看到在办法履行之前和之后,我们的日志记载切面正确地打印了日志信息。
要运转这个比方,我们或许需求创立一个简略的Java运用程序的主类,然后在其中调用ExampleService
的performAction
办法。
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中,告诉界说了切面在连接点(即程序履行的特定点)上要履行的操作。有五种根本类型的告诉,每种都有其特定的用途:
- 前置告诉(Before advice):在方针办法履行之前履行,用于预备资源或查看前提条件。
- 后置告诉(After returning advice):在方针办法成功履行后履行,常用于整理资源。
- 反常告诉(After throwing advice):在方针办法抛出反常后履行,用于反常处理或回滚操作。
- 最终告诉(After (finally) advice):无论方针办法怎么结束(正常回来或抛出反常),都会履行的告诉,常用于释放资源。
- 盘绕告诉(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());
}
}
在这个比方中,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
引介了一个新的接口UsageTracked
到com.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都能供给强壮的支撑。期望我们能将这些常识运用到实践开发中,处理更多杂乱的编程问题。