前置-代理設計
我們先介紹相關的幾種代理設計方法
1、靜態代理
// 原始業務介面
public interface UserService {
void login(String username,String password);
}
// 業務實作類
public class UserServiceImpl implements UserService {
@Override
public void login(String username, String password) {
System.out.println("用戶名:"+username+" 密碼:"+password);
}
}
// 代理類
public class UserServiceStaticProxy implements UserService {
private UserService userService=new UserServiceImpl();
@Override
public void login(String username, String password) {
System.out.println("-----log-----");
userService.login(username,password);
}
}
2、JDK的動態代理
public class JDKProxy {
public static UserService getProxy(UserService userService) {
//獲取原始類物件的類加載器
ClassLoader loader = userService.getClass().getClassLoader();
//被原始類物件的介面
Class<?>[] interfaces = userService.getClass().getInterfaces();
//方法執行器,執行原始類的目標方法
InvocationHandler handler = new InvocationHandler() {
/**
*
* @param proxy 代理物件,jdk使用的
* @param method 原始類要執行的目標方法
* @param args 方法呼叫傳入的引數值
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("-----method before advice log------");
Object result = method.invoke(userService, args);
System.out.println("-----method after advice log------");
return result;
}
};
// Proxy.newProxyInstance() 通過目標類實作的介面創建代理類
Object proxyInstance = Proxy.newProxyInstance(loader, interfaces, handler);
return (UserService) proxyInstance;
}
}
3、CGlib的動態代理
//通過繼承被代理類創建代理類
public class CGlibProxy {
public static UserService getProxy(UserService userService) {
// 創建Enhancer
Enhancer enhancer = new Enhancer();
// 設定類加載器
enhancer.setClassLoader(userService.getClass().getClassLoader());
// 設定父類(目標類)
enhancer.setSuperclass(userService.getClass());
// 設定回呼,執行原始類的目標方法
enhancer.setCallback(new MethodInterceptor() {
////相當于 InvocationHandler的invoke()方法
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("-----method before advice log------");
Object result = method.invoke(userService, args);
System.out.println("-----method after advice log------");
return result;
}
});
//通過Enhancer物件創建代理
Object proxyInstance = enhancer.create();
return (UserService) proxyInstance;
}
}
4、Spring動態代理
先配置一下依賴
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/aopalliance/aopalliance -->
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.3.RELEASE</version>
</dependency>
1)、實作MethodBeforeAdvice介面
public class Before implements MethodBeforeAdvice {
/**
* 作用:給主業務方法添加輔助功能,會在業務方法之前執行
*
* @param method 業務方法,比如:login()
* @param args 業務方法中的引數串列,比如:login()方法中的username和password
* @param target 業務類的物件,比如:UserServiceImpl
* @throws Throwable 拋出例外
*/
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("-----method before advice log------");
}
}
<?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="userService" ></bean>
<bean id="before" ></bean>
<!--aop:config標簽:配置aop動態代理
aop:pointcut標簽:定義切入點,就是輔助功能的定義位置,expression="execution(* *(..))":所有方法都添加輔助功能
aop:advisor標簽:將輔助功能類的物件和切入點組裝在一起
-->
<aop:config>
<aop:pointcut id="mypoint" expression="execution(* *(..))"/>
<aop:advisor advice-ref="before" pointcut-ref="mypoint"></aop:advisor>
</aop:config>
</beans>
2)、實作MethodInterceptor介面
MethodBeforeAdvice和MethodInterceptor不同點就是:前者只能業務方法之前執行,而后者業務方法之前、之后、前后都能執行
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class Around implements MethodInterceptor {
/**
* @param invocation 封裝了業務方法,invocation.proceed():表示執行的業務方法
* @return 業務方法的回傳值
* @throws Throwable
*/
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("-----method before advice log------");
Object returnValue = https://www.cnblogs.com/hanyu-2020/p/invocation.proceed();
System.out.println("-----method after advice log------");
try {
int num = 6 / 0;
} catch (Exception e) {
e.printStackTrace();
System.out.println("-----method exception advice log------");
}
System.out.println("finally advice log--------------");
return returnValue;
}
}
下面我們完整寫一個日志類:LogUtil.java
public class LogUtil {
//JoinPoint物件用來獲取方法名和引數串列等資訊
public static void before(JoinPoint joinPoint) {
String name = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println(name + " 前置通知,引數是:" + Arrays.asList(args));
}
public void after(JoinPoint joinPoint) {
String name = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println(name + " method after advice log 后置通知,引數是:" + Arrays.asList(args));
}
public void Exception(JoinPoint joinPoint) {
String name = joinPoint.getSignature().getName();
System.out.println(name + " method exception advice log 例外通知------");
}
public void Finally(JoinPoint joinPoint, Object result) {
String name = joinPoint.getSignature().getName();
System.out.println(name + " method afterReturn advice log 后置回傳通知,方法執行完成");
}
}
<bean id="userService" ></bean>
<bean id="logUtil" ></bean>
<bean id="logUtil2" ></bean>
<aop:config>
<!--我們可以單獨定義一個pointcut,下面使用的時候直接參考就好-->
<aop:pointcut id="myPoint" expression="execution(* com.ty.service.UserServiceImpl.*(..))"/>
<aop:aspect ref="logUtil" >
<aop:before method="before" pointcut-ref="myPoint"></aop:before>
<aop:after method="after" pointcut-ref="myPoint"></aop:after>
<aop:after-throwing method="Exception" pointcut-ref="myPoint"></aop:after-throwing>
<aop:after-returning method="afterReturn" pointcut-ref="myPoint"></aop:after-returning>
</aop:aspect>
</aop:config>
后置回傳通知如果有結果值或者例外資訊回傳,可以在引數串列添加一個對應的引數
public void Exception(JoinPoint joinPoint,Throwable exception) {
String name = joinPoint.getSignature().getName();
System.out.println(name + " method exception advice log 例外通知------");
}
public void Finally(JoinPoint joinPoint, Object result) {
String name = joinPoint.getSignature().getName();
System.out.println(name + " method afterReturn advice log 后置回傳通知,方法執行完成");
}
而對應的組態檔就變成這樣
<!--標簽中的throwing和returning屬性的值與引數名相同-->
<aop:after-throwing method="Exception" pointcut-ref="myPoint" throwing="exception"></aop:after-throwing>
<aop:after-returning method="Finally" pointcut-ref="myPoint" returning="result"></aop:after-returning>
注解方式
@Component
//此注解=配置方式的<aop:config>標簽
@Aspect
//此注解表示如果有多個輔助功能類,指定哪個先執行
@Order
public class LogUtil {
@Pointcut("execution(* com.ty.service.UserServiceImpl.*(..))")
public void myPoint() {
}
@Before(value = "https://www.cnblogs.com/hanyu-2020/p/myPoint()")
public static void before(JoinPoint joinPoint) {
String name = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println(name + " method before advice log 前置通知,引數是:" + Arrays.asList(args));
}
@After(value = "https://www.cnblogs.com/hanyu-2020/p/myPoint()")
public void after(JoinPoint joinPoint) {
String name = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println(name + " method after advice log 后置通知,引數是:" + Arrays.asList(args));
}
@AfterThrowing(value = "https://www.cnblogs.com/hanyu-2020/p/myPoint()", throwing = "exception")
public void Exception(JoinPoint joinPoint, Throwable exception) {
String name = joinPoint.getSignature().getName();
System.out.println(name + " method exception advice log 例外通知:" + exception);
}
@AfterReturning(value = "https://www.cnblogs.com/hanyu-2020/p/myPoint()", returning = "result")
public void Finally(JoinPoint joinPoint, Object result) {
String name = joinPoint.getSignature().getName();
System.out.println(name + " method afterReturn advice log 后置回傳通知,方法執行完成");
}
@Around(value = "https://www.cnblogs.com/hanyu-2020/p/myPoint()")
public Object around(ProceedingJoinPoint joinPoint) {
String name = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
Object result = null;
try {
System.out.println("環繞前置通知:" + name + "方法開始,引數是" + Arrays.asList(args));
//利用反射呼叫目標方法,相當于method.invoke()
result = joinPoint.proceed(args);
System.out.println("環繞回傳通知:" + name + "方法回傳,回傳值是" + result);
} catch (Throwable throwable) {
System.out.println("環繞例外通知" + name + "方法出現例外,例外資訊是:" + throwable);
} finally {
System.out.println("環繞后置通知" + name + 方法結束");
}
return result;
}
組態檔
<context:component-scan base-package="com.ty"></context:component-scan>
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
5、Spring工廠回傳代理物件
public class ProxyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("-----method before advice log------");
return method.invoke(bean, args);
}
};
return Proxy.newProxyInstance(bean.getClass().getClassLoader(), bean.getClass().getInterfaces(), handler);
}
}
<bean id="userService" ></bean>
<bean id="postProcessor" ></bean>
這就是運用了postProcessAfterInitialization后置處理器方法,創建的是userService物件,但是經過一步步的處理到后置處理器這里進行加工,看到創建了代理物件,所以最后就回傳了一個代理物件
AOP
AOP的概念
前面我們寫過Spring動態代理的代碼,我們沒具體介紹,Spring動態代理對應的就可以看成AOP,AOP是一種編程思想模型,
對應的就是學過的OOP
-
AOP:Aspect Oriented Programming 面向切面編程
-
OOP:Object Oriented Programming 面向物件編程
面向切面編程:基于OOP基礎之上新的編程思想,OOP面向的主要物件是類,而AOP面向的主要物件是切面,在處理日志、安全管理、
事務管理等方面有非常重要的作用,AOP是Spring中重要的核心點,雖然IOC容器沒有依賴AOP,但是AOP提供了非常強大的功能,用來
對IOC做補充,通俗點說的話就是在程式運行期間,將某段代碼動態切入到指定方法的指定位置進行運行的這種編程方式,
AOP的核心概念及術語
簡單的AOP代碼就是上面的Spring動態代理代碼,就不重復寫了,我們介紹幾個專業術語,先把上面的代碼用圖表示

-
切面(Aspect): 指關注點模塊化,這個關注點可能會橫切多個物件,事務管理是企業級Java應用中有關橫切關注點的例子, 在Spring AOP中,切面可以使用通用類基于模式的方式(schema-based approach)或者在普通類中以
@Aspect注解(@AspectJ 注解方式)來實作, -
連接點(Join point): 在程式執行程序中某個特定的點,例如某個方法呼叫的時間點或者處理例外的時間點,在Spring AOP中,一個連接點總是代表一個方法的執行,
-
通知(Advice): 在切面的某個特定的連接點上執行的動作,通知有多種型別,包括“around”, “before” and “after”等等,通知的型別將在后面的章節進行討論, 許多AOP框架,包括Spring在內,都是以攔截器做通知模型的,并維護著一個以連接點為中心的攔截器鏈,
-
切點(Pointcut): 匹配連接點的斷言,通知和切點運算式相關聯,并在滿足這個切點的連接點上運行(例如,當執行某個特定名稱的方法時),切點運算式如何和連接點匹配是AOP的核心:Spring默認使用AspectJ切點語意,
-
引入(Introduction): 宣告額外的方法或者某個型別的欄位,Spring允許引入新的介面(以及一個對應的實作)到任何被通知的物件上,例如,可以使用引入來使bean實作
IsModified介面, 以便簡化快取機制(在AspectJ社區,引入也被稱為內部型別宣告(inter)), -
目標物件(Target object): 被一個或者多個切面所通知的物件,也被稱作被通知(advised)物件,既然Spring AOP是通過運行時代理實作的,那么這個物件永遠是一個被代理(proxied)的物件,
-
AOP代理(AOP proxy):AOP框架創建的物件,用來實作切面契約(aspect contract)(包括通知方法執行等功能),在Spring中,AOP代理可以是JDK動態代理或CGLIB代理,
-
織入(Weaving): 把切面連接到其它的應用程式型別或者物件上,并創建一個被被通知的物件的程序,這個程序可以在編譯時(例如使用AspectJ編譯器)、類加載時或運行時中完成, Spring和其他純Java AOP框架一樣,是在運行時完成織入的,
切入點詳解
之前寫過的* *(..) 代表所有方法,分別表示:修飾符 回傳值型別 方法全限定名 對應的引數串列型別
上面的切入點運算式匹配粒度太粗,還有相對精準的表達方法
1)、類切入點運算式
<!--類的所有方法都添加輔助功能,
*表示任意修飾符和回傳值型別
..表示匹配任意型別引數-->
<aop:pointcut id="mypoint" expression="execution(* com.ty.service.UserServiceImpl.*(..))"/>
<!--添加訪問修飾符-->
<aop:pointcut id="mypoint" expression="execution(public * com.ty.service.UserServiceImpl.login(String,String))"/>
<!--類的login方法都添加輔助功能-->
<aop:pointcut id="mypoint" expression="execution(* com.ty.service.UserServiceImpl.login(..))"/>
或者:
<aop:pointcut id="mypoint" expression="execution(* com.ty.service.UserServiceImpl.login(String,String))"/>
或者:這里的引數*也是代表所有引數型別
<aop:pointcut id="mypoint" expression="execution(* com.ty.service.UserServiceImpl.login(String,*))"/>
<!--詳細版本-->
<aop:pointcut id="mypoint" expression="execution(public void com.ty.service.UserServiceImpl.login(String,String))"/>
2)、包切入點運算式
<!-- com.ty.service包下類的所有login方法都添加輔助功能
注意:只能匹配一層路徑,如果專案路徑下有多層目錄,那么*只能匹配一層路徑
-->
<aop:pointcut id="mypoint" expression="execution(* com.ty.service.*.login(..))"/>
<!-- 匹配多級包-->
<aop:pointcut id="mypoint" expression="execution(* com.ty..*.login(..))"/>
切入點函式
<aop:pointcut id="mypoint" expression="execution(* com.ty..*.login(..))"/>
<!-- args()用于匹配方法引數,比如
execution(* *(String,String) = args(String,String))
-->
<aop:pointcut id="mypoint" expression="args(String,String))"/>
<!-- within():用于類和包的切入點
expression="execution(* *..UserServiceImpl.*(..)) = within(*..UserServiceImpl)"
execution(* com.ty..*.*(..)) = within(com.ty..*)
-->
<aop:pointcut id="mypoint" expression="within(*..UserServiceImpl)"/>
<aop:pointcut id="mypoint" expression="within(com.ty..*)"/>
<!-- @annotation():為具有特殊注解的方法加入額外功能 -->
<aop:pointcut id="mypoint" expression="@annotation(com.ty.proxy.Log)"/>
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
}
public class UserServiceImpl implements UserService {
@Log()
@Override
public void login(String username, String password) {
System.out.println("登錄用戶!\t用戶名:" + username + " 密碼:" + password);
}
切入點函式間的邏輯表示
<!-- and或者&& :同時滿足運算式-->
<aop:pointcut id="mypoint" expression="execution(* login(..)) and args(String,String)"/>
<!-- or或者|| :滿足任意運算式即可-->
<aop:pointcut id="mypoint" expression="execution(public void register()) or execution(* *.login(..))"/>
或者:
<aop:pointcut id="mypoint" expression="execution(public void register()) || execution(* *.login(..))"/>
<!-- !:只要不是這個位置即可-->
<aop:pointcut id="mypoint" expression="! execution(* login(..))"/>
AOP的通知型別
-
前置通知(Before advice): 在連接點之前運行但無法阻止執行流程進入連接點的通知(除非它引發例外),
-
后置回傳通知(After returning advice):在連接點正常完成后執行的通知(例如,當方法沒有拋出任何例外并正常回傳時),
-
后置例外通知(After throwing advice): 在方法拋出例外退出時執行的通知,
-
后置通知(總會執行)(After (finally) advice): 當連接點退出的時候執行的通知(無論是正常回傳還是例外退出),
-
環繞通知(Around Advice):環繞連接點的通知,例如方法呼叫,這是最強大的一種通知型別,,環繞通知可以在方法呼叫前后完成自定義的行為,它可以選擇是否繼續執行連接點或直接回傳自定義的回傳值又或拋出例外將執行結束,
通知方法的執行順序
之前的代碼也沒說過通知的執行順序,我們集體說一下
1、正常執行:@Before--->@After--->@AfterReturning
2、例外執行:@Before--->@After--->@AfterThrowing
3、環繞通知:環繞前置-->普通前置-->目標方法執行-->環繞正常結束/出現例外-->環繞后置-->普通后置-->普通回傳或例外,
AOP的應用場景
- 日志管理
- 權限認證
- 安全檢查
- 事務控制
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/243118.html
標籤:Java
上一篇:Java入門
下一篇:JavaSE 基礎大綱
