tip:作為程序員一定學(xué)習(xí)編程之道,一定要對(duì)代碼的編寫(xiě)有追求,不能實(shí)現(xiàn)就完事了。我們應(yīng)該讓自己寫(xiě)的代碼更加優(yōu)雅,即使這會(huì)費(fèi)時(shí)費(fèi)力。
???? 推薦:體系化學(xué)習(xí)Java(Java面試專(zhuān)題)
1、什么是 AOP
1.1、概述
AOP(面向切面編程)是一種編程范式,用于將橫切關(guān)注點(diǎn)(如日志記錄、性能統(tǒng)計(jì)等)從主要業(yè)務(wù)邏輯中分離出來(lái)。通過(guò)將這些橫切關(guān)注點(diǎn)與業(yè)務(wù)邏輯分離開(kāi)來(lái),可以提高代碼的可重用性、可維護(hù)性和可擴(kuò)展性。在AOP中,切面是一個(gè)模塊化的單元,它封裝了與橫切關(guān)注點(diǎn)相關(guān)的行為,并可以在多個(gè)不同的應(yīng)用程序中重用。切面可以通過(guò)一種稱(chēng)為“織入”的過(guò)程將其與主要業(yè)務(wù)邏輯相結(jié)合,從而創(chuàng)建一個(gè)完整的應(yīng)用程序。
1.2、AOP 的作用
AOP 的作用主要有以下幾個(gè)方面:
1. 代碼復(fù)用:AOP 可以將一些通用的功能,如日志記錄、安全控制等,抽象出來(lái)形成切面,這些切面可以被多個(gè)模塊或應(yīng)用程序共享,從而避免了代碼重復(fù)。
2. 降低耦合度:AOP 可以將一些橫跨多個(gè)模塊的關(guān)注點(diǎn)從業(yè)務(wù)邏輯中解耦出來(lái),使得應(yīng)用程序更加模塊化,降低了各個(gè)模塊之間的耦合度。
3. 提高代碼可維護(hù)性:AOP 可以將一些非核心的功能從主要業(yè)務(wù)邏輯中分離出來(lái),使得代碼更加清晰、易于維護(hù)。
4. 提高代碼可擴(kuò)展性:AOP 可以在不修改主要業(yè)務(wù)邏輯的情況下,通過(guò)增加新的切面來(lái)擴(kuò)展應(yīng)用程序的功能。
5. 提高代碼的靈活性:AOP 可以在運(yùn)行時(shí)動(dòng)態(tài)地將切面織入到主要業(yè)務(wù)邏輯中,從而可以根據(jù)不同的需求對(duì)應(yīng)用程序進(jìn)行配置和定制。
1.3、AOP 的應(yīng)用場(chǎng)景
AOP 的應(yīng)用場(chǎng)景比較廣泛,以下是一些常見(jiàn)的應(yīng)用場(chǎng)景:
-
日志記錄:通過(guò) AOP 可以在方法執(zhí)行前后記錄日志,方便開(kāi)發(fā)人員對(duì)系統(tǒng)進(jìn)行調(diào)試和問(wèn)題排查。
-
安全控制:通過(guò) AOP 可以在方法執(zhí)行前進(jìn)行權(quán)限校驗(yàn),從而保證系統(tǒng)的安全性。
-
緩存管理:通過(guò) AOP 可以在方法執(zhí)行前后對(duì)數(shù)據(jù)進(jìn)行緩存,從而提高系統(tǒng)的性能。
-
事務(wù)管理:通過(guò) AOP 可以在方法執(zhí)行前后進(jìn)行事務(wù)管理,從而保證系統(tǒng)的數(shù)據(jù)一致性。
-
性能統(tǒng)計(jì):通過(guò) AOP 可以在方法執(zhí)行前后進(jìn)行性能統(tǒng)計(jì),從而方便開(kāi)發(fā)人員對(duì)系統(tǒng)進(jìn)行性能優(yōu)化。
-
異常處理:通過(guò) AOP 可以在方法執(zhí)行過(guò)程中捕獲異常,并進(jìn)行統(tǒng)一的處理,從而提高系統(tǒng)的健壯性和穩(wěn)定性。
-
分布式追蹤:通過(guò) AOP 可以在方法執(zhí)行前后進(jìn)行分布式追蹤,從而方便開(kāi)發(fā)人員對(duì)系統(tǒng)進(jìn)行分布式調(diào)試和問(wèn)題排查。
2、AOP 的配置方式
AOP 的配置方式主要有兩種:基于 XML 的配置和基于注解的配置。
2.1、基于 XML 的配置方式
基于 XML 的配置方式需要在 Spring 的配置文件中定義切面、切點(diǎn)、通知等元素,并使用 aop:config 元素將它們組合起來(lái)。以下是一個(gè)基于 XML 的 AOP 配置的示例:
<!-- 定義切面 -->
<bean id="loggingAspect" class="com.example.LoggingAspect"/>
<!-- 定義切點(diǎn) -->
<aop:pointcut id="serviceMethod" expression="execution(* com.example.Service.*(..))"/>
<!-- 定義通知 -->
<aop:advisor advice-ref="loggingAdvice" pointcut-ref="serviceMethod"/>
<!-- 定義通知實(shí)現(xiàn)類(lèi) -->
<bean id="loggingAdvice" class="org.springframework.aop.interceptor.CustomizableTraceInterceptor">
<property name="enterMessage" value="Entering $[methodName]($[arguments])"/>
<property name="exitMessage" value="Leaving $[methodName](): $[returnValue]"/>
</bean>
<!-- 啟用 AOP -->
<aop:config/>
上述配置文件定義了一個(gè)名為 loggingAspect 的切面,一個(gè)名為 serviceMethod 的切點(diǎn),一個(gè)名為 loggingAdvice 的通知實(shí)現(xiàn)類(lèi),并將通知綁定到切點(diǎn)上。這個(gè)示例的作用是在 com.example.Service 包下的所有方法執(zhí)行前后打印日志。
2.2、基于注解的配置方式
基于注解的配置方式需要在 Java 類(lèi)中使用注解來(lái)標(biāo)記切面、切點(diǎn)、通知等元素。以下是一個(gè)基于注解的 AOP 配置的示例:
@Aspect
@Component
public class LoggingAspect {
@Pointcut("execution(* com.example.Service.*(..))")
public void serviceMethod() {}
@Before("serviceMethod()")
public void beforeAdvice() {
System.out.println("Entering method...");
}
@AfterReturning("serviceMethod()")
public void afterAdvice() {
System.out.println("Leaving method...");
}
}
上述代碼定義了一個(gè)名為 LoggingAspect 的切面,使用 @Pointcut 注解定義了一個(gè)名為 serviceMethod 的切點(diǎn),并使用 @Before 和 @AfterReturning 注解定義了兩個(gè)通知方法。這個(gè)示例的作用與前面的示例相同,都是在 com.example.Service 包下的所有方法執(zhí)行前后打印日志。
3、AOP 實(shí)現(xiàn)原理
AOP 的實(shí)現(xiàn)原理主要是基于動(dòng)態(tài)代理和字節(jié)碼操作。Spring AOP 使用了 JDK 動(dòng)態(tài)代理和 CGLIB 字節(jié)碼操作兩種方式來(lái)實(shí)現(xiàn) AOP。
JDK 動(dòng)態(tài)代理是通過(guò)反射機(jī)制在運(yùn)行時(shí)動(dòng)態(tài)地創(chuàng)建代理對(duì)象,代理對(duì)象與目標(biāo)對(duì)象實(shí)現(xiàn)了相同的接口,并在代理對(duì)象中增加了切面邏輯。以下是一個(gè)使用 JDK 動(dòng)態(tài)代理實(shí)現(xiàn) AOP 的示例代碼:
public interface UserService {
void addUser(String name);
}
public class UserServiceImpl implements UserService {
public void addUser(String name) {
System.out.println("addUser: " + name);
}
}
public class UserServiceProxy implements InvocationHandler {
private Object target;
public UserServiceProxy(Object target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before addUser");
Object result = method.invoke(target, args);
System.out.println("after addUser");
return result;
}
}
public class Main {
public static void main(String[] args) {
UserService userService = new UserServiceImpl();
UserService proxy = (UserService) Proxy.newProxyInstance(
userService.getClass().getClassLoader(),
userService.getClass().getInterfaces(),
new UserServiceProxy(userService));
proxy.addUser("John");
}
}
上述代碼中, UserService 接口定義了一個(gè) addUser 方法, UserServiceImpl 類(lèi)是 UserService 接口的實(shí)現(xiàn)類(lèi)。 UserServiceProxy 類(lèi)是代理類(lèi),實(shí)現(xiàn)了 InvocationHandler 接口,用于在代理對(duì)象的方法執(zhí)行前后增加切面邏輯。 Main 類(lèi)中使用 Proxy.newProxyInstance 方法創(chuàng)建代理對(duì)象,并調(diào)用 addUser 方法。
CGLIB 字節(jié)碼操作是通過(guò)繼承目標(biāo)對(duì)象并重寫(xiě)目標(biāo)方法的方式來(lái)實(shí)現(xiàn) AOP,因此目標(biāo)對(duì)象不需要實(shí)現(xiàn)接口。以下是一個(gè)使用 CGLIB 實(shí)現(xiàn) AOP 的示例代碼:
public class UserService {
public void addUser(String name) {
System.out.println("addUser: " + name);
}
}
public class UserServiceInterceptor implements MethodInterceptor {
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("before addUser");
Object result = proxy.invokeSuper(obj, args);
System.out.println("after addUser");
return result;
}
}
public class Main {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserService.class);
enhancer.setCallback(new UserServiceInterceptor());
UserService userService = (UserService) enhancer.create();
userService.addUser("John");
}
}
上述代碼中, UserService 類(lèi)是目標(biāo)對(duì)象, UserServiceInterceptor 類(lèi)是攔截器類(lèi),實(shí)現(xiàn)了 MethodInterceptor 接口,用于在目標(biāo)方法執(zhí)行前后增加切面邏輯。 Main 類(lèi)中使用 Enhancer 類(lèi)創(chuàng)建代理對(duì)象,并調(diào)用 addUser 方法。
無(wú)論是使用 JDK 動(dòng)態(tài)代理還是 CGLIB 字節(jié)碼操作,AOP 的實(shí)現(xiàn)原理都是基于代理模式和字節(jié)碼操作。通過(guò)在代理對(duì)象中增加切面邏輯,實(shí)現(xiàn)了對(duì)目標(biāo)對(duì)象的增強(qiáng)。
4、什么是動(dòng)態(tài)代理
動(dòng)態(tài)代理是一種在運(yùn)行時(shí)動(dòng)態(tài)生成代理類(lèi)的技術(shù),可以在不修改原始代碼的情況下,為類(lèi)或?qū)ο筇砑右恍╊~外的功能。動(dòng)態(tài)代理通常用于實(shí)現(xiàn) AOP(面向切面編程)和遠(yuǎn)程方法調(diào)用等場(chǎng)景。
動(dòng)態(tài)代理的實(shí)現(xiàn)原理是通過(guò) Java 反射機(jī)制,在運(yùn)行時(shí)動(dòng)態(tài)生成代理類(lèi)。代理類(lèi)實(shí)現(xiàn)了與目標(biāo)類(lèi)相同的接口,并在代理類(lèi)中增加了額外的邏輯,例如記錄日志、性能監(jiān)控等。當(dāng)調(diào)用代理對(duì)象的方法時(shí),實(shí)際上是調(diào)用了代理類(lèi)中的方法,代理類(lèi)再調(diào)用目標(biāo)對(duì)象的方法,并在方法執(zhí)行前后執(zhí)行額外的邏輯。
Java 中有兩種動(dòng)態(tài)代理方式:JDK 動(dòng)態(tài)代理和 CGLIB 動(dòng)態(tài)代理。JDK 動(dòng)態(tài)代理是基于接口的代理,只能代理實(shí)現(xiàn)了接口的類(lèi)。而 CGLIB 動(dòng)態(tài)代理是基于繼承的代理,可以代理任何類(lèi),但代理的類(lèi)不能聲明為 final 類(lèi)型。
4.1、JDK 動(dòng)態(tài)代理
JDK 動(dòng)態(tài)代理是 Java 標(biāo)準(zhǔn)庫(kù)中提供的一種動(dòng)態(tài)代理實(shí)現(xiàn)方式,它可以在運(yùn)行時(shí)動(dòng)態(tài)生成代理類(lèi),并實(shí)現(xiàn)被代理接口的所有方法。JDK 動(dòng)態(tài)代理主要依賴(lài)于 Java 的反射機(jī)制和 InvocationHandler 接口。
下面是一個(gè)簡(jiǎn)單的 JDK 動(dòng)態(tài)代理的示例代碼:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 定義一個(gè)接口
interface Hello {
void sayHello();
}
// 實(shí)現(xiàn)接口的類(lèi)
class HelloImpl implements Hello {
public void sayHello() {
System.out.println("Hello, world!");
}
}
// 實(shí)現(xiàn) InvocationHandler 接口的代理類(lèi)
class HelloProxy implements InvocationHandler {
private Object target;
public HelloProxy(Object target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before method " + method.getName());
Object result = method.invoke(target, args);
System.out.println("After method " + method.getName());
return result;
}
}
public class Main {
public static void main(String[] args) {
// 創(chuàng)建被代理對(duì)象
Hello hello = new HelloImpl();
// 創(chuàng)建代理對(duì)象
Hello proxy = (Hello) Proxy.newProxyInstance(
hello.getClass().getClassLoader(),
hello.getClass().getInterfaces(),
new HelloProxy(hello)
);
// 調(diào)用代理對(duì)象的方法
proxy.sayHello();
}
}
在上面的代碼中,我們定義了一個(gè)接口 Hello 和一個(gè)實(shí)現(xiàn)該接口的類(lèi) HelloImpl 。接著我們定義了一個(gè)實(shí)現(xiàn) InvocationHandler 接口的代理類(lèi) HelloProxy ,它的作用是在調(diào)用被代理對(duì)象的方法前后輸出日志。最后我們?cè)?main 函數(shù)中創(chuàng)建了被代理對(duì)象 HelloImpl 和代理對(duì)象 HelloProxy ,并通過(guò) Proxy.newProxyInstance 方法生成了代理對(duì)象實(shí)例。當(dāng)我們調(diào)用代理對(duì)象的 sayHello 方法時(shí),實(shí)際上是調(diào)用了 HelloProxy 中的 invoke 方法,該方法會(huì)在調(diào)用被代理對(duì)象的 sayHello 方法前后輸出日志。
JDK 動(dòng)態(tài)代理的原理是通過(guò)反射機(jī)制在運(yùn)行時(shí)動(dòng)態(tài)生成代理類(lèi),并實(shí)現(xiàn)被代理接口的所有方法。當(dāng)調(diào)用代理對(duì)象的方法時(shí),實(shí)際上是調(diào)用了代理類(lèi)中的方法,代理類(lèi)再調(diào)用目標(biāo)對(duì)象的方法,并在方法執(zhí)行前后執(zhí)行額外的邏輯。
4.2、CGLIB 動(dòng)態(tài)代理
CGLIB 動(dòng)態(tài)代理是一種基于繼承的代理實(shí)現(xiàn)方式,它可以在運(yùn)行時(shí)動(dòng)態(tài)生成代理類(lèi),并繼承被代理類(lèi)。CGLIB 動(dòng)態(tài)代理主要依賴(lài)于 ASM(一個(gè) Java 字節(jié)碼操作庫(kù))。
下面是一個(gè)簡(jiǎn)單的 CGLIB 動(dòng)態(tài)代理的示例代碼:
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
// 被代理類(lèi)
class Hello {
public void sayHello() {
System.out.println("Hello, world!");
}
}
// 實(shí)現(xiàn) MethodInterceptor 接口的代理類(lèi)
class HelloProxy implements MethodInterceptor {
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("Before method " + method.getName());
Object result = proxy.invokeSuper(obj, args);
System.out.println("After method " + method.getName());
return result;
}
}
public class Main {
public static void main(String[] args) {
// 創(chuàng)建 Enhancer 對(duì)象
Enhancer enhancer = new Enhancer();
// 設(shè)置被代理類(lèi)為父類(lèi)
enhancer.setSuperclass(Hello.class);
// 設(shè)置回調(diào)函數(shù)
enhancer.setCallback(new HelloProxy());
// 創(chuàng)建代理對(duì)象
Hello proxy = (Hello) enhancer.create();
// 調(diào)用代理對(duì)象的方法
proxy.sayHello();
}
}
5、AOP 在項(xiàng)目中的應(yīng)用
AOP(面向切面編程)是一種編程思想,它可以通過(guò)將橫切關(guān)注點(diǎn)(如日志記錄、性能統(tǒng)計(jì)、事務(wù)管理等)與業(yè)務(wù)邏輯分離,使得代碼更加模塊化、易于維護(hù)和擴(kuò)展。在項(xiàng)目中,AOP 可以應(yīng)用于很多場(chǎng)景,下面舉例說(shuō)明幾個(gè)常見(jiàn)的應(yīng)用場(chǎng)景。
- 日志記錄
在項(xiàng)目中,我們通常需要記錄一些關(guān)鍵操作的日志,以便于后續(xù)排查問(wèn)題。使用 AOP 可以很方便地實(shí)現(xiàn)日志記錄,例如使用 Spring AOP,我們可以定義一個(gè)切面,通過(guò)在切面中定義一個(gè)方法,在方法中記錄日志,并將該切面織入到需要記錄日志的方法中。 - 安全控制
在項(xiàng)目中,我們通常需要對(duì)一些敏感操作進(jìn)行安全控制,例如需要登錄才能訪(fǎng)問(wèn)某些頁(yè)面或執(zhí)行某些操作。使用 AOP 可以很方便地實(shí)現(xiàn)安全控制,例如使用 Spring Security,我們可以定義一個(gè)切面,在切面中判斷用戶(hù)是否已經(jīng)登錄,并根據(jù)需要進(jìn)行權(quán)限控制。 - 性能統(tǒng)計(jì)
在項(xiàng)目中,我們通常需要對(duì)一些關(guān)鍵操作進(jìn)行性能統(tǒng)計(jì),以便于優(yōu)化系統(tǒng)性能。使用 AOP 可以很方便地實(shí)現(xiàn)性能統(tǒng)計(jì),例如使用 Spring AOP,我們可以定義一個(gè)切面,在切面中記錄方法的執(zhí)行時(shí)間,并將該切面織入到需要進(jìn)行性能統(tǒng)計(jì)的方法中。 - 事務(wù)管理
在項(xiàng)目中,我們通常需要對(duì)一些關(guān)鍵操作進(jìn)行事務(wù)管理,以保證數(shù)據(jù)的一致性和完整性。使用 AOP 可以很方便地實(shí)現(xiàn)事務(wù)管理,例如使用 Spring AOP,我們可以定義一個(gè)切面,在切面中開(kāi)啟事務(wù)、提交事務(wù)或回滾事務(wù),并將該切面織入到需要進(jìn)行事務(wù)管理的方法中。
總之,AOP 在項(xiàng)目中的應(yīng)用非常廣泛,可以幫助我們更好地實(shí)現(xiàn)代碼的分離和模塊化,提高代碼的可維護(hù)性和可擴(kuò)展性。
6、實(shí)踐-手寫(xiě)一個(gè) AOP 的案例
以下是一個(gè)基于Spring AOP實(shí)現(xiàn)日志記錄的示例代碼:
- 創(chuàng)建一個(gè)切面類(lèi):
@Aspect
@Component
public class LogAspect {
private static final Logger LOGGER = LoggerFactory.getLogger(LogAspect.class);
@Around("execution(* com.example.demo.controller.*.*(..))")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
Object result = joinPoint.proceed();
long endTime = System.currentTimeMillis();
LOGGER.info("Method {} execution time: {}ms", joinPoint.getSignature().getName(), endTime - startTime);
return result;
}
@Before("execution(* com.example.demo.controller.*.*(..)) && args(request,..)")
public void logBefore(JoinPoint joinPoint, HttpServletRequest request) {
LOGGER.info("Request URL: {} {}", request.getMethod(), request.getRequestURL());
LOGGER.info("Request parameters: {}", request.getParameterMap());
}
}
這里定義了兩個(gè)切點(diǎn),一個(gè)是 @Around ,用于記錄方法的執(zhí)行時(shí)間,另一個(gè)是 @Before ,用于記錄請(qǐng)求的參數(shù)。
- 在Spring Boot主類(lèi)中添加 @EnableAspectJAutoProxy 注解:
@SpringBootApplication
@EnableAspectJAutoProxy
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
- 測(cè)試
編寫(xiě)一個(gè)簡(jiǎn)單的Controller:
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello(@RequestParam("name") String name) {
return "Hello, " + name;
}
}
啟動(dòng)應(yīng)用,訪(fǎng)問(wèn) http://localhost:8080/hello?name=world,可以在控制臺(tái)看到類(lèi)似如下的日志:
2021-09-23 16:53:11.453 INFO 12345 --- [nio-8080-exec-1] c.e.d.aop.LogAspect : Request URL: GET http://localhost:8080/hello
2021-09-23 16:53:11.454 INFO 12345 --- [nio-8080-exec-1] c.e.d.aop.LogAspect : Request parameters: {name=[world]}
2021-09-23 16:53:11.454 INFO 12345 --- [nio-8080-exec-1] c.e.d.aop.LogAspect : Method hello execution time: 4ms
可以看到,請(qǐng)求的參數(shù)和方法的執(zhí)行時(shí)間都被記錄下來(lái)了。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-757847.html
???? 本文由激流原創(chuàng),首發(fā)于CSDN博客,博客主頁(yè) https://blog.csdn.net/qq_37967783?spm=1010.2135.3001.5421
????喜歡的話(huà)記得點(diǎn)贊收藏啊文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-757847.html
到了這里,關(guān)于【Java 初級(jí)】Spring核心之面向切面編程(AOP)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!