一. AOP與@AspectJ
AOP 是 Aspect Oriented Programming 的縮寫,意思是面向方面的編程。我們在系統開發中可以提取出很多共性的東西作為一個 Aspect,可以理解為在系統中,我們需要很多次重復實現的功能。比如計算某個方法運行了多少毫秒,判斷用戶是不是具有訪問權限,用戶是否已登錄,數據的事務處理,日志記錄等等。
AOP的術語
- 連接點(Joinpoint)
程序執行的某個特殊位置:比如類開始初始化前,類初始化后,某個方法調用前,調用后等。 連接點 可 以 理解為AOP向目標類織入代碼的地方。
- 切點(Pointcut)
每一個類都有許多的連接點,所以AOP通過切點來定位特定的連接點。可以通過數據庫查詢的概念來理解切點和連接點的關系:連接點相當於數據庫中的記錄,而切點就是查詢條件。切點和連接點不是一對一的關系,一個切點可以匹配多個連接點。
- 增強(Advice)
增強就是織入到目標類連接點上的一段代碼。它既包含了添加到目標連接點上的一段執行邏輯,也包含了用於定位連接點的方位信息。
AspectJ是語言級的AOP實現,它擴展了AOP的語法,能夠在編輯提供橫切代碼的織入。@AspectJ是AspecJ1.5新增的功能,它通過JDK5.0的注解技術,允許開發者在POJO中定義切面。
二. 自定義注解
因為@AspectJ是基於JDK5.0的注解技術實現,所以我們有必要了解一下注解相關的知識,並學習如何自定義一個注解。
注解(Annotation)是代碼的附屬信息,它可以用來修飾類,屬性,方法,同時它遵循一個基本原則,注解不能直接干擾程序代碼的運行,無論是增加或者刪除注解,代碼都能正常運行。
(1)一個簡單的注解類
Java規定使用@interface修飾定義的注解類。一個注解類可以擁有多個成員,成員聲明和接口方法聲明類似。
1: package com.wbl.aop;
2:
3: import java.lang.annotation.ElementType;
4: import java.lang.annotation.Retention;
5: import java.lang.annotation.RetentionPolicy;
6: import java.lang.annotation.Target;
7:
8: /**
9: * Created by Lulala on 2015/7/15.
10: */
11: @Retention(RetentionPolicy.RUNTIME) //聲明注解的保留期限
12: @Target(ElementType.METHOD) //聲明可以使用該注解的目標類型
13: public @interface UserAccessAnnotation { //定義注解
14: ACCESS value() default ACCESS.LOOK; //聲明注解成員
15: }
三. @AspectJ的使用
Spring采用AspectJ提供的@AspectJ注解類庫及相應的解析庫,所以需要在項目中導入aspectjweaver.jar類包。
(1)定義一個切面
1: import org.aspectj.lang.annotation.Aspect;
2: import org.aspectj.lang.annotation.Before;
3: @Aspect //通過注解將AspectTest標識為一個切面
4: public class AspectTest{
5: @Before("execution(* set(..))") //定義切點和增強類型
6: public void before(){ //增強的橫切邏輯
7: System.out.println("Aspect before");
8: }
9: }
首先,在AspectTest類的定義處,標注了一個@Aspect的注解,表示這個類是一個切面,其次在before方法定義處,標注了@Before注解,並為該注解提供了成員值"execution(* set(..))"。@Before注解表示該增強是前置增強,成員值是一個切點表達式,表示在目標類的set方法上織入增強。
(2)通過配置使用@AspectJ切面
1: <beans xmlns="http://www.springframework.org/schema/beans"
2: xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3: xmlns:p="http://www.springframework.org/schema/p"
4: xmlns:context="http://www.springframework.org/schema/context"
5: xmlns:aop="http://www.springframework.org/schema/aop"
6: xsi:schemaLocation="http://www.springframework.org/schema/beans
7: http://www.springframework.org/schema/beans/spring-beans.xsd
8: http://www.springframework.org/schema/context
9: http://www.springframework.org/schema/context/spring-context-3.0.xsd
10: http://www.springframework.org/schema/aop
11: http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
12:
13: <aop:aspectj-autoproxy/>
14: <bean class="com.wbl.AspectTest"/>
首先,在配置文件中引入aop的命名空間,然后通過aop命名空間的<aop:aspectj-autoproxy/>自動為Spring容器中那些匹配@AspectJ切面的Bean創建代理,完成自動代理的創建工作。
<aop:aspectj-autoproxy/>有一個proxy-target-class屬性,默認為false,表示使用JDK的動態代理織入增強,當配置為<aop:aspectj-autoproxy proxy-target-class=”true”/>時,表示使用CGLib的動態代理織入增強。
四. 利用基於@AspectJ的AOP實現權限控制
(1) 定義一個枚舉類
1: public enum ACCESS {
2: LOOK,EXIT,DOWNLOAD,ADMIN
3: }
該枚舉表示用戶擁有的權限,權限由低到高。
(2)定義一個注解
1: @Retention(RetentionPolicy.RUNTIME) //聲明注解的保留期限
2: @Target(ElementType.METHOD) //聲明可以使用該注解的目標類型
3: public @interface UserAccessAnnotation { //定義注解
4: ACCESS value() default ACCESS.LOOK; //聲明注解成員
5: }
定義好這個注解之后,可以把注解放在需要進行權限控制的方法前
1: public class DataOperate extends ActioinSupport{
2:
3: @UserAccessAnnotation(ACCESS.ADMIN)
4: public String updateData(String data){
5: System.out.println("Updata Data");
6: }
7: }
在這里我們使用 UserAccessAnnotation 來表示需要在updateData 方法執行之前判斷用戶的權限是否為管理員權限。
(3) 定義切點Pointcut
1: public class SystemArchitecture {
2: /**
3: * A Join Point is defined in the action layer where the method needs
4: * a permission check.
5: */
6: @Pointcut("@annotation(com.wbl.aop.UserAccessAnnotation)")
7: public void userAccess(){}
8: }
PointCut 即切入點,就是定義方法執行的點,before表示在方法執行前、after 表示方法執行后或者 around表示方法執行前后。 一般情況下,我們把 PointCut 全部集中定義在 SystemArchitecture 類中,以方便修改和管理。
(4) 定義一個切面
1: @Aspect
2: public class PermissionAspect {
3: @Before(value = "com.wbl.aop.SystemArchitecture.userAccess()&&" +
4: "@annotation(userAccessAnnotation)",argNames="userAccessAnnotation")
5:
6: public void checkPermission(UserAccessAnnotation userAccessAnnotation) throws Exception{
7: User user = (User)ActionContext.getContext().getSession().get("user");
8: if(user != null){
9: if(user.getBehaviorLevel() < userAccessAnnotation.value().ordinal() + 1){
10: throw new NoPermissionException("NO_ACCESS");
11: }
12: }
13: }
14: }
argNames="userAccessAnnotation" 的意思是把 Annotation 當做參數傳遞進來,並判斷用戶的權限是否足夠。如果用戶的權限不足以訪問該方法,就要拋出 NoPermissionException,通知系統該用戶沒有權限。其中NoPermissionException是自定義的異常。
1: public class NoPermissionException extends Exception {
2: public NoPermissionException(String msg){
3: super(msg);
4: }
5: }
(5) 在applicationContext.xml中配置切面
1: <aop:aspectj-autoproxy/>
2: <bean class="com.wbl.aop.PermissionAspect"/>
(6) 在Struts中配置全局異常
1: <global-results>
2: <result name="error">/WEB-INF/jsp/error.jsp</result>
3: </global-results>
4: <global-exception-mappings>
5: <exception-mapping exception="com.wbl.exceptions.NoPermissionException"
6: result="loginerror"/>
7: </global-exception-mappings>