天天看點

淺析 Spring AOP

 前兩天,在給新入職的同僚做技術介紹時,講到spring的AOP。使我又一次認識到,對于AOP,特别是spring AOP的了解,雖然大家都能說上來幾句,但是許多人認識并不太全面,甚至可以說是一知半解----即使是對于那些已經有過幾年開發經驗的工程師也是如此。是以,回來之後,我幹脆對這塊東西做了個膚淺的小結,以便再有類似任務時,直接拿來給大家借鑒。

AOP(Aspect-Oriented Programming)其實是OOP(Object-Oriented Programing)思想的補充和完善。我們知道,OOP引進"抽象"、"封裝"、"繼承"、"多态"等概念,對萬事萬物進行抽象和封裝,來建立一種對象的層次結構,它強調了一種完整事物的自上而下的關系。但是具體細粒度到每個事物内部的情況,OOP就顯得無能為力了。比如日志功能。日志代碼往往水準地散布在所有對象層次當中,卻與它所散布到的對象的核心功能毫無關系。對于其他很多類似功能,如事務管理、權限控制等也是如此。這導緻了大量代碼的重複,而不利于各個子產品的重用。

   而AOP技術則恰恰相反,它利用一種稱為"橫切"的技術,能夠剖解開封裝的對象内部,并将那些影響了多個類并且與具體業務無關的公共行為 封裝成一個獨立的子產品(稱為切面)。更重要的是,它又能以巧奪天功的妙手将這些剖開的切面複原,不留痕迹的融入核心業務邏輯中。這樣,對于日後橫切功能的編輯和重用都能夠帶來極大的友善。

AOP技術的具體實作,無非也就是通過動态代理技術或者是在程式編譯期間進行靜态的"織入"方式。下面是這方面技術的幾個基本術語:

1、join point(連接配接點):是程式執行中的一個精确執行點,例如類中的一個方法。它是一個抽象的概念,在實作AOP時,并不需要去定義一個join point。

2、point cut(切入點):本質上是一個捕獲連接配接點的結構。在AOP中,可以定義一個point cut,來捕獲相關方法的調用。

3、advice(通知):是point cut的執行代碼,是執行“方面”的具體邏輯。

4、aspect(方面):point cut和advice結合起來就是aspect,它類似于OOP中定義的一個類,但它代表的更多是對象間橫向的關系。

5、introduce(引入):為對象引入附加的方法或屬性,進而達到修改對象結構的目的。有的AOP工具又将其稱為mixin。

所有AOP技術基本上都是基于以上這些概念實作的。

  太抽象了,還是趕快上例子吧。下面,我寫了一個用spring AOP實作的記錄方法調用的日志功能的應用執行個體:目的是記錄 系統登入功能 的執行情況,技術架構簡單采用Spring+Struts。

1.登入頁面:

1 2 3 4 5

<

form

action

=

"login.do"

method

=

"post"

>

username:<

input

type

=

"text"

name

=

"username"

/><

br

>

password:<

input

type

=

"password"

name

=

"password"

><

br

>

<

input

type

=

"submit"

value

=

"login"

>

</

form

>

2.表單類:

1 2 3 4 5 6 7 8 9 10 11 12

public

class

LoginActionForm 

extends

ActionForm {

private

String username;

private

String password;

//setter,getter方法

}

3.Action類:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24

public

class

LoginAction 

extends

Action {

private

ILoginService loginService;

@Override

public

ActionForward execute(ActionMapping mapping, ActionForm form,

HttpServletRequest request, HttpServletResponse response)

throws Exception {

LoginActionForm laf = (LoginActionForm) form;

String

username = laf.getUsername();

String

password = laf.getPassword();

boolean ret = loginService.login(username, password);

if

(ret) {

return

mapping.findForward(

"success"

);

}

return

mapping.findForward(

"fail"

);

}

//loginService的setter,getter方法

}

4.service層接口:

1 2 3 4 5 6 7 8

public

interface

ILoginService {

public

boolean login(

String

userName, 

String

password);

}

public

interface

ILogService {

public

void

log();

public

void

logArg(JoinPoint point);

public

void

logArgAndReturn(JoinPoint point, 

Object

returnObj);

}

5.service層實作類:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23

public

class

LoginServiceImpl 

implements

ILoginService {

public

boolean

login(String userName, String password) {

StringBuffer sb=

new

StringBuffer();

sb.append(

"Target:"

).append(

"login:"

).append(userName).append(

","

).append(password);

System.out.println(sb.toString());

return

true

;

}

}

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85

public

class

LogServiceImpl 

implements

ILogService {

public

void

log() {

System.out.println(

new

SimpleDateFormat(

"yyyy-MM-dd HH:mm:ss"

).format(

new

Date())+

" *******Log*********"

);

}

public

void

logArg(JoinPoint point) {

StringBuffer sb=

new

StringBuffer();

//擷取連接配接點所在的目标對象

Object obj=point.getTarget();

//擷取連接配接點的方法簽名對象

String method=point.getSignature().getName();

//擷取連接配接點方法運作時的入參清單

Object[] args = point.getArgs();

sb.append(

new

SimpleDateFormat(

"yyyy-MM-dd HH:mm:ss"

).format(

new

Date())).append(

" "

);

sb.append(obj.toString().substring(

,obj.toString().indexOf(

'@'

)));

sb.append(

"."

).append(method).append(

" "

);

sb.append(

"Args:["

);

if

(args != 

null

) {

for

(

int

i=

;i<args.length;i++){

Object o=args[i];

sb.append(o);

if

(i<args.length-

1

){

sb.append(

","

);

}

}

}

sb.append(

"]"

);

System.out.println(sb.toString());

}

public

void

logArgAndReturn(JoinPoint point, Object returnObj) {

StringBuffer sb=

new

StringBuffer();

//擷取連接配接點所在的目标對象

Object obj=point.getTarget();

//擷取連接配接點的方法簽名對象

String method=point.getSignature().getName();

//擷取連接配接點方法運作時的入參清單

Object[] args = point.getArgs();

sb.append(

new

SimpleDateFormat(

"yyyy-MM-dd HH:mm:ss"

).format(

new

Date())).append(

" "

);

sb.append(obj.toString().substring(

,obj.toString().indexOf(

'@'

)));

sb.append(

"."

).append(method).append(

" "

);

sb.append(

"Args:["

);

if

(args != 

null

) {

for

(

int

i=

;i<args.length;i++){

Object o=args[i];

sb.append(o);

if

(i<args.length-

1

){

sb.append(

","

);

}

}

}

sb.append(

"]"

).append(

" "

);

sb.append(

"Ret:["

).append(returnObj).append(

"]"

);

System.out.println(sb.toString());

}

}

6.web.xml:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30

<!-- spring config -->

<

context-param

>

<

param-name

>contextConfigLocation</

param-name

>

<

param-value

>classpath:config/spring/app*.xml</

param-value

>

</

context-param

>

<

listener

>

<

listener-class

>org.springframework.web.context.ContextLoaderListener</

listener-class

>

</

listener

>

<!-- struts config -->

<

servlet

>

<

servlet-name

>action</

servlet-name

>

<

servlet-class

>org.apache.struts.action.ActionServlet</

servlet-class

>

<

init-param

>

<

param-name

>config</

param-name

>

<

param-value

>/config/struts/struts-config.xml</

param-value

>

</

init-param

>

<

init-param

>

<

param-name

>debug</

param-name

>

<

param-value

>2</

param-value

>

</

init-param

>

<

init-param

>

<

param-name

>detail</

param-name

>

<

param-value

>2</

param-value

>

</

init-param

>

<

load-on-startup

>2</

load-on-startup

>

</

servlet

>

<

servlet-mapping

>

<

servlet-name

>action</

servlet-name

>

<

url-pattern

>*.do</

url-pattern

>

</

servlet-mapping

>

7.spring配置資訊:

  applicationContext.xml:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29

<?

xml

version

=

"1.0"

encoding

=

"UTF-8"

?>

<

beans

xmlns

=

"http://www.springframework.org/schema/beans"

xmlns:xsi

=

"http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-2.0.xsd

http://www.springframework.org/schema/aop

http://www.springframework.org/schema/aop/spring-aop-2.0.xsd

http://www.springframework.org/schema/tx

http://www.springframework.org/schema/tx/spring-tx-2.0.xsd"

xmlns:aop

=

"http://www.springframework.org/schema/aop"

xmlns:tx

=

"http://www.springframework.org/schema/tx"

>

<

bean

id

=

"logService"

class

=

"com.bruceyang.login.service.impl.LogServiceImpl"

></

bean

>

<

bean

id

=

"loginService"

class

=

"com.bruceyang.login.service.impl.LoginServiceImpl"

></

bean

>

<

aop:config

>

<!-- 切入點 -->

<

aop:pointcut

expression

=

"execution(* com.bruceyang.login.service.impl.Login*.*(..))"

id

=

"myPointcut"

/>

<!-- 切面: 将哪個對象中的哪個方法,織入到哪個切入點 -->

<

aop:aspect

id

=

"dd"

ref

=

"logService"

>

<!-- 前置通知

<aop:before method="log" pointcut-ref="myPointcut" />

<aop:after method="logArg" pointcut-ref="myPointcut"/>

<aop:after-returning method="logArgAndReturn" returning="returnObj" pointcut-ref="myPointcut"/>

-->

<

aop:before

method

=

"log"

pointcut-ref

=

"myPointcut"

/>

<

aop:after

method

=

"logArg"

pointcut-ref

=

"myPointcut"

/>

<

aop:after-returning

method

=

"logArgAndReturn"

returning

=

"returnObj"

pointcut-ref

=

"myPointcut"

/>

</

aop:aspect

>

</

aop:config

>

</

beans

>

7.Struts配置資訊:

  struts-config.xml:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28

<?

xml

version

=

"1.0"

encoding

=

"UTF-8"

?>

<!DOCTYPE struts-config PUBLIC

"-//Apache Software Foundation//DTD Struts Configuration 1.2//EN"

"http://jakarta.apache.org/struts/dtds/struts-config_1_2.dtd">

<

struts-config

>

<

form-beans

>

<

form-bean

name

=

"loginActionForm"

type

=

"com.bruceyang.login.web.form.LoginActionForm"

/>

</

form-beans

>

<

action-mappings

>

<

action

path

=

"/tologin"

forward

=

"/login.jsp"

/>

<

action

path

=

"/login"

type

=

"org.springframework.web.struts.DelegatingActionProxy"

name

=

"loginActionForm"

scope

=

"request"

input

=

"/login.jsp"

>

<

forward

name

=

"success"

path

=

"/success.jsp"

/>

<

forward

name

=

"fail"

path

=

"/login.jsp"

/>

</

action

>

</

action-mappings

>

<

plug-in

className

=

"org.springframework.web.struts.ContextLoaderPlugIn"

>

<

set-property

property

=

"contextConfigLocation"

value

=

"/WEB-INF/classes/config/struts/action-servlet.xml"

/>

</

plug-in

>

</

struts-config

>

action-servlet.xml:

1 2 3 4 5

<?

xml

version

=

"1.0"

encoding

=

"UTF-8"

?>

<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">

<

beans

>

<

import

resource

=

"login-action.xml"

/>

</

beans

>

login-action.xml:

1 2 3 4 5 6 7 8

<?

xml

version

=

"1.0"

encoding

=

"UTF-8"

?>

<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">

<

beans

>

<

bean

name

=

"/login"

class

=

"com.bruceyang.login.web.action.LoginAction"

>

<

property

name

=

"loginService"

ref

=

"loginService"

/>

</

bean

>

</

beans

>

  其實,spring為我們提供的事務管理等功能也是基于這個邏輯來實作的。并且我們還可以用這種方式實作更加複雜多樣的AOP程式設計。以達到把所有所謂的橫切關注點分離出來,一勞永逸的加以實作,以後集中精力解決核心關注點的實作。

本文出自 “夜狼” 部落格,請務必保留此出處http://yangfei520.blog.51cto.com/1041581/1273069