java---------AOP面向切面
AOP代理物件的生成
Spring提供了兩種方式來生成代理物件,JDKProxy和Cglib,具體使用哪種方式生成由aopProxyFactory根據AdvisedSupport物件的配置來決定,預設的策略是如果目標類是介面,則使用JDK動態代理技術,否則使用cglib來生成代理。Spring如何使用JDK來生成代理物件,具體的生成程式碼放在jdkDynamicAopProxy這個類中。
Java動態代理機制:以巧妙的方式實現代理模式的設計理念,對一個物件新增代理時,可以使用動態代理,只有型別物件,沒有例項物件,這時候可以使用靜態代理。
java的靜態代理:是代理模式的一種使用,靜態代理的構建就是實現了被代理類介面的類,其中儲存了一個被代理類的例項。在介面的實現方法中,呼叫被代理物件對應的方法,同時新增需要的其他操作。例項:
1)先定義一個介面 package com.proxy.staticclass; public interface HelloWorld { public void print(); } 2)定義介面的實現類 public class HelloWorldImpl implements HelloWorld{ @Override public void print() { // TODO Auto-generated method stub System.out.println("hello world"); } } 3)定義一個靜態代理類 package com.proxy.staticclass; public class StaticProxy implements HelloWorld { private HelloWorldImpl helloWorldImpl; public StaticProxy(HelloWorldImpl helloWorldImpl) { // TODO Auto-generated constructor stub this.helloWorldImpl = helloWorldImpl; } @Override public void print() { // TODO Auto-generated method stub System.out.println("before"); helloWorldImpl.print(); System.out.println("after"); } } 4)呼叫靜態代理類 public static void main(String[] args) { HelloWorld helloWorld = new StaticProxy(new HelloWorldImpl()); helloWorld.print(); }
靜態代理的缺點: 如果介面中有很多個方法,那實現代理就需要為每個方法實現代理,會存在很多重複的邏輯。不利於應用的維護。
java動態代理有兩種:基於介面進行動態代理 和 基於繼承進行的動態代理
java動態代理類位於java.lang.reflect包下,主要設計兩個類:
1、interface InvocationHandle
該介面中只定義了一個方法:
Object invoke(Object proxy, Method method,Object[] args)
在實際的使用時,第一個引數obj一般是指代理類,method是被代理的方法,args為該方法的引數陣列(無參時設定為null)
這個抽象方法在代理類中動態實現。
2、Proxy
該類即為動態代理類,其中主要包含如下內容:
protected Proxy(InvocationHandle h):建構函式,用於給內部的invocation handle賦值。
static Class<?> getProxyClass(ClassLoader loader,Class<?> ...interfaces): loader是類裝載器,interfaces 是真實類所擁有的全部介面的陣列。
Static Class<?> newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandle h):返回代理類的一個例項,返回後的代理類可以當作被代理類使用(可使用被代理類在Subject介面中宣告過的方法)。
jdk動態代理的例項
1)定義一個jdk代理類實現invocationHandle介面
public class JdkProxy implements InvocationHandler {
private HelloWorldImpl helloWorldImpl;
public JdkProxy(HelloWorldImpl helloWorldImpl) {
this.helloWorldImpl = helloWorldImpl;
// TODO Auto-generated constructor stub
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("jdk before");
Object result = null;
try {
result = method.invoke(helloWorldImpl, args);
} catch (Exception e) {
// TODO: handle exception
System.out.println("error"+e.getMessage());
throw e;
}finally{
System.out.println("jdk after");
}
// TODO Auto-generated method stub
return result;
}
}
2)、呼叫動態代理
public static void main(String[] args) {
HelloWorld helloWorld = (HelloWorld) Proxy.newProxyInstance(Client.class.getClassLoader(),
new Class[]{HelloWorld.class}, new JdkProxy(new HelloWorldImpl()));
helloWorld.sysName();
}
動態代理建立過程可分為以下四個步驟:
1、通過實現InvocationHandle介面建立自己的呼叫用處理器InvocatoinHandle handle = new InvocationHandleImpl(...);
2、通過為Proxy類指定ClassLoader物件和一組interface建立動態代理類Class clazz = Proxy.getProxyClass(classLoader,new Class[]{...});
3、通過反射機制獲取動態代理類的建構函式,其引數型別是呼叫處理器型別Constructor constructor = clazz.getConstructor(new Class[]{invocationHandle.class});
4、通過建構函式建立代理類例項,此時需要將呼叫處理器物件作為引數被傳入 Interface Proxy = (Interface) constructor.newInstance(new Object[](handle));
為了簡化建立物件,Proxy類中的newInstance方法封裝了2~4,只需要兩步即可完成代理物件的建立。
生成的ProxySubject繼承Proxy類實現Subject介面,實現的Subject的方法實際呼叫處理器的invoke方法,而invoke方法利用反射呼叫的是被代理物件的方法(Object result = method.invoke(proxied,args))
靜態代理和靜態代理的異同點
兩種代理的使用有一個限制:被代理物件必須實現一個介面,代理類只能代理介面中的方法。
不同在於,靜態代理比較簡單,編寫介面中所有方法的實現,在介面中方法數量很多時,代理類的實現比較繁瑣,動態代理則實現java反射功能,將所有方法的實現集中在一處,更加動態。
cglib:
上述所說的動態代理和靜態代理都要求被代理類實現某個介面,如果想對某個實現介面的類進行代理。
cglib是一個高效能的程式碼生成包。
1)、它廣泛的被許多AOP的框架使用,例如Spring AOP 和dynaop,為它們提供方法的interception(攔截器);
2)、hibernate使用cglib來代理單端single-ended(多對一和一對一)關聯(對集合的延遲抓取,是採取其他機制實現的);
3)、EasyMock和jMock是通過使用模板(moke)物件來測試java程式碼的包。
他們都是通過cglib來為那些沒有介面的類建立模仿(moke)物件。
cglib包的底層是通過使用一個小而快的位元組碼處理框架ASM(java位元組碼操控框架),來轉換位元組碼並生成新的類。除了cglib包,指令碼語言例如Groovy和BeanShell,也是使用ASM來生成java位元組碼。當不鼓勵直接使用ASM,因為它要求你必須對JVM內部結構包括class檔案的格式和指令集都很熟悉。所以cglib包要依賴於ASM包,
cglib動態代理的實現程式碼:
1)定義一個cglib的動態代理類
public Object intercept(Object obj,Method method,Object[] args,MethodProxy proxy) throws Throwable{
System.out.println("before in cglib");
Object result = null;
try {
result = proxy.incokeSuper(obj,args);
} catch (Exception e) {
// TODO: handle exception
System.out.println("ex:"+e.getMessage());
}finally{
System.out.println("after in cglib");
}
return result;
}
2)呼叫cglib的的代理
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(HelloWorldImpl.class);
enhancer.setCallback(new CglibProxy());
HelloWorld helloWorld = enhancer.create();
helloWorld.print();
}
jdk動態代理和cglib動態代理的區別
jdk只能針對有介面的類的介面方法進行動態代理
cglib基於繼承來實現代理,無法對static、final類進行代理
jdk代理和cglib基於繼承來實現代理,無法對peivate、static方法驚醒代理
spring AOP如何選擇兩種動態代理模式:
如果目標物件實現了介面,則預設採用jdk動態代理
如果目標物件沒有實現介面,則採用cglib進行動態代理
如果目標物件實現了介面,且強制cglib代理,則使用cglib代理
面向切面的aspect註解: @Aspect @Pointcut @Advice
Spring AOP的使用場景:
authentication 許可權
caching 快取
context passing 內容快取
Error handling 錯誤處理
Lazy loading 懶載入
Debugging 除錯
PerSistence持久化
Resource pooling 資源池
Synchronization 同步
Transaction 事務
logging,tracing,profilong and monitoring 記錄跟蹤 優化 校準
AOP的相關概念
Aspect(方面):一個關注點的模組化,這個關注點實現可能另外橫切多個物件,事務管理是J2EE應用中一個很好的橫切關注點,方面用spring的Advisor或攔截器實現。
Joinpoint(連線點):程式執行過程中明確的點,如方法的呼叫或特定的異常被丟擲。
Advice(通知):在特定的連線點,AOP框架執行的動作,各種型別的通知包括“around”、“before” 和“thorws”通知。
通知型別將在下面討論,許多AOP框架包括spring都是以攔截器做通知模型,維護一個“圍繞”連線點的攔截器鏈。
Spring中定義了四個advice:BeforeAdvice,AfterAdvice,ThrowAdvice和DynamicIntroductionAdvice
Pointcut(切入點):指定一個通知將被引發的一系列連線點的集合。AOP框架必須允許開發者指定切入點:例如,使用正則表示式。Spring定義了 Pointcut介面,用來組合MethodMatcher和ClassFilter,可以通過名字很清楚的理解,
MethodMatcher是用來檢驗目標類的方法是否可以被應用此通知,而classFilter是用來檢驗是否應該用到目標類上
Introduction(引入):新增方法或欄位到被通知的類。Spring允許引入新的介面到任何被通知的物件。例如,你可以使用一個引入使任何物件實現 IsModified介面,來化簡快取。Spring中要使用Introduction,可有通過DelegatingIntroductionInterceptor來實現通知,通過DefaultIntroductionAdvisor來配置Advice和代理類藥實現的介面。
Target Object(目標物件):包含連線點的物件,也被稱作被通知或被代理物件
AOP Proxy(AOP代理):AOP框架建立的物件,包含通知,在Spring中,AOP代理可以是JDK動態代理或者CGLIB代理
如何使用Spring AOP
1):配置檔案(xml)來進行,大概有四種方式:
a. 配置ProxyFactoryBean,顯式地設定advisors,advice ,target等。
b. 配置AutoProxyCreator,這種方式下,還是和以前一樣使用定義的bean,但是從容器中獲得的已經是代理物件。
c. 通過<aop:config>類配置
d. 通過<aop:aspect-autoproxy>來配置,使用AspectJ的註解標識通知及切入點。
也可以直接使用ProxyFactory來程式設計的方式使用Spring AOP,通過ProxyFactory提供的方法可以設定target物件,Advisor等相關配置,最終通過getProxy()方法來獲取代理物件。