前置-代理設計
我們先介紹相關的幾種代理設計方法
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:///artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
<!-- https:///artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
<!-- https:///artifact/aopalliance/aopalliance -->
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<!-- https:///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./schema/beans"
xmlns:xsi="http://www./2001/XMLSchema-instance" xmlns:aop="http://www./schema/aop"
xsi:schemaLocation="http://www./schema/beans http://www./schema/beans/spring-beans.xsd http://www./schema/aop https://www./schema/aop/spring-aop.xsd">
<!--創建業務類的對象和輔助功能類的對象-->
<bean id="userService" class="com.ty.service.UserServiceImpl"></bean>
<bean id="before" class="com.ty.proxy.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 = 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" class="com.ty.service.UserServiceImpl"></bean>
<bean id="logUtil" class="com.ty.util.LogUtil"></bean>
<bean id="logUtil2" class="com.ty.util.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 = "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 = "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 = "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 = "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 = "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" class="com.ty.service.UserServiceImpl"></bean>
<bean id="postProcessor" class="com.ty.proxy.ProxyBeanPostProcessor"></bean>
這就是運用了postProcessAfterInitialization后置處理器方法,創建的是userService對象,但是經過一步步的處理到后置處理器這里進行加工,看到創建了代理對象,所以最后就返回了一個代理對象
AOP
AOP的概念
前面我們寫過Spring動態代理的代碼,我們沒具體介紹,Spring動態代理對應的就可以看成AOP,AOP是一種編程思想模型,
對應的就是學過的OOP
面向切面編程:基于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的應用場景
|