Java動態代理模式
1、怎樣實現靜態代理模式?
可以想到的方式起碼有兩種繼承和聚合。
創建一個接口
package com.jyd.proxy;
/**
* 定義一個能夠工作的接口。定義一系列操作方法
* @author hadoop
*
*/
public interface Workable {
void renting();
}
創建一個類繼承這個接口
package com.jyd.proxy;
import java.util.Random;
/**
* 定義一個中介人來實現能夠工作的接口
* @author hadoop
*
*/
public class Middleman implements Workable {
private Random random = new Random();
@Override
public void renting() {
//中介操作
try {
Thread.sleep(random.nextInt(10000));
System.out.println("中介開始租房...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
創建client類來調用此方法
package com.jyd.proxy;
public class Client {
public static void main(String[] args) {
Workable workable = new Middleman();
workable.renting();
}
}
1.1、使用繼承來實現靜態代理:
假設我想知道一個類里的某一個方法一共運行多長時間。怎樣實現了?前提是我們并沒有這個類的源代碼
創建一個類來繼承中間類
package com.jyd.proxy;
public class MiddlemanTimeProxy extends Middleman {
@Override
public void renting() {
long startTime = System.currentTimeMillis();
System.out.println("開始計時: " + startTime);
super.renting();//一個類繼承了父類,super會調用自己父類
long endTime = System.currentTimeMillis();
System.out.println("這種方法一共使用多長時間:" + (endTime - startTime));
}
}
創建一個client類來調用此代理方法
package com.jyd.proxy;
public class TimeProxyClient {
public static void main(String[] args) {
Workable workable = new MiddlemanTimeProxy();
workable.renting();
}
}
依次來類推:假設如今我想實現一個記錄日志的功能,那么的話又一次創建一個類來繼承中間人的方法在super. renting()方法的前后來處理日志的操作;如今我又像實現一個權限驗證的
功能,那么須要再次創建一個類里繼承中間人的方法在super.renting()方法的前后來處理權限驗證操作;
重點:
再想假設我想實現一個先記錄日志再實現權限驗證的功能,那么是不是再創建一個
類來繼承中間人的方法在super. renting()方法的前后來處理日志的操作在驗證權限,假設這么做的話,就會出現無窮無盡的類繼承到最后會出現一個類爆炸的情況。這里我們得出一個結論:假設通過類繼承來實現靜態代理會出現類爆炸。
怎么辦???
1.2、使用聚合來實現靜態代理:
假設我想知道一個類里的某一個方法一共運行多長時間。怎樣實現了?前提是我們并沒有這個類的源代碼
創建一個記錄日志聚合的代理類
package com.jyd.proxy;
public class MiddlemanTimeProxy implements Workable{
//聚合就是一個類包括還有一類的引用
private Workable workable;
public MiddlemanTimeProxy(Workable workable) {
super();
this.workable = workable;
}
@Override
public void renting() {
long startTime = System.currentTimeMillis();
System.out.println("開始計時: " + startTime);
workable.renting();
long endTime = System.currentTimeMillis();
System.out.println("這種方法一共使用多長時間:" + (endTime - startTime));
}
}
創建一個client類來調用此代理方法
package com.jyd.proxy;
public class TimeProxyClient {
public static void main(String[] args) {
Middleman m = new Middleman();
MiddlemanTimeProxy mt = new MiddlemanTimeProxy(m);
mt.renting();
}
}
創建一個權限檢查聚合的代理類
package com.jyd.proxy;
public class MiddlemanPermissionProxy implements Workable{
private Workable workable;
public MiddlemanPermissionProxy(Workable workable) {
super();
this.workable = workable;
}
@Override
public void renting(){
System.out.println("開始檢查權限...");
workable.renting();
System.out.println("結束檢查權限...");
}
}
比方說:如今須要實現一個計算時間的功能在檢查權限的代理功能。那么的話如今我就不須要又一次一個代理類,直接使用之前實現的代理類組合一下就可以,這樣能夠有效的降低代理類的實現
package com.jyd.proxy;
public class ProxyClient {
public static void main(String[] args) {
Workable workable = new Middleman();
MiddlemanTimeProxy mt = new MiddlemanTimeProxy(workable);
MiddlemanPermissionProxy mp = new MiddlemanPermissionProxy(mt);
mp.renting();
}
}
結論:聚合實現靜態代理類能夠有效降低代理類的創建,所以集合實現靜態代理要比繼承實現要好。
2、動態代理
那么問題來了。假設一個項目中有非常多類要實現各個方法都實現計算方法的運行時間怎么處理???
為每一個類都創建一個代理對象嗎?顯然這樣做的方式是很不可取的,那么我們來分析之前實現的靜態代理類,我們發現一個問題一個代理類中包括被代理的對象,被代理對象去調用須要運行的方法,--這種話基本上這個代理類是一個模板式的代碼—>發揮我們想象,這個代理類可不能夠動態的去創建???
2.1、將代理對象刪除寫成固定字符串動態編譯執行
能夠把代理對象模板類寫成一個字符串,通過java的JavaCompile編譯器動態來編譯此文件,然后通過URLClassLoader載入器來載入對象,然后通過反射來動態調用須要被代理的方法
package com.jyd.proxy;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
public class Proxy {
private static final String BASE_TARGET_PATH = "d:/src/";
private static final String TARGET_PATH = BASE_TARGET_PATH + "com/jyd/proxy";
private static final String RT = "\r\n";
private static final String PROXY_CLASS_NAME = "$Proxy";
private static URLClassLoader classLoader;
/**
* 1、分析全部靜態代理類,模式都是固定的如
* 開始之前處理邏輯
* target.method()目標對象調用方法
* 目標對象調用之后處理邏輯
*
* 處于這樣的情況的話,那么我通過代碼自己主動生成模式固定的代理類
* @return
*/
public static Object newInstance(){
// 1、創建一個段代碼的字符通過代碼編譯。生成對象
String proxySource = "package com.jyd.proxy;"+RT+
"public class "+PROXY_CLASS_NAME+" implements Workable{"+RT+
" private Workable workable;"+RT+
" public "+PROXY_CLASS_NAME+"(Workable workable) {"+RT+
" super();"+RT+
" this.workable = workable;"+RT+
" }"+RT+
" @Override"+RT+
" public void renting() {"+RT+
" long startTime = System.currentTimeMillis();"+RT+
" System.out.println(\"開始計時: \" + startTime);"+RT+
" workable.renting();"+RT+
" long endTime = System.currentTimeMillis();"+RT+
" System.out.println(\"這種方法一共使用多長時間:\" + (endTime - startTime));"+RT+
" }"+RT+
"}";
//推斷目標路徑是否存在
File targetDir = new File(TARGET_PATH);
if(!targetDir.exists())targetDir.mkdirs();
//將源代碼文件生成到磁盤上
File file = new File(TARGET_PATH, PROXY_CLASS_NAME+".java");
try {
FileWriter fw = new FileWriter(file);
fw.write(new String(proxySource.getBytes(), "UTF-8"));
fw.close();
} catch (Exception e) {
e.printStackTrace();
}
//使用代碼動態編譯此java文件
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager standardJavaFileManager = compiler.getStandardFileManager(null, null, null);
List<File> files = new ArrayList<File>();
files.add(file);
Iterable compilationUnits = standardJavaFileManager.getJavaFileObjectsFromFiles(files);
CompilationTask task = compiler.getTask(null, standardJavaFileManager, null, null, null, compilationUnits);
task.call();
//通過URLClassLoader來載入編譯好的字節碼文件
URL[] urls = null;;
try {
urls = new URL[]{new URL("file:/" + BASE_TARGET_PATH)};
} catch (MalformedURLException e) {
e.printStackTrace();
}
URLClassLoader classLoader = new URLClassLoader(urls);
//此處載入class類名須要類的全路徑
Class<?> clazz = null;
try {
clazz = classLoader.loadClass("com.jyd.proxy."+PROXY_CLASS_NAME);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
Object proxyObject = null;
try {
Constructor<?
> constructor = clazz.getConstructor(new Class[]{Workable.class});
proxyObject = constructor.newInstance(new Middleman());
} catch (Exception e) {
e.printStackTrace();
}
return proxyObject;
}
public static void main(String[] args) {
Workable workable = (Workable)Proxy.newInstance();
workable.renting();
}
}
2.2、動態生成代理對象可代理不論什么接口
2.1中的動態編譯的java對象眼下所實現的接口還是固定這樣不符合我的需求,思考:怎樣實現能夠代理不論什么接口的代理類???
package com.jyd.proxy;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
public class Proxy {
private static final String BASE_TARGET_PATH = "d:/src/";
private static final String TARGET_PATH = BASE_TARGET_PATH + "com/jyd/proxy";
private static final String RT = "\r\n";
private static final String PROXY_CLASS_NAME = "$Proxy";
private static URLClassLoader classLoader;
/**
* 1、分析全部靜態代理類。模式都是固定的如
* 開始之前處理邏輯
* target.method()目標對象調用方法
* 目標對象調用之后處理邏輯
*
* 處于這樣的情況的話,那么我通過代碼自己主動生成模式固定的代理類
* @return
*/
public static Object newInstance(Class<?> interf){
// 1、創建一個段代碼的字符通過代碼編譯,生成對象
// 1.2、怎樣將代理對象實現為動態的?
// 能夠在創建Proxy的newInstance方法的時候將接口對象傳入再通過反射來獲取此接口中全部的方法
// " @Override"+RT+
// " public void renting() {"+RT+
// " long startTime = System.currentTimeMillis();"+RT+
// " System.out.println(\"開始計時: \" + startTime);"+RT+
// " workable.renting();"+RT+
// " long endTime = System.currentTimeMillis();"+RT+
// " System.out.println(\"這種方法一共使用多長時間:\" + (endTime - startTime));"+RT+
// " }"+RT+
// 1.3、一個接口中有N個方法須要代理實現那么僅僅能循環獲取該類中全部的方法
String methodSources = "";
Method[] methods = interf.getMethods();
if(null != methods && methods.length > 0){
for(Method m : methods){
methodSources += " @Override"+RT+
" public void "+m.getName()+"() {"+RT+
" long startTime = System.currentTimeMillis();"+RT+
" System.out.println(\"開始計時: \" + startTime);"+RT+
" target."+m.getName()+"();"+RT+
" long endTime = System.currentTimeMillis();"+RT+
" System.out.println(\"這種方法一共使用多長時間:\" + (endTime - startTime));"+RT+
" }"+RT;
}
}
String proxySource = "package com.jyd.proxy;"+RT+
"public class "+PROXY_CLASS_NAME+" implements "+interf.getName()+"{"+RT+
" private "+interf.getName()+" target;"+RT+
" public "+PROXY_CLASS_NAME+"("+interf.getName()+" target) {"+RT+
" super();"+RT+
" this.target = target;"+RT+
" }"+RT+
methodSources +RT+
"}";
//推斷目標路徑是否存在
File targetDir = new File(TARGET_PATH);
if(!targetDir.exists())targetDir.mkdirs();
//將源代碼文件生成到磁盤上
File file = new File(TARGET_PATH, PROXY_CLASS_NAME+".java");
try {
FileWriter fw = new FileWriter(file);
fw.write(new String(proxySource.getBytes(), "UTF-8"));
fw.close();
} catch (Exception e) {
e.printStackTrace();
}
//使用代碼動態編譯此java文件
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager standardJavaFileManager = compiler.getStandardFileManager(null, null, null);
List<File> files = new ArrayList<File>();
files.add(file);
Iterable compilationUnits = standardJavaFileManager.getJavaFileObjectsFromFiles(files);
CompilationTask task = compiler.getTask(null, standardJavaFileManager, null, null, null, compilationUnits);
task.call();
//通過URLClassLoader來載入編譯好的字節碼文件
URL[] urls = null;;
try {
urls = new URL[]{new URL("file:/" + BASE_TARGET_PATH)};
} catch (MalformedURLException e) {
e.printStackTrace();
}
URLClassLoader classLoader = new URLClassLoader(urls);
//此處載入class類名須要類的全路徑
Class<?> clazz = null;
try {
clazz = classLoader.loadClass("com.jyd.proxy."+PROXY_CLASS_NAME);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
Object proxyObject = null;
try {
Constructor<?
> constructor = clazz.getConstructor(new Class[]{Workable.class});
proxyObject = constructor.newInstance(new Middleman());
} catch (Exception e) {
e.printStackTrace();
}
return proxyObject;
}
public static void main(String[] args) {
Workable workable = (Workable)Proxy.newInstance(Workable.class);
workable.renting();
}
}
2.3、規則由我定,實現靠子類自己
通過2.2的處理,proxy類能夠動態生成代理不論什么接口代理類,可是美中不足的是代理類中全部做的事情是固定。這樣則沒有靈活性。思考???
此處能不能由子類自己實現須要處理的邏輯了?能夠定義一個規范。由子類自己去實現,因為多態方式,當程序執行的時候那個子類來實現的則調用子類的方法
那么此處須要定義一個接口:
package com.jyd.proxy;
import java.lang.reflect.Method;
/**
* 定義一個出來規范的邏輯,讓子類去實現自己的業務邏輯
* @author hadoop
*
*/
public interface InvocationHandler {
/**
*
* @param o 當前實例的對象
* @param method 需要被代理的方法
*/
public void invok(Object o, Method method);
}
創建一個子類處理類來實現記錄方法時間的類
package com.jyd.proxy;
import java.lang.reflect.Method;
public class TimeInvocationHandler implements InvocationHandler {
private Object target;
public TimeInvocationHandler(Object target) {
super();
this.target = target;
}
@Override
public void invok(Object o, Method method) {
//開始處理的業務邏輯
long startTime = System.currentTimeMillis();
System.out.println("開始計時: " + startTime);
try {
//被代理的對象的方法須要調用,針對自己的目標對象,此處沒有目標對象。須要將目標對象通過構成函數傳入就可以
method.invoke(target, new Object[]{});
} catch (Exception e) {
e.printStackTrace();
}
long endTime = System.currentTimeMillis();
System.out.println("這種方法一共使用多長時間:" + (endTime - startTime));
}
}
通過代理類動態創建代理對象
package com.jyd.proxy;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
public class Proxy {
private static final String BASE_TARGET_PATH = "d:/src/";
private static final String TARGET_PATH = BASE_TARGET_PATH + "com/jyd/proxy";
private static final String RT = "\r\n";
private static final String PROXY_CLASS_NAME = "$Proxy";
/**
* 1、分析全部靜態代理類,模式都是固定的如
* 開始之前處理邏輯
* target.method()目標對象調用方法
* 目標對象調用之后處理邏輯
*
* 處于這樣的情況的話,那么我通過代碼自己主動生成模式固定的代理類
* @return
*/
public static Object newInstance(Class<?> interf, InvocationHandler h){
// 1、創建一個段代碼的字符通過代碼編譯。生成對象
// 1.2、怎樣將代理對象實現為動態的?
// 能夠在創建Proxy的newInstance方法的時候將接口對象傳入再通過反射來獲取此接口中全部的方法
// " @Override"+RT+
// " public void renting() {"+RT+
// " long startTime = System.currentTimeMillis();"+RT+
// " System.out.println(\"開始計時: \" + startTime);"+RT+
// " workable.renting();"+RT+
// " long endTime = System.currentTimeMillis();"+RT+
// " System.out.println(\"這種方法一共使用多長時間:\" + (endTime - startTime));"+RT+
// " }"+RT+
// 1.3、一個接口中有N個方法須要代理實現那么僅僅能循環獲取該類中全部的方法
// String methodSources = "";
// Method[] methods = interf.getMethods();
// if(null != methods && methods.length > 0){
// for(Method m : methods){
// methodSources += " @Override"+RT+
// " public void "+m.getName()+"() {"+RT+
// " long startTime = System.currentTimeMillis();"+RT+
// " System.out.println(\"開始計時: \" + startTime);"+RT+
// " target."+m.getName()+"();"+RT+
// " long endTime = System.currentTimeMillis();"+RT+
// " System.out.println(\"這種方法一共使用多長時間:\" + (endTime - startTime));"+RT+
// " }"+RT;
// }
// }
//
// 如今須要使用一個接口來規范此業務操作邏輯。須要改寫如今處理業務,那么須要處理邏輯的類傳入到代理對象中
//
String methodSources = "";
Method[] methods = interf.getMethods();
if(null != methods && methods.length > 0){
for(Method m : methods){
methodSources += " @Override"+RT+
" public void "+m.getName()+"() {"+RT+
" try {"+RT+
" java.lang.reflect.Method md = " + interf.getName() +".class.getMethod(\""+m.getName()+"\", new Class[]{});"+RT+
" h.invok(this, md);"+RT+
" }catch(Exception e){e.printStackTrace();}"+RT+
" }"+RT;
}
}
// 此處須要把InvocationHandler對象傳入到代理類中,通過InvocationHandler中通過反射來調用被代理的對象的方法
// String proxySource = "package com.jyd.proxy;"+RT+
// "public class "+PROXY_CLASS_NAME+" implements "+interf.getName()+"{"+RT+
// " private "+interf.getName()+" target;"+RT+
// " public "+PROXY_CLASS_NAME+"("+interf.getName()+" target) {"+RT+
// " super();"+RT+
// " this.target = target;"+RT+
// " }"+RT+
// methodSources +RT+
// "}";
String proxySource = "package com.jyd.proxy;"+RT+
"public class "+PROXY_CLASS_NAME+" implements "+interf.getName()+"{"+RT+
" private com.jyd.proxy.InvocationHandler h;"+RT+
" public "+PROXY_CLASS_NAME+"(com.jyd.proxy.InvocationHandler h) {"+RT+
" super();"+RT+
" this.h = h;"+RT+
" }"+RT+
methodSources +RT+
"}";
//推斷目標路徑是否存在
File targetDir = new File(TARGET_PATH);
if(!targetDir.exists())targetDir.mkdirs();
//將源代碼文件生成到磁盤上
File file = new File(TARGET_PATH, PROXY_CLASS_NAME+".java");
try {
FileWriter fw = new FileWriter(file);
fw.write(new String(proxySource.getBytes(), "UTF-8"));
fw.close();
} catch (Exception e) {
e.printStackTrace();
}
//使用代碼動態編譯此java文件
// 獲取java中JDK中java編譯器,此編譯器僅僅在jdk1.6之后提供。假設要做這個實驗jdk版本號大于1.6
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager standardJavaFileManager = compiler.getStandardFileManager(null, null, null);
List<File> files = new ArrayList<File>();
files.add(file);
Iterable compilationUnits = standardJavaFileManager.getJavaFileObjectsFromFiles(files);
CompilationTask task = compiler.getTask(null, standardJavaFileManager, null, null, null, compilationUnits);
task.call();
//通過URLClassLoader來載入編譯好的字節碼文件
URL[] urls = null;;
try {
urls = new URL[]{new URL("file:/" + BASE_TARGET_PATH)};
} catch (MalformedURLException e) {
e.printStackTrace();
}
URLClassLoader classLoader = new URLClassLoader(urls);
//此處載入class類名須要類的全路徑
Class<?> clazz = null;
try {
clazz = classLoader.loadClass("com.jyd.proxy."+PROXY_CLASS_NAME);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
Object proxyObject = null;
try {
// Constructor<?> constructor = clazz.getConstructor(new Class[]{Workable.class});
// proxyObject = constructor.newInstance(new Middleman());
// 此時proxy類中就不能是接口對象而InvocationHandler
Constructor<?> constructor = clazz.getConstructor(InvocationHandler.class);
proxyObject = constructor.newInstance(h);
} catch (Exception e) {
e.printStackTrace();
}
try {
classLoader.close();
} catch (IOException e) {
e.printStackTrace();
}
return proxyObject;
}
public static void main(String[] args) {
Workable workable = (Workable)Proxy.newInstance(Workable.class, new TimeInvocationHandler(new Middleman()));
workable.renting();
}
}
3、jdk自帶的動態代理實現
3.1、創建一個接口
package com.jyd.proxy;
/**
* 定義一個能夠工作的接口,定義一系列操作方法
* @author hadoop
*
*/
public interface Workable {
void renting();
}
3.2、定義一個真實對象來實現Workable接口
package com.jyd.proxy;
import java.util.Random;
/**
* 定義一個中介人來實現能夠工作的接口
* @author hadoop
*
*/
public class Middleman implements Workable {
private Random random = new Random();
@Override
public void renting() {
//中介操作
try {
Thread.sleep(random.nextInt(1000));
System.out.println("中介開始租房...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
3.3、定義業務處理邏輯的子類實現InvocationHandler接口
package com.jyd.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class TimeInvocationHandler implements InvocationHandler {
private Object target;
public TimeInvocationHandler(Object target) {
super();
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//開始處理的業務邏輯
long startTime = System.currentTimeMillis();
System.out.println("開始計時: " + startTime);
Object result = null;
try {
//被代理的對象的方法須要調用,針對自己的目標對象。此處沒有目標對象。須要將目標對象通過構成函數傳入就可以
result = method.invoke(target, args);
} catch (Exception e) {
e.printStackTrace();
}
long endTime = System.currentTimeMillis();
System.out.println("這種方法一共使用多長時間:" + (endTime - startTime));
return result;
}
}
3.4、定義一個client來使用代理對象
package com.jyd.proxy;
import java.lang.reflect.Proxy;
public class Client {
public static void main(String[] args) {
Workable workable = (Workable) Proxy.newProxyInstance(Workable.class.getClassLoader(), new Class[]{Workable.class}, new TimeInvocationHandler(new Middleman()));
workable.renting();
}
}
4、Spring AOP
4.1、Spring AOP的實現原理
AOP(Aspect-OrientedProgramming。面向方面編程),能夠說是OOP(Object-OrientedPrograming,面向對象編程)的補充和完好。OOP引入封裝、繼承和多態性等概念來建立一種對象層次結構,用以模擬公共行為的一個集合。當我們須要為分散的對象引入公共行為的時候。OOP則顯得無能為力。
也就是說,OOP同意你定義從上到下的關系。但并不適合定義從左到右的關系。比如日志功能。日志代碼往往水平地散布在全部對象層次中。而與它所散布到的對象的核心功能毫無關系。對于其它類型的代碼。如安全性、異常處理和透明的持續性也是如此。
這樣的散布在各處的無關的代碼被稱為橫切(cross-cutting)代碼,在OOP設計中。它導致了大量代碼的反復。而不利于各個模塊的重用。
4.2、Spring AOP的術語
Aspect(方面):一個關注點的模塊化,這個關注點實現可能另外橫切多個對象。
事務管理是J2EE應用中一個非常好的橫切關注點樣例。方面用spring的Advisor或攔截器實現。
Joinpoint(連接點):程序運行過程中明白的點,如方法的調用或特定的異常被拋出。
Advice(通知):在特定的連接點,AOP框架運行的動作。
各種類型的通知包含“around”、“before”、“throws”通知。
Pointcut(切入點):指定一個通知將被引發的一系列連接點的集合。
weaving(織入):組裝方面來創建一個被通知對象。
浙公網安備 33010602011771號