代理模式
代理模式的產生:明星A會唱歌,很多人都想找A商演,A自己不會。經紀人B,作爲明星A的代理,負責接收邀請,篩選邀請,安排演唱前後接送,粉絲公關等事宜。明星A只負責唱歌就可以了。
代理模式:抽象一下,經紀人B 這羣人就是ProxyClass-代理類,而明星A 這羣人就是Class。ProxyClass類向外提供 Class 所有的方法(功能)。外部只需調用ProxyClass的方法,就可以讓Class完成對應功能。ProxyClass 可以對調用前後進行控制。比如當有人調用ProxyClass的m1讓明星A唱歌的時候,其對參數進行篩選,再調用 Class 的 m1 讓明星A去赴演。
代理模式的設計類圖:可以看到ProxyClass擁有Class的所有方法,並調用Class的方法。我想肯定有人會問,爲什麼還需要畫蛇添足讓兩個類實現同一個接口。一是一種對外申明,Class有的接口ProxyClass全能代理。二是Class可以有很多個代理類,代理類都具有共同的代理接口,其次不需要修改現有依賴Class/ProxyClass的代碼。
JDK動態代理
JDK動態代理的產生:
- 被代理類方法特別多,代理類要做的事情雷同:假設 明星A 十項全能,有很多才藝。經紀人B 在接收邀請後做的事情卻都是篩選。那麼需要完全手寫一個新的類,然後定義所有明星A擁有的方法。
- 被代理類內容更改:再者隨着時間的推移,A的技能在不斷變化,B也又要隨之修改,非常繁雜。
- 代理類更換被代理類:假設 經紀人B 不想再給 明星A 代理了,換了另一個 運動明星C,則又得根據 明星C 修改 經紀人B 的代理方法。
本質想說明兩點,普通的靜態代理,也就是自己編寫代理類:一個是代理類編寫時的繁雜的重複,另一個是代理類ProxyClass 受到 被代理類Class 的變更的影響很大。
JDK動態代理的設計:
- 供自定義方法模板:因爲通常代理類的所有方法體都類似,因此不再定義一個代理類,只定義一個如下的方法模板即可。Java提供了一個java.lang.reflect.InvacationHandler接口,實現該接口的invoke方法,在方法體中寫下述自定義模板。
do proxy stuff
call origin class's method like sing
do proxy stuff
- 生成代理類:Java不是在編譯時生成的類,而是在運行時生成類,Java提供了一個java.lang.reflect.Proxy類,類中有一個newInstance方法:Proxy.newInstance(類加載器ClassLoader,被代理的接口列表Class<?>[],自定義的方法模板InvocationHandler),返回一個爲指定接口代理的代理對象。
JDK動態代理的使用:代碼如下,我代理的是java.lang.Runnable接口。
package basics.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* @author 100101001
* @date 12/23/2019 10:18 AM
*/
public class jdk {
private static int a = 123;
public static void main(String[] args) {
Target t =new Target();
//運行時生成類 Proxy$0 extends Proxy implements Runnable
Runnable a = (Runnable) Proxy.newProxyInstance(Runnable.class.getClassLoader(), new Class[]{
Runnable.class
}, new InvocationHandler() { //這裏採用了匿名類實現接口
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("jdk proxy before method..");
method.invoke(t, args);
System.out.println("jdk proxy after method..");
//Runnable接口的run方法無返回值
return null;
}
});
new Thread(a).start();
}
//靜態內部類等效於外部類,只不過類名jdk$Target
//能訪問jdk的所有靜態變量和成員
static class Target implements Runnable{
@Override
public void run() {
System.out.println("target run"+a);
}
}
}
運行結果:
jdk proxy before method..
target run123
jdk proxy after method..
運行局部變量:a就是代理對象$Proxy0,其內部有一個h就是InvocationHnadler,因爲它是jdk的一個匿名內部類,所以取類名 jdk$1,而t也是jdk的內部類Target(定義時)對象,所以取名 jdk$Target(可以和外部Target類區分,外部通過jdk.Target來訪問該類)。
動態生成的代理類:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class Proxy0
extends Proxy
implements Runnable
{
// 在靜態初始化代碼塊中使用反射獲取Method
// 比如 m3=Class.forName(java.lang.Runnable).getMethod("run", new Class[0])
// h是構造Proxy時,傳入的InvocationHandler
// 在run方法體中直接 this.h.invoke(this, m3, null);
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
public ProxySubject(InvocationHandler paramInvocationHandler)
{
super(paramInvocationHandler);
}
public final boolean equals(Object paramObject)
{
try
{
return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final void run()
{
try
{
this.h.invoke(this, m3, null);
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final String toString()
{
try
{
return (String)this.h.invoke(this, m2, null);
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final int hashCode()
{
try
{
return ((Integer)this.h.invoke(this, m0, null)).intValue();
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
static
{
try
{
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
m3 = Class.forName("java.lang.Runnable").getMethod("run", new Class[0]);
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
return;
}
catch (NoSuchMethodException localNoSuchMethodException)
{
throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
}
catch (ClassNotFoundException localClassNotFoundException)
{
throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
}
}
}
AOP: Aspect-Oriented Programming
面向切面編程的產生:在OOP(Object-Oriented Progamming)中,方法都需要做一些共同的非功能性操作,比如對訪問加鎖,記錄調用日誌,異常處理。通過繼承提供共同的操作,有單繼承限制。因此,AOP產生了。我們把這些公共操作一一提煉成對應的代理模板(方法),組合到一個類中,這個類被稱爲切面(與繼承垂直共享相對的一種橫向共享的概念)。比如寫一個日誌切面類,定義實現兩個代理模板方法,分別是開始計時(調用原方法前)和結束計時(調用原方法前)。這個日誌切面類,記錄了方法執行時間,方便優化分析,這些使用了切面功能的方法就是切入點。切面類似之前動態代理中的實現InvocationHandler的類,切入點類似之前的動態代理中的被代理的接口中的方法。類似這樣的提煉公共操作,編寫切面類,定義切入點的過程就是AOP。
面向切面編程的應用:這一部分參考了 簡書作者:糖紙瘋了 的文章 006--【SpringBoot】AOP使用集錦,web應用框架往往會使用AOP實現功能模塊(比如框架提供對Controller類所有方法返回值序列化這一公共操作,開發者無需自行編寫),向外提供AOP工具(比如在SpringBoot中提供AOP註解,允許用戶自定義切面)。
這裏自定義接口日誌切面效果如下。在接口執行前,記錄了接口方法,接口所在類,接口參數,接口URL等信息,執行後,記錄了返回值類型。
在SpringBoot中,自定義Aspect的方法如下。
- Aspect註解切面類
- 切面類內PointCut註解方法指明代理的方法
- Before,After,AfterReturning,AfterThrowing,Around等指明切面在方法執行的流程中代做的事情
@Aspect
@Component
public class LogWebAspect {
//統計請求的處理時間
ThreadLocal<Long> startTime = new ThreadLocal<>();
/**
* 指定切點
* 匹配 com.enzoism.controller包及其子包下的所有類的所有方法
*/
@Pointcut("execution(public * com.enzoism.controller.*.*(..))")
public void webLog(){
}
/**
* 前置通知,方法調用前被調用
* @param joinPoint
*/
@Before("webLog()")
public void doBefore(JoinPoint joinPoint){
System.out.println("我是前置通知!!!");
startTime.set(System.currentTimeMillis());
//獲取目標方法的參數信息
Object[] obj = joinPoint.getArgs();
System.out.println("請求參數:"+obj);
// 代理
Signature signature = joinPoint.getSignature();
//代理的是哪一個方法
System.out.println("方法:"+signature.getName());
//AOP代理類的名字
System.out.println("方法所在包:"+signature.getDeclaringTypeName());
//AOP代理類的類(class)信息
signature.getDeclaringType();
MethodSignature methodSignature = (MethodSignature) signature;
String[] strings = methodSignature.getParameterNames();
System.out.println("參數名:"+ Arrays.toString(strings));
System.out.println("參數值ARGS : " + Arrays.toString(joinPoint.getArgs()));
// 接收到請求,記錄請求內容
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest req = attributes.getRequest();
// 記錄下請求內容
System.out.println("請求URL : " + req.getRequestURL().toString());
System.out.println("HTTP_METHOD : " + req.getMethod());
System.out.println("IP : " + req.getRemoteAddr());
System.out.println("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
}
/**
* 處理完請求返回內容
* @param ret
* @throws Throwable
*/
@AfterReturning(returning = "ret", pointcut = "webLog()")
public void doAfterReturning(Object ret) throws Throwable {
// 處理完請求,返回內容
System.out.println("方法的返回值 : " + ret);
}
/**
* 後置異常通知
* @param jp
*/
@AfterThrowing("webLog()")
public void throwss(JoinPoint jp){
System.out.println("方法異常時執行.....");
}
/**
* 後置最終通知,final增強,不管是拋出異常或者正常退出都會執行
* @param jp
*/
@After("webLog()")
public void after(JoinPoint jp){
System.out.println("方法執行時間:"+ (System.currentTimeMillis() - startTime.get()));
System.out.println();
}
/**
* 環繞通知,環繞增強,相當於MethodInterceptor
* @param pjp
* @return
*/
@Around("webLog()")
public Object arround(ProceedingJoinPoint pjp) {
try {
Object o = pjp.proceed();
return o;
} catch (Throwable e) {
e.printStackTrace();
return null;
}
}
}
切面除了可以應用到某個包下的所有類中的所有方法,還可以指定應用於某個註解??待更新
@Aspect
@Component
public class LogAnnotationAspect {
/**
* 指定切點
* 匹配 被添加LogAnnotation標註的方法
*/
@Pointcut("@annotation(logAnnotation)")
public void pointCut(LogAnnotation logAnnotation) {
}
/**
* 環繞通知,環繞增強,相當於MethodInterceptor
* @param pjp
* @return
*/
@Around("pointCut(logAnnotation)")
public Object arround(ProceedingJoinPoint pjp, LogAnnotation logAnnotation) {
try {
Object o = pjp.proceed();
System.out.println("---------------直接指定到logAnnotation位置");
System.out.println("actionType:"+logAnnotation.actionType());
System.out.println("controllerName:"+logAnnotation.controllerName());
System.out.println("module:"+logAnnotation.module());
return o;
} catch (Throwable e) {
e.printStackTrace();
return null;
}
}
}