OOP的完美點綴—AOP之SpringAOP實現原理


OOP的完美點綴—AOP之SpringAOP實現原理

前言

OOP與AOP

OOP(Object Oriented Programming,面向對象編程),通過封裝、繼承將程序抽象為各個層次的對象,進而組合為模塊或者程序,達到了軟件工程中的重用性、靈活性、擴展性。程序的運行籠統地可以看為各層次對象之間的相互調用。
AOP(Aspect Oriented Programming,面向切面編程),將程序運行過程分解為一個個的切面,對特定的切面(某個步驟或者階段)進行提取,達到解耦各種不同邏輯代碼。
OOP是在程序分塊層面上進行考慮,而AOP則是在程序運行的切面上進行考慮。

可以將AOP理解為一種無損傷型的”切面”激光手術刀。OOP使屬性和行為被封裝為了一個嚴實、密不透風的對象。想要改變由對象保護着的方法,就可以通過該激光手術刀,既不損傷對象(即是,不破壞對象的封裝),又不添加任何冗余的代碼,實現對原對象的方法的增強處理。

不得不說,AOP實在是一種巧妙的編程思想!!!彌補了OOP中一些難以解決的問題。例如,
1. 類應該是純凈的,不應含有與本身無關的邏輯。如日志跟蹤的邏輯代碼。這樣的類就可以更好地重用,更有效地被AOP的切入更多的業務邏輯, 舉例代碼如下:


/*
* 假如需要記錄某只柯基狗的日常,
* 我們總不能讓它自己來記錄吧??
* 如 下面的注釋了的方法
* 看起來是不是非常怪異,一只狗狗自己給自己寫日記
*/
class dog{
void run(){
/*note.write("散步了");*/
}

void sleep(){
/*note.write("又睡懶覺了");*/
}
}

2. OOP為不同類別的對象引入公共方法時,往往力不從心,造成大量的分散的重復代碼,重用性真的很差,每次都要改動真的很麻煩。


class dog{
private Note note = new Note();

void run(){
note.write("散步了");
}
void sleep(){
note.write("又睡懶覺了");
}
}

本文中”特定處理”指的日志記錄、事務管理、異常處理等等之類的各種通用的業務邏輯

AOP實現原理

主要分為兩大類:
是采用動態代理,對被代理對象和特定處理進行修飾和封裝,得到代理對象,從使得被代理對象中不需切入任何的代碼,如圖:
簡單的代理:實現不入侵原始對象而切入各種通用的業務邏輯(eg: 參數驗證、日志記錄方法等)


interface Interface{
void doSomething();
}

/*原始對象*/
class RealObject implements Interface{
@Override
public void doSomething() {
System.out.println("原始對象的行為");
}
}

/*代理*/
class SimplProxy implements Interface {
private Interface proxied;

public SimplProxy(Interface proxied){
this.proxied = proxied;
}

public void doSomething(){
System.out.println("處理一些通用的業務邏輯, 如參數校驗等等");
proxied.doSomething();
}
}

/*調用者*/
class Caller{
public static void call(Interface iface){
iface.doSomething();
}

public static void main(String[] args){
call(new SimplProxy(new RealObject()));
}
}
/*輸出:*/
1.處理一些通用的業務邏輯, 如參數校驗等等
2.原始對象的行為
就這樣,一些通用的業務邏輯被代理簡單地切入到了原始對象之前執行

是采用靜態織入,如AspectJ,使用其特定的語法創建切面,在編譯期間將切面織入代碼中。又如,通過AspectJ編譯出來的class文件與普通編譯出來的有很大區別,這塊沒有了解,不再贅述。

AOP使用場景

權限控制、異常處理、緩存、事務管理、日志記錄、數據校驗等等

AOP基本概念

  • 切面(Aspect): 程序運行過程中的某個的步驟或者階段
  • 連接點(Joinpoint): 程序運行過程中可執行特定處理(增強處理)的點, 如異常處理。而在SpringAOP中,方法調用是連接點。
  • Advice(通知、處理、增強處理): 在符合的連接點進行的特定處理 (增強處理)
  • 切入點(Pointcut): 可切入進行增強處理的連接點。AOP核心之一就是如何用表達式來定義符合的切入點。在Spring中,默認使用AspectJ的切入點語法。
    由於Spring AOP只支持以Spring Bean的方法調用來作為連接點, 所以在這里切入點的定義包括:
    • 切入點表達式, 來限制該能作用的范圍大小,即是,能匹配哪些bean的方法
    • 命名切入點

@Pointcut("execution(* com.xxx.xxx.service.*.* (..)) ") /*切入點*/
public void pointCutExpress(){   /*命名切入點*/
}
/*常見的 切入點表達式語法 如下:*/
execution(返回值類型 方法所屬類.方法名 方法形參列表 拋出的異常聲明)
例如:
上面第一個 * 指匹配所有返回值類型
第二個 * 指service包下的所有類
第三個 * 指各個類中的所有方法
(..)中的 .. 指零個或者多個(任意數量)的參數
  • 目標對象: 被進行增強處理的對象
  • AOP代理: 是一個重新封裝了(增強處理 + 被代理對象的方法 )方法的代理對象。
  • 織入(Weaving): 就如它名字表述的一樣,增強處理切入目標對象以后,並獲得代理對象(AOP代理)的這個過程,就是織入。按其作用的時間分類為,編譯時織入與運行時織入。

SpringAOP的使用方法

基於注解的零配置方式:
一、啟動@AspectJ注解支持,旭在相應的容器內,加入如下片段:


<beans xmlns:aop="http://www.springframework.org/schema/aop" <!-- 必須有相應的XML Schema 配置 -->
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd

<!-- 必須相應的容器內,啟動@AspectJ支持,如對Springmvc的Contrller織入, 則應在Springmvc.xml中配置 -->
<aop:aspectj-autoproxy />
<!-- 掃描相應的包 -->

二、定義切面bean


@Aspect
@Compement
public class Advice{

/*定義切入點, 業務處理邏輯等等其他內容*/

}

三、定義@Before、@After、@Around等增強處理

    
    
/*定義切入點表達式*/
/*配置匹配service包下所有的類、所有的方法*/
@Pointcut("execution(* com.xxx.xxx.service.*.*(..))")
public void pointCutExpress(){
}

四、定義處理方法


@After("pointCutExpress()")
public void closeResource(){
/*After注解,更適合於釋放資源*/
}

通過注解和動態代理實現簡單AOP

一、切入點注解


@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyPointCut {
/*模擬簡單的注解定義切入點*/
public String expression();

}

二、原始對象及接口



interface SomeMethods{
void method1();
void method2(String args);
}

public class Implementation implements SomeMethods{

@Override
public void method1() {
System.out.println("原始對象的方法1");
}

@Override
public void method2(String args) {
System.out.println("原始對象的方法2及參數:" + args);
}
}

三、動態代理工廠


class MyAspect{

@MyPointCut(expression = "com.auhnayuil.comm.Implementation.*")
public void Logger(){
System.out.println(">>>>>>>>>>>>>>>>>正在記載日志中<<<<<<<<<<<<<<<<<<<<<<<");
}

}
class SimpleProxyFactory{
/*簡單代理工廠*/

public static Object getProxyObject(final Object realObject, final Object aspectObject){/*代理對象 切面定義類*/
final Class<?> realObjectClass = realObject.getClass();
final Class<?> aspectObjectClass = aspectObject.getClass();
return Proxy.newProxyInstance(
realObjectClass.getClassLoader(),
realObjectClass.getInterfaces(),
new InvocationHandler(){
/*模擬簡單的@Before日志注解*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
/*加載切入點信息. 這里的方法Logger被硬編碼了, 后期可以根據注解來解決*/
Method pointCutMethod = aspectObjectClass.getMethod("Logger", new Class[] {});
MyPointCut myPointCut = pointCutMethod.getAnnotation(MyPointCut.class);
/*判斷切入點, 並執行其方法*/
String expression = myPointCut.expression();
String[] express = expression.split("\\.");
int exprLength = express.length;
if("*".equals(express[exprLength - 1])){
/*這里只演示一種情況*/
pointCutMethod.invoke(aspectObject, new Class[] {});
}
/*執行原始對象的方法*/
return method.invoke(realObject, args);
}
}
);
}

}

public class ProxyDemo {

public static void main(String[] args){

SomeMethods someMethods = new Implementation();
SomeMethods proxyObject = (SomeMethods) SimpleProxyFactory.getProxyObject(someMethods, new MyAspect());
proxyObject.method1();
proxyObject.method2("auhnayuiL");

}

}

四、輸出結果


>>>>>>>>>>>>>>>>>正在記載日志中<<<<<<<<<<<<<<<<<<<<<<<
原始對象的方法1
>>>>>>>>>>>>>>>>>正在記載日志中<<<<<<<<<<<<<<<<<<<<<<<
原始對象的方法2及參數:auhnayuiL


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM