1. 程式人生 > >Spring aop 原理及各種應用場景

Spring aop 原理及各種應用場景

AOP是Aspect Oriented Programing的簡稱,面向切面程式設計。AOP適合於那些具有橫切邏輯的應用:如效能監測,訪問控制,事務管理、快取、物件池管理以及日誌記錄。AOP將這些分散在各個業務邏輯中的程式碼通過橫向切割的方式抽取到一個獨立的模組中。AOP 實現的關鍵就在於 AOP 框架自動建立的 AOP 代理,AOP 代理則可分為靜態代理和動態代理兩大類,其中靜態代理是指使用 AOP 框架提供的命令進行編譯,從而在編譯階段就可生成 AOP 代理類,因此也稱為編譯時增強;而動態代理則在執行時藉助於 JDK 動態代理、CGLIB 等在記憶體中“臨時”生成 AOP 動態代理類,因此也被稱為執行時增強。

代理物件的方法 = 增強處理 + 被代理物件的方法

Spring AOP 則採用執行時生成 AOP 代理類,因此無需使用特定編譯器進行處理。由於 Spring AOP 需要在每次執行時生成 AOP 代理,因此效能略差一些。

AOP使用場景

AOP用來封裝橫切關注點,具體可以在下面的場景中使用

Authentication 許可權

Caching 快取

Context passing 內容傳遞

Error handling 錯誤處理

Lazy loading 懶載入

Debugging 除錯

logging, tracing, profiling and monitoring 記錄跟蹤 優化 校準

Performance optimization 效能優化

Persistence 持久化

Resource pooling 資源池

Synchronization 同步

Transactions 事務

AOP相關概念

方面(Aspect):一個關注點的模組化,這個關注點實現可能另外橫切多個物件。事務管理是J2EE應用中一個很好的橫切關注點例子。方面用Spring的 Advisor或攔截器實現。

連線點(Joinpoint): 程式執行過程中明確的點,如方法的呼叫或特定的異常被丟擲

通知(Advice): 在特定的連線點,AOP框架執行的動作。各種型別的通知包括“around”、“before”和“throws”通知。通知型別將在下面討論。許多AOP框架包括Spring都是以攔截器做通知模型,維護一個“圍繞”連線點的攔截器鏈。Spring中定義了四個advice: BeforeAdvice, AfterAdvice, ThrowAdvice和DynamicIntroductionAdvice

切入點(Pointcut): 指定一個通知將被引發的一系列連線點的集合。AOP框架必須允許開發者指定切入點:例如,使用正則表示式。 Spring定義了Pointcut介面,用來組合MethodMatcher和ClassFilter,可以通過名字很清楚的理解, MethodMatcher是用來檢查目標類的方法是否可以被應用此通知,而ClassFilter是用來檢查Pointcut是否應該應用到目標類上

引入(Introduction): 新增方法或欄位到被通知的類。 Spring允許引入新的介面到任何被通知的物件。例如,你可以使用一個引入使任何物件實現 IsModified介面,來簡化快取。Spring中要使用Introduction, 可有通過DelegatingIntroductionInterceptor來實現通知,通過DefaultIntroductionAdvisor來配置Advice和代理類要實現的介面

目標物件(Target Object): 包含連線點的物件。也被稱作被通知或被代理物件。POJO

AOP代理(AOP Proxy): AOP框架建立的物件,包含通知。 在Spring中,AOP代理可以是JDK動態代理或者CGLIB代理。

織入(Weaving): 組裝方面來建立一個被通知物件。這可以在編譯時完成(例如使用AspectJ編譯器),也可以在執行時完成。Spring和其他純Java AOP框架一樣,在執行時完成織入。

日誌應用:

實現登陸和日誌管理(使用Spring AOP

1)LoginService   LogService   TestMain

2)用Spring 管理  LoginService 和 LogService 的物件

3)確定哪些連線點是切入點,在配置檔案中

4)將LogService封裝為通知

5)將通知植入到切入點

6)客戶端呼叫目標

<aop:config>

<aop:pointcut expression="execution(* cn.com.spring.service.impl.*.*(..))" id="myPointcut"/>

<!--將哪個-->

<aop:aspect id="dd" ref="logService">

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

</aop:aspect>

</aop:config>

execution(* * cn.com.spring.service.impl.*.*(..))

1)* 所有的修飾符

2)* 所有的返回型別

3)* 所有的類名

4)* 所有的方法名

5)* ..所有的引數名

1.ILoginService.java

package cn.com.spring.service;

public interface ILoginService {

public boolean login(String userName, String password);

}

2.LoginServiceImpl.java

package cn.com.spring.service.impl;

import cn.com.spring.service.ILoginService;

public class LoginServiceImpl implements ILoginService {

public boolean login(String userName, String password) {

System.out.println("login:" + userName + "," + password);

return true;

}

}

3.ILogService.java

package cn.com.spring.service;

import org.aspectj.lang.JoinPoint;

public interface ILogService {

//無參的日誌方法

public void log();

//有參的日誌方法

public void logArg(JoinPoint point);

//有參有返回值的方法

public void logArgAndReturn(JoinPoint point,Object returnObj);

}

4.LogServiceImpl.java

package cn.com.spring.service.impl;

import org.aspectj.lang.JoinPoint;

import cn.com.spring.service.ILogService;

public class LogServiceImpl implements ILogService {

@Override

public void log() {

System.out.println("*************Log*******************");

}

//有參無返回值的方法

public void logArg(JoinPoint point) {

//此方法返回的是一個數組,陣列中包括request以及ActionCofig等類物件

Object[] args = point.getArgs();

System.out.println("目標引數列表:");

if (args != null) {

for (Object obj : args) {

System.out.println(obj + ",");

}

System.out.println();

}

}

//有參並有返回值的方法

public void logArgAndReturn(JoinPoint point, Object returnObj) {

//此方法返回的是一個數組,陣列中包括request以及ActionCofig等類物件

Object[] args = point.getArgs();

System.out.println("目標引數列表:");

if (args != null) {

for (Object obj : args) {

System.out.println(obj + ",");

}

System.out.println();

System.out.println("執行結果是:" + returnObj);

}

}

}

5.applicationContext.java

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

xmlns:aop="http://www.springframework.org/schema/aop" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns:p="http://www.springframework.org/schema/p"

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

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

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

http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">

<bean id="logService" class="cn.com.spring.service.impl.LogServiceImpl"></bean>

<bean id="loginService" class="cn.com.spring.service.impl.LoginServiceImpl"></bean>

<aop:config>

<!-- 切入點 -->

<aop:pointcut

expression="execution(* cn.com.spring.service.impl.LoginServiceImpl.*(..))"

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:aspect>

</aop:config>

</beans>

6.TestMain.java

public class TestMain {

public static void testSpringAOP(){

ApplicationContext ctx = new ClassPathXmlApplicationContext("app*.xml");

ILoginService loginService = (ILoginService)ctx.getBean("loginService");

loginService.login("zhangsan", "12344");

}

public static void main(String[] args) {

testSpringAOP();

}

}

7.輸出結果:

login:zhangsan,12344

目標引數列表:

zhangsan,

12344,

執行結果是:true

解析:1.先呼叫了login()方法System.out.println("login:" + userName + "," + password);

2.再呼叫了logArgAndReturn()方法輸出了日誌,並且返回了login()方法是否成功

System.out.println("目標引數列表:");

if (args != null) {

for (Object obj : args) {

System.out.println(obj + ",");

}

System.out.println();

System.out.println("執行結果是:" + returnObj);

}

許可權控制

首先定義一個使用者:

Java程式碼  收藏程式碼

public class User { 

private String username; 

public String getUsername() { 

return username; 

public void setUsername(String username) { 

this.username = username; 

使用者有三種人:未註冊使用者,註冊使用者,與管理員

註冊使用者可以可以發表,回覆帖子

管理員除了可以發表,回覆帖子,還可以刪除帖子!

下面定義TestCommunity介面:

Java程式碼  收藏程式碼

public interface TestCommunity { 

public void answerTopic(); 

public void deleteTopic(); 

實現上面介面的TestCommunityImpl類:

Java程式碼  收藏程式碼

public class TestCommunityImpl implements TestCommunity { 

//註冊使用者與管理員擁有的功能 

public void answerTopic() { 

System.out.println("可以發表,回覆帖子"); 

//管理員擁有的功能 

public void deleteTopic() { 

System.out.println("可以刪除帖子!"); 

下一步,建立一下依賴注入的實現類TestResultImpl:

Java程式碼  收藏程式碼

public class TestResultImpl { 

private TestCommunity test; 

public void setTest(TestCommunity test) { 

this.test = test; 

}    

public void answerTopic() 

test.answerTopic(); 

public void deleteTopic() 

test.deleteTopic(); 

接下來,就是最重要的一個類,攔截器,Around處理型別的,類TestAuthorityInterceptor:

Java程式碼  收藏程式碼

import org.aopalliance.intercept.MethodInterceptor; 

import org.aopalliance.intercept.MethodInvocation; 

//建立Around處理應該實現MethodInterceptor介面 

public class TestAuthorityInterceptor implements MethodInterceptor { 

private User user; 

public User getUser() { 

return user; 

public void setUser(User user) { 

this.user = user; 

// invoke方法返回呼叫的結果 

public Object invoke(MethodInvocation invocation) throws Throwable { 

String methodName = invocation.getMethod().getName(); 

if (user.getUsername().equals("unRegistedUser")) { 

System.out.println("你的身份是未註冊使用者,沒有許可權回覆,刪除帖子!"); 

return null; 

if ((user.getUsername().equals("user")) 

&& (methodName.equals("deleteTopic"))) { 

System.out.println("你的身份是註冊使用者,沒有許可權刪除帖子"); 

return null; 

// proceed()方法對連線點的整個攔截器鏈起作用,攔截器鏈中的每個攔截器都執行該方法,並返回它的返回值 

return invocation.proceed(); 

配置檔案:

Java程式碼  收藏程式碼

<?xml version="1.0" encoding="UTF-8"?> 

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

<beans> 

<bean id="authTarget" class="org.test.lighter.TestCommunityImpl" /> 

<!-- 其中的username可以寫為admin,user,和unRegistedUser --> 

<bean id="user" class="org.test.lighter.User"> 

<property name="username" value="user" /> 

</bean> 

<!-- 配置攔截器 --> 

<bean id="TestAuthorityInterceptor" 

class="org.test.lighter.TestAuthorityInterceptor"> 

<property name="user" ref="user" /> 

</bean> 

<!-- 配置代理工廠bean --> 

<bean id="service" 

class="org.springframework.aop.framework.ProxyFactoryBean"> 

<property name="proxyInterfaces"> 

<value>org.test.lighter.TestCommunity</value> 

</property> 

<property name="target" ref="authTarget"/> 

<property name="interceptorNames"> 

<list> 

<value>TestAuthorityInterceptor</value> 

</list> 

</property> 

</bean> 

<bean id="testResult" class="org.test.lighter.TestResultImpl"> 

<property name="test" ref="service" /> 

</bean> 

</beans> 

再寫一個執行檔案BeanTest:

Java程式碼  收藏程式碼

import org.springframework.context.ApplicationContext; 

import org.springframework.context.support.FileSystemXmlApplicationContext; 

public class BeanTest { 

public static void main(String[] args) throws Exception 

ApplicationContext ctx = new FileSystemXmlApplicationContext("src/bean.xml"); 

TestResultImpl test = (TestResultImpl)ctx.getBean("testResult"); 

test.answerTopic(); 

test.deleteTopic(); 

執行結果:大家猜一下啦

Java程式碼  收藏程式碼

1、如果是管理員,打印出: 

可以發表,回覆帖子 

可以刪除帖子! 

2、如果是註冊使用者: 

可以發表,回覆帖子 

你的身份是註冊使用者,沒有許可權刪除帖子 

3、未註冊使用者: 

你的身份是未註冊使用者,沒有許可權回覆,刪除帖子! 

Spring為我們提供了,根據beanName匹配後進行自動代理的解決方法

mark from http://blog.csdn.net/sd0902/article/details/8393770

http://lighter.iteye.com/blog/42673