spring6代理模式和AOP示例詳解
jdbcTemplate
jdbcTemplate是spring提供的一個jdbc模板類,是對jdbc的封裝。
當然你也可以使用其他框架融入MyBatis、Hibernate。
GoF之代理模式
代理模式的作用
- 當一個對象需要受到保護的時候,可以使用代理對象去完成某個行為。
- 需要給某個對象進行功能增強的時候,可以找一個代理進行增強。
- A對象和B對象無法直接交互時,也可以使用代理模式來完成。
代理模式中的三個角色:
- 目標對象
- 代理對象
- 目標對象和代理對象的公共接口
如果使用代理模式的話,客戶端程序是無法察覺的,客戶端在使用代理對象的時候就像在使用目標對象。
代理模式分為靜態(tài)代理和動態(tài)代理。
靜態(tài)代理
目標對象類:
// 目標對象
public class OrderServiceImpl implements OrderService{
@Override
public void generateOrder() {
System.out.println("生成訂單");
}
@Override
public void modifyOrder() {
System.out.println("修改訂單");
}
@Override
public void detailOrder() {
System.out.println("查看訂單詳情");
}
}代理對象類:
// 代理對象
public class OrderServiceProxy implements OrderService{
// 代理對象中含有目標對象的引用
// 這里使用OrderService類型,因為他耦合度低
private OrderService orderService;
// 構造方法傳入目標對象
public OrderServiceProxy(OrderService orderService) {
this.orderService = orderService;
}
@Override
public void generateOrder() {
// 功能增強:統(tǒng)計方法執(zhí)行時間
long begin = System.currentTimeMillis();
orderService.generateOrder();
long end = System.currentTimeMillis();
System.out.println("生成訂單耗時:" + (end - begin) + "ms");
}
@Override
public void modifyOrder() {
long begin = System.currentTimeMillis();
orderService.modifyOrder();
long end = System.currentTimeMillis();
System.out.println("修改訂單耗時:" + (end - begin) + "ms");
}
@Override
public void detailOrder() {
long begin = System.currentTimeMillis();
orderService.detailOrder();
long end = System.currentTimeMillis();
System.out.println("查看訂單詳情耗時:" + (end - begin) + "ms");
}
}公共接口:
// 訂單服務接口
// 目標對象和代理對象的公共接口
public interface OrderService {
// 生成訂單
void generateOrder();
// 修改訂單
void modifyOrder();
// 查看訂單詳情
void detailOrder();
}測試:
// 實現(xiàn)目標對象方法執(zhí)行時間的統(tǒng)計
public static void main(String[] args) {
// 創(chuàng)建目標對象
OrderService orderService = new OrderServiceImpl();
// 創(chuàng)建代理對象,同時將目標對象傳入代理對象中
OrderServiceProxy orderServiceProxy = new OrderServiceProxy(orderService);
// 通過代理對象調用目標對象的方法
orderServiceProxy.generateOrder();
orderServiceProxy.modifyOrder();
orderServiceProxy.detailOrder();
}
靜態(tài)代理優(yōu)點:1.解決了ocp問題 2.采用代理模式的has a。降低了耦合度。
靜態(tài)代理的缺點:假設系統(tǒng)中有上千個接口,每個接口都需要寫代理類,這樣類的數(shù)量會急劇膨脹,不好維護。
那怎么解決類爆炸的問題呢?
采用動態(tài)代理。 動態(tài)代理還是代理模式,只不過是在內存中為我們動態(tài)的生成一個class字節(jié)碼,這個字節(jié)碼就是代理類。
動態(tài)代理
在程序運行階段,在內存中動態(tài)生成代理類,成為動態(tài)代理。目的是減少代理類的數(shù)量。
常見的動態(tài)代理技術有:JDK動態(tài)代理(只能代理接口)、CGLIB動態(tài)代理、Javassist動態(tài)代理。
JDK動態(tài)代理
公共接口 和目標對象類引用 上面的代碼。Jdk動態(tài)代理不需要寫代理類,jdk會在內存中自動生成代理類,因此直接在客戶端代碼里直接調用:
客戶端代碼:
public class Client {
public void main() {
// 1. 創(chuàng)建目標對象
OrderService orderService = new OrderServiceImpl();
// 2. 創(chuàng)建InvocationHandler對象
// 3. 創(chuàng)建代理對象
// 1. Proxy.newProxyInstance 的作用是創(chuàng)建代理對象。其實做了兩件事:
// 1). 在內存中動態(tài)的構建一個類,
// 2). 這個類要實現(xiàn)接口, 這個接口就是目標對象的接口,并且new了一個對象
// 2. 這個方法的三個參數(shù):
// 1). ClassLoader: 類加載器, 用于加載內存中的代理對象類. 和目標對象使用相同的類加載器
// 2). Class[]: 字節(jié)碼數(shù)組, 代理對象和目標對象實現(xiàn)相同的接口. 用于讓代理對象和目標對象具有相同的方法
// 3). InvocationHandler: 調用處理器對象,他是一個接口, 這個調用處理器用于編寫增強代碼
OrderService o = (OrderService)Proxy.newProxyInstance(OrderService.class.getClassLoader(), orderService.getClass().getInterfaces(),
new TimerInvocationHandler(orderService));
// 4. 通過代理對象調用方法
// 調用代理對象的代理方法時,如果代理方法的作用是功能增強,那目標對象的目標方法必須執(zhí)行。
o.generateOrder();
}
}
調用處理器類:
/**
* 負責計時的一個調用處理器類
* 在這個調用處理器中編寫增強代碼
* 這個調用處理器只需要一個就好
*/
public class TimerInvocationHandler implements InvocationHandler {
// 目標對象,就是要被增強的對象
private Object target;
// 構造方法傳入目標對象
public TimerInvocationHandler(Object target) {
this.target = target;
}
// 這個方法必須是invoke()方法,因為jdk在底層會調用這個方法
// 這個方法什么時候調用?
// 什么時候通過代理對象調用方法的時候就會調用這個invoke()方法
// 這個方法的參數(shù):
// proxy: 代理對象,就是通過Proxy.newProxyInstance()方法創(chuàng)建的代理對象
// method: 目標對象的目標方法,這就是要執(zhí)行的目標方法
// args: 目標方法上的參數(shù)
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 功能增強:
Long begin = System.currentTimeMillis();
Object invoke = method.invoke(target, args);
Long end = System.currentTimeMillis();
System.out.println("方法執(zhí)行耗時:" + (end - begin) + "ms");
return invoke;
}
}CGLIB動態(tài)代理
既可以代理接口,也可以代理類。底層采用繼承的方式實現(xiàn),因此被代理的目標類不能被final修飾。
目標類:
// 目標類
public class UserService {
public boolean login(String username, String password) {
System.out.println("用戶登錄,用戶名:" + username + ",密碼:" + password);
return "admin".equals(username) && "123456".equals(password);
}
public void logout(String username) {
System.out.println("用戶退出登錄,用戶名:" + username);
}
}客戶端代碼:
public static void main(String[] args) {
// 創(chuàng)建字節(jié)碼增強對象
// 這個對象是cglib的核心對象,依靠它來生成代理類
Enhancer enhancer = new Enhancer();
// 設置父類,也就是目標類
enhancer.setSuperclass(UserService.class);
// 設置回調函數(shù)(等同于jdk動態(tài)代理的中的調用處理器)
// 在cglib中是實現(xiàn)方法攔截器MethodInterceptor接口
enhancer.setCallback(new TimerMethodInterceptor());
UserService userServiceProxy = (UserService)enhancer.create();
boolean login = userServiceProxy.login("admin", "123456");
System.out.println(login?"登錄成功":"登錄失敗");
userServiceProxy.logout("admin");
}增強代碼寫在方法攔截器里面
public class TimerMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
Long startTime = System.currentTimeMillis();
Object o1 = methodProxy.invokeSuper(o, objects);
Long endTime = System.currentTimeMillis();
System.out.println("方法 " + method.getName() + " 執(zhí)行耗時:" + (endTime - startTime) + "ms");
return o1;
}
}
注意:在jdk17環(huán)境下,cglib動態(tài)代理功能啟動時會報錯,需要添加啟動時參數(shù)設置:
vm 參數(shù):--add-opens java.base/java.lang=ALL-UNNAMED
program 參數(shù):--add-opens java.base/sun.net.util=ALL-UNNAMED

面向切面編程AOP
在一個系統(tǒng)中一般會有許多系統(tǒng)服務,如:日志,事務管理、安全等。這些服務成為交叉業(yè)務。
這些交叉業(yè)務是通用的。
如果在每一個業(yè)務處理過程中,都摻雜這些交叉業(yè)務代碼會出現(xiàn)2個問題:
- 交叉業(yè)務代碼在多個業(yè)務中反復出現(xiàn)。代碼沒有得到復用,修改這些代碼會非常困難。
- 開發(fā)人員無法專業(yè)核心業(yè)務代碼,在編寫核心業(yè)務代碼時還要處理這些交叉業(yè)務代碼。
這就需要使用AOP來解決以上問題。

總之,AOP就是將與核心業(yè)務無關的代碼獨立的抽取出來。形成一個獨立的組件,然后以橫向交叉的方式應用到業(yè)務流程當中的過程。
aop底層使用動態(tài)代理技術實現(xiàn)。
spring AOP 使用的時jdk動態(tài)代理+cglib動態(tài)代理。spring 在這2種動態(tài)代理種靈活切換。如果是代理接口,則使用jdk動態(tài)代理,如果代理某個類,則使用cglib。當然也可以手動配置來強制使用cglib。
AOP的七大術語
連接點Joinpoint
在程序執(zhí)行流程中,可以織入切面的位置。方法的執(zhí)行前后,異常拋出之后等位置。
連接點描述的是位置。
切點Pointcut
在程序執(zhí)行流程中,真正織入切面的方法。(一個切點對應多個連接點)
切點描述的是方法。
通知Advice
通知又叫增強,就是具體你要織入的的代碼。
通知包括:前置通知、后置通知、環(huán)繞通知、異常通知、最終通知。
通知描述的是代碼。
切面Aspect
切點+通知就是切面。
織入Weaving
把通知應用到目標對象上的過程。
代理對象Proxy
一個目標對象被織入通知后產(chǎn)生的新對象。
目標對象Target
被織入通知的對象。
public void main(String[] args) {
try {
// Joint point 連接點
do1(); // Pointcut 切點
// Joint point 連接點
do2();// Pointcut 切點
// Joint point 連接點
do3();// Pointcut 切點
// Joint point 連接點
do4();// Pointcut 切點
// Joint point 連接點
do5();// Pointcut 切點
// Joint point 連接點
} catch (Exception e) {
// Joint point 連接點
}
}
切點表達式
切點表達式用來定義通知(Advice)往哪些方法上切入
使用Spring的AOP
Spring對AOP的實現(xiàn)包括三種方式:
- Spring結合AspectJ框架實現(xiàn)的AOP,基于注解方式
- Spring結合AspectJ框架實現(xiàn)的AOP,基于XML方式
- spring自己實現(xiàn)的AOP,基于xml配置方式
常用的是前2種方式。
準備工作
? 引入依賴
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.0.4</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>6.0.4</version>
</dependency>引入命名空間(context和aop)和配置xml文件
<?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:context="http://www.springframework.org/schema/context"
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/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd" >
<!-- 組件掃描-->
<context:component-scan base-package="com.ali.service" />
<!-- 開啟aspectj自動代理,spring容器在掃描類的時候,會查看類上是否有@Aspect注解
如果有。就給這個類生成代理對象 。
proxy-target-class="true" 表示強制使用cglib動態(tài)代理
proxy-target-class="false" 默認值,表示接口使用jdk動態(tài)代理,反之使用cglib動態(tài)代理-->
<aop:aspectj-autoproxy proxy-target-class="true"/>
</beans>編寫目標類
// 目標類
@Service
public class UserService {
// 目標方法
public void login() {
System.out.println("UserService login....");
}
}
編寫切面類
// 切面類,需要@Aspect 標注
@Aspect
@Component("logAspect")
public class LogAspect {
// 切面 = 通知+切點
// 通知就是增強,就是具體要編寫的增強代碼
// 這里通知以方法的形式出現(xiàn)。
// @Before 標注的方法就是一個前置通知
@Before("execution(* com.ali.service.UserService.*(..))")
public void advice( ) {
System.out.println("這是一個前置通知,方法執(zhí)行前執(zhí)行....");
}
// 環(huán)繞通知是最大的通知,在前置通知之前,在后置通知之后
@Around("execution(* com.ali.service.UserService.*(..))")
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("around start");
// 執(zhí)行目標方法
joinPoint.proceed();
System.out.println("around end");
}
}測試代碼:
@Test
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
UserService userService = context.getBean("userService", UserService.class);
userService.login();
}
注意:當有多個切面類時,可以使用@Order()注解進行優(yōu)先級排序,數(shù)字越小,優(yōu)先級越高,就先執(zhí)行。比如:@Order(2) 比@Order(3) 先執(zhí)行。
在每個方法上都寫一遍切點表達式很麻煩,可以定義一個通用的切點表達式,然后在方法上使用這個通用的表達式即可。
// 定義通用的切點表達式,后續(xù)通知直接使用方法名來引用切點表達式
// 切點就是一個表達式,定義了在哪些連接點上執(zhí)行通知
@Pointcut("execution(* com.ali.service.UserService.*(..))")
public void commmonPointcut() {
// 這個方法只是一個標識,方法體不需要編寫任何代碼
}
@AfterReturning("commmonPointcut()")
public void afterAdvice( ) {
System.out.println("這是一個前置通知,方法執(zhí)行前執(zhí)行....");
}JoinPoint的使用
通知方法可以加入?yún)?shù)JoinPoint,這個是spring容器自動傳入的。
@Before("execution(* com.ali.service.UserService.*(..))")
public void advice(JoinPoint joinPoint) {
System.out.println("這是一個前置通知,方法執(zhí)行前執(zhí)行....");
// 使用JoinPoint對象獲取連接點的信息
// joinPoint.getSignature() 獲取連接點的方法簽名
// 通過方法簽名可以獲取到這個方法的具體信息
// 獲取方法名
String methodName = joinPoint.getSignature().getName();
System.out.println("正在執(zhí)行的方法是:" + methodName);
}
到此這篇關于spring6代理模式和AOP的文章就介紹到這了,更多相關spring6代理模式和AOP內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
IDEA中JDK是1.8但Java版本只有21和17的解決辦法
JDK 1.8(Java Development Kit 1.8)是Java平臺的一個版本,它包含了用于開發(fā)和運行Java應用程序的工具和庫,下面這篇文章主要給大家介紹了關于IDEA中JDK是1.8但Java版本只有21和17的解決辦法,需要的朋友可以參考下2024-01-01
關于Springboot | @RequestBody 接收到的參數(shù)對象屬性為空的問題
這篇文章主要介紹了關于Springboot | @RequestBody 接收到的參數(shù)對象屬性為空的問題,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-03-03
Intellij?IDEA創(chuàng)建web項目的超詳細步驟記錄
如果剛開始接觸IDEA,或者之前使用的是eclipse/myEclipse的話,即使是創(chuàng)建一個JAVA WEB項目,估計也讓很多人費了好幾個小時,下面這篇文章主要給大家介紹了關于Intellij?IDEA創(chuàng)建web項目的超詳細步驟,需要的朋友可以參考下2022-08-08
IDEA?code?template配置和參數(shù)方式
這篇文章主要介紹了IDEA?code?template配置和參數(shù)方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教<BR>2024-01-01
Spring Boot/Angular整合Keycloak實現(xiàn)單點登錄功能
Keycloak新的發(fā)行版命名為Quarkus,專為GraalVM和OpenJDK HotSpot量身定制的一個Kurbernetes Native Java框架,計劃2019年底正式發(fā)布。這篇文章主要介紹了Spring Boot/Angular整合Keycloak實現(xiàn)單點登錄,需要的朋友可以參考下2019-10-10
java動態(tài)獲取實體類字段的8種方法實現(xiàn)
本文介紹了Java中動態(tài)獲取實體類字段的多種方法,包括反射API、注解處理器、字節(jié)碼操作庫、ORM框架等,具有一定的參考價值,感興趣的可以了解一下2025-07-07

