今天記錄一下春天(Spring)的AOP拴事,從以下幾個(gè)部分介紹:
- 代理模式
- Java注解(Annotation)
- AOP中的Java動(dòng)態(tài)代理兩種方式
- AOP中的術(shù)語(yǔ)與應(yīng)用
AOP為Aspect Oriented Programming践剂,意為:面向切面編程,通過(guò)預(yù)編譯方式和運(yùn)行期動(dòng)態(tài)代理實(shí)現(xiàn)程序功能的統(tǒng)一維護(hù)的一種技術(shù)陨瘩。降低程序業(yè)務(wù)之間的耦合程度腕够,提高可維護(hù)性與開(kāi)發(fā)效率。里面的基礎(chǔ)知識(shí)就是Java代理與注解舌劳。最后就是AOP相關(guān)的術(shù)語(yǔ)與應(yīng)用燕少。
代理模式
之前在Android源碼中也經(jīng)常遇到代理模式,比如Binder進(jìn)程間通信蒿囤,使用AIDL過(guò)程中客们,當(dāng)前線程傳遞數(shù)據(jù)會(huì)判斷是否需要跨進(jìn)程,如果需要跨進(jìn)程材诽,此時(shí)封裝數(shù)據(jù)為Parcelable對(duì)象底挫,然后再執(zhí)行transact函數(shù),而此時(shí)AIDL中封裝的代理完成了這些操作脸侥。類似下面例子:
//吃東西的接口
public interface Eat {
void eat();
}
//吃蘋(píng)果類建邓,實(shí)現(xiàn)吃的接口
public class EatApple implements Eat {
@Override
public void eat() {
System.out.println(">>>>吃掉蘋(píng)果");
}
}
//洗蘋(píng)果工具類
public class WashApple {
public static void washAnApple() {
System.out.println("先洗洗蘋(píng)果");
}
}
//代理類,洗完了再安全的吃睁枕。
public class EatAppleProxy {
EatApple eatApple;
public EatAppleProxy() {
eatApple = new EatApple();
}
//健康的吃蘋(píng)果
public void eatApple() {
//先把蘋(píng)果洗干凈
WashApple.washAnApple();
//吃
eatApple.eat();
}
}
我們只需要調(diào)用代理類官边,EatAppleProxy就可以了,而不需要自己再去寫(xiě)其中的過(guò)程外遇。
Java注解(Annotation)
注解(Annotation) 為我們?cè)诖a中添加信息提供了一種形式化的方法注簿,是我們可以在稍后某個(gè)時(shí)刻方便地使用這些數(shù)據(jù),Annotation(注解)是JDK1.5及以后版本引入的跳仿。它可以用于創(chuàng)建文檔诡渴,跟蹤代碼中的依賴性,甚至執(zhí)行基本編譯時(shí)檢查菲语。簡(jiǎn)單點(diǎn)講就是給一些代碼添加標(biāo)記妄辩,便于后續(xù)對(duì)添加統(tǒng)一標(biāo)記的代碼做處理惑灵。
學(xué)習(xí)注解,最好學(xué)會(huì)自定義注解眼耀,這樣能更好理解注解原理英支。
元注解的作用就是負(fù)責(zé)注解其他注解。Java5.0定義了4個(gè)標(biāo)準(zhǔn)的meta-annotation類型哮伟,它們被用來(lái)提供對(duì)其它 annotation類型作說(shuō)明干花。Java5.0定義的元注解:
- 1@Target,
- 2@Retention,
- 3@Documented,
- 4@Inherited
@Target
說(shuō)明了Annotation所修飾的對(duì)象范圍,取值(ElementType)有:
- CONSTRUCTOR:用于描述構(gòu)造器
- FIELD:用于描述域
- LOCAL_VARIABLE:用于描述局部變量
- METHOD:用于描述方法
- PACKAGE:用于描述包
- PARAMETER:用于描述參數(shù)
- TYPE:用于描述類、接口(包括注解類型) 或enum聲明
@Retention
定義了該Annotation被保留的時(shí)間長(zhǎng)短
- 在源文件中有效(即源文件保留)
- CLASS:在class文件中有效(即class保留)
- RUNTIME:在運(yùn)行時(shí)有效(即運(yùn)行時(shí)保留)
@Documented
用于生成Java的Doc澈吨。
@Inherited
元注解是一個(gè)標(biāo)記注解把敢,@Inherited闡述了某個(gè)被標(biāo)注的類型是被繼承的寄摆。如果一個(gè)使用了@Inherited修飾的annotation類型被用于一個(gè)class谅辣,則這個(gè)annotation將被用于該class的子類。
如何自定義注解:
/*
* 定義注解 Test
* 為方便測(cè)試:注解目標(biāo)為類 方法婶恼,屬性及構(gòu)造方法
* 注解中含有三個(gè)元素 id ,name和 gid;
* id 元素 有默認(rèn)值 0
*/
@Target({TYPE,METHOD,FIELD,CONSTRUCTOR})
@Retention(RetentionPolicy.RUNTIME)
public @interface TestA {
String name();
int id() default 0;
Class<Long> gid();
}
使用自定義注解
/**
* 這個(gè)類專門用來(lái)測(cè)試注解使用
* @author tmser
*/
@TestA(name="type",gid=Long.class) //類成員注解
public class UserAnnotation {
@TestA(name="param",id=1,gid=Long.class) //類成員注解
private Integer age;
@TestA (name="construct",id=2,gid=Long.class)//構(gòu)造方法注解
public UserAnnotation(){
}
@TestA(name="public method",id=3,gid=Long.class) //類方法注解
public void a(){
Map<String,String> m = new HashMap<String,String>(0);
}
@TestA(name="protected method",id=4,gid=Long.class) //類方法注解
protected void b(){
Map<String,String> m = new HashMap<String,String>(0);
}
@TestA(name="private method",id=5,gid=Long.class) //類方法注解
private void c(){
Map<String,String> m = new HashMap<String,String>(0);
}
public void b(Integer a){
}
}
** 最重要的桑阶,獲取注解上的內(nèi)容**:java1.5中增加注解,也同樣增加了讀取注解的方法勾邦,在java.lang.reflect包中新增了AnnotatedElement接口蚣录,JDK源碼如下:
public interface AnnotatedElement {
//判斷是否標(biāo)注了指定注解
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass);
//獲取指定注解,沒(méi)有則返回null
<T extends Annotation> T getAnnotation(Class<T> annotationClass);
//獲取所有注解眷篇,包括繼承自基類的萎河,沒(méi)有則返回長(zhǎng)度為0的數(shù)組
Annotation[] getAnnotations();
//獲取自身顯式標(biāo)明的所有注解,沒(méi)有則返回長(zhǎng)度為0的數(shù)組
Annotation[] getDeclaredAnnotations();
}
請(qǐng)看測(cè)試?yán)樱?/p>
public class ParseAnnotation {
/**
* 簡(jiǎn)單打印出UserAnnotation 類中所使用到的類注解
* 該方法只打印了 Type 類型的注解
* @throws ClassNotFoundException
*/
public static void parseTypeAnnotation() throws ClassNotFoundException {
Class clazz = Class.forName("com.tmser.annotation.UserAnnotation");
Annotation[] annotations = clazz.getAnnotations();
for (Annotation annotation : annotations) {
TestA testA = (TestA)annotation;
System.out.println("id= \""+testA.id()+"\"; name= \""+testA.name()+"\"; gid = "+testA.gid());
}
}
/**
* 簡(jiǎn)單打印出UserAnnotation 類中所使用到的方法注解
* 該方法只打印了 Method 類型的注解
* @throws ClassNotFoundException
*/
public static void parseMethodAnnotation(){
Method[] methods = UserAnnotation.class.getDeclaredMethods();
for (Method method : methods) {
/*
* 判斷方法中是否有指定注解類型的注解
*/
boolean hasAnnotation = method.isAnnotationPresent(TestA.class);
if (hasAnnotation) {
/*
* 根據(jù)注解類型返回方法的指定類型注解
*/
TestA annotation = method.getAnnotation(TestA.class);
System.out.println("method = " + method.getName()
+ " ; id = " + annotation.id() + " ; description = "
+ annotation.name() + "; gid= "+annotation.gid());
}
}
}
/**
* 簡(jiǎn)單打印出UserAnnotation 類中所使用到的方法注解
* 該方法只打印了 Method 類型的注解
* @throws ClassNotFoundException
*/
public static void parseConstructAnnotation(){
Constructor[] constructors = UserAnnotation.class.getConstructors();
for (Constructor constructor : constructors) {
/*
* 判斷構(gòu)造方法中是否有指定注解類型的注解
*/
boolean hasAnnotation = constructor.isAnnotationPresent(TestA.class);
if (hasAnnotation) {
/*
* 根據(jù)注解類型返回方法的指定類型注解
*/
TestA annotation =(TestA) constructor.getAnnotation(TestA.class);
System.out.println("constructor = " + constructor.getName()
+ " ; id = " + annotation.id() + " ; description = "
+ annotation.name() + "; gid= "+annotation.gid());
}
}
}
public static void main(String[] args) throws ClassNotFoundException {
parseTypeAnnotation();
parseMethodAnnotation();
parseConstructAnnotation();
}
}
Java動(dòng)態(tài)代理
Java動(dòng)態(tài)代理分為JDK動(dòng)態(tài)代理和CGLib動(dòng)態(tài)代理蕉饼。Java作為一門靜態(tài)語(yǔ)言虐杯,不像JavaScript、php昧港、python等具有標(biāo)準(zhǔn)的動(dòng)態(tài)特性擎椰。但Java逐漸加入了一些特性來(lái)支持動(dòng)態(tài)特性,主要包括:反射機(jī)制创肥、動(dòng)態(tài)編譯达舒、動(dòng)態(tài)字節(jié)碼操作、動(dòng)態(tài)類型轉(zhuǎn)換 等叹侄。
JDK動(dòng)態(tài)代理
基于JDK的反射(reflect)機(jī)制巩搏;在JDK中,提供了InvocationHandler這個(gè)接口趾代。源碼中解釋如下:
InvocationHandler is the interface implemented by the invocation handler of a proxy instance.
Each proxy instance has an associated invocation handler. When a method is invoked on a proxy instance, the method invocation is encoded and dispatched to the invoke method of its invocation handler.
該接口由被代理對(duì)象的handler所實(shí)現(xiàn)塔猾;當(dāng)調(diào)用代理對(duì)象的方法時(shí),該方法調(diào)用將被編碼稽坤,然后交給代理對(duì)象的invoke方法去執(zhí)行丈甸。
具體使用:
public class ProxyFactory implements InvocationHandler {
private Object delegate;
public Object bind(Object delegate){
this.delegate= delegate;
return Proxy.newProxyInstance(delegate.getClass().getClassLoader(),
delegate.getClass().getInterfaces(), this);
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//前期處理
WashApple.washAnApple();
Object result=null;
try {
//調(diào)用原類中的方法
result=method.invoke(delegate, args);
} catch (Exception e) {
// TODO: handle exceptions
}
return result;
}
}
CGLib代理方式
一個(gè)強(qiáng)大的糯俗,高性能,高質(zhì)量的Code生成類庫(kù)睦擂,它可以在運(yùn)行期擴(kuò)展Java類與實(shí)現(xiàn)Java接口得湘。底層是通過(guò)使用一個(gè)小而快的字節(jié)碼處理框架ASM,來(lái)轉(zhuǎn)換字節(jié)碼并生成新的類顿仇√哉可見(jiàn)CGLib是從字節(jié)碼層改變Java執(zhí)行的。具體使用例子如下:
public class CglibProxyFactory implements MethodInterceptor{
private Object delegate;
public Object bind(Object delegate){
this.delegate=delegate;
Enhancer enhancer= new Enhancer();
enhancer.setSuperclass(delegate.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
public Object intercept(Object object, Method method, Object[] args, MethodProxy proxy) throws Throwable {
//前期處理
WashApple.washAnApple();
Object o =proxy.invoke(this.delegate, args);
PerformanceUtil.endPerformance();
return o;
}
}
很明顯臼闻,這里的MethodInterceptor類似于JDK動(dòng)態(tài)代理中的InvocationHandler鸿吆。
AOP中的術(shù)語(yǔ)與應(yīng)用
- 切面(Aspect):切面就是一個(gè)關(guān)注點(diǎn)的模塊化,如事務(wù)管理述呐、日志管理惩淳、權(quán)限管理等;
- 連接點(diǎn)(Joinpoint):程序執(zhí)行時(shí)的某個(gè)特定的點(diǎn)乓搬,在Spring中就是一個(gè)方法的執(zhí)行思犁;
- 通知(Advice):通知就是在切面的某個(gè)連接點(diǎn)上執(zhí)行的操作,也就是事務(wù)管理进肯、日志管理等激蹲;
- 切入點(diǎn)(Pointcut):切入點(diǎn)就是描述某一類選定的連接點(diǎn),也就是用來(lái)指定某一類要織入通知的方法的描述江掩;
- 目標(biāo)對(duì)象(Target):就是被AOP動(dòng)態(tài)代理的目標(biāo)對(duì)象学辱;
上圖表示了以上術(shù)語(yǔ)之間的關(guān)系。
通知類型:
- 前置通知(Before advice):在某連接點(diǎn)之前執(zhí)行的通知环形,但這個(gè)通知*
不能阻止連接點(diǎn)之前的執(zhí)行流程(除非它拋出一個(gè)異常)策泣。 - 后置通知(After returning advice):在某連接點(diǎn)正常完成后執(zhí)行的通知:例如,一個(gè)方法沒(méi)有拋出任何異常斟赚,正常返回着降。
- 異常通知(After throwing advice):在方法拋出異常退出時(shí)執(zhí)行的通知。
- 最終通知(After (finally) advice):當(dāng)某連接點(diǎn)退出的時(shí)候執(zhí)行的通知(不論是正常返回還是異常退出)拗军。
- 環(huán)繞通知(Around Advice):包圍一個(gè)連接點(diǎn)的通知任洞,如方法調(diào)用。這是最強(qiáng)大的一種通知類型发侵。環(huán)繞通知可以在方法調(diào)用前后完成自定義的行為交掏。它也會(huì)選擇是否繼續(xù)執(zhí)行連接點(diǎn)或直接返回它自己的返回值或拋出異常來(lái)結(jié)束執(zhí)行。
Spring的AOP文檔里提到:
Spring目前僅支持使用方法調(diào)用作為連接點(diǎn)(join point)(在Spring bean上通知方法的執(zhí)行)刃鳄。雖然可以在不影響到Spring AOP核心API的情況下加入對(duì)成員變量攔截器支持扶认,但Spring并沒(méi)有實(shí)現(xiàn)成員變量攔截器多糠。如果你需要把對(duì)成員變量的訪問(wèn)和更新也作為通知的連接點(diǎn)唠倦,可以考慮其它的語(yǔ)言据过,如AspectJ与涡。
注意Spring的AOP和AspectJ不是一回事。Spring側(cè)重于提供一種AOP實(shí)現(xiàn)和Spring IoC容器之間的整合,用于幫助解決在企業(yè)級(jí)開(kāi)發(fā)中的常見(jiàn)問(wèn)題。
Spring中使用AOP可以采用Spring特有的xml配置解取,或者使用AspectJ實(shí)現(xiàn)AOP。
Spring的xml配置
//目標(biāo)類
<bean id="userServiceId" class="cn.leyue.a_aop.c_spring.UserServiceImpl"></bean>
//切面類(通知)
<bean id="myAspectId" class="cn.leyue.a_aop.c_spring.MyAspect"></bean>
<!--
創(chuàng)建代理類
* 使用工廠bean FactoryBean ,底層調(diào)用getObject()返回特殊bean
* ProxyFactoryBean 用于創(chuàng)建代理工程bean,生成特殊代理對(duì)象,
interfaces:確定接口們
通過(guò)<array>可以設(shè)置多個(gè)值
只有一個(gè)值時(shí),value=""
target 確定目標(biāo)類
interceptorNames:通知切面類的名稱,類型String[],如果設(shè)置一個(gè)值 value=""
optimize:強(qiáng)制使用cglib
<property name="optimize" value="true"></property>
* 底層機(jī)制
如果目標(biāo)類接口,采用jdk動(dòng)態(tài)代理
如果沒(méi)有接口,采用cglib 字節(jié)碼增強(qiáng)
如果生命optimize=true 無(wú)論是否有接口,都采用cglib
-->
<bean id="proxyServiceId" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="interfaces" value="cn.leyue.a_aop.c_spring.UserService"></property>
<property name="target" ref="userServiceId"></property>
<property name="interceptorNames" value="myAspectId"></property>
<property name="optimize" value="true"></property>
</bean>
目標(biāo)類
public interface UserService {
void addUser();
void updateUser();
void deleteUser();
}
public class UserServiceImpl implements UserService {
@Override
public void addUser() {
System.out.println("b_cglib------addUser");
}
@Override
public void updateUser() {
System.out.println("b_cglib------updateUser");
}
@Override
public void deleteUser() {
System.out.println("b_cglib------deleteUser");
}
}
切面類
/**
* 切面類中確定通知,需要實(shí)現(xiàn)不同接口,接口就是規(guī)范,從而確定方法名稱.
* 采用"環(huán)繞通知"MethodInterceptor
*/
public class MyAspect implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
System.out.println("c_spring_方法之前");
//手動(dòng)執(zhí)行目標(biāo)方法
Object obj = methodInvocation.proceed();
System.out.println("c_spring_方法之后");
return obj;
}
}
測(cè)試類
public class SpringTest {
@Test
public void spring() {
String path = "cn/leyue/a_aop/c_spring/beans.xml";
ClassPathXmlApplicationContext application = new ClassPathXmlApplicationContext(path);
UserService service = application.getBean("proxyServiceId", UserService.class);
service.addUser();
service.updateUser();
service.deleteUser();
}
}
AspectJ
這部分語(yǔ)法比較多返顺,建議使用查文檔即可禀苦,當(dāng)然記住最好啦,傳送門遂鹊,AspectJ使用
This is the end, that's all, Thank U.