aop學習筆記之基於@Aspect的AOP實現(一)

aop學習筆記之Pointcut Express和Advice:http://blog.csdn.net/zyb2017/article/details/79444476
本篇代碼下載地址:https://download.csdn.net/download/zyb2017/10277492

一 環境

開始解決之前先來搭建一個測試的環境,MAVEN+Spring-Boot,版本爲1.5.10,因爲要使用AOP所以在pom裏添加aop的依賴

    <!--spring aop 依賴-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>

刷新Maven後看下External Libraries中是否有我們要的包

這裏寫圖片描述

二 普通寫法

測試程序爲管理員對產品進行刪除或添加操作,操作之前要驗證管理員身份,不是admin則拋出異常

2.1 產品類

package com.march.aop.domain;

/**
 * Product class
 *
 * @author TransientBa
 * @date 2018/03/01
 */
public class Product {
    private Long id;
    private String name;

    public Long getId() { return id; }

    public String getName() { return name; }

    public void setName(String name) { this.name = name;  }

    public void setId(Long id) { this.id = id; }

}

2.2 持有校驗

package com.march.aop.security;

/**
 * CurrentUserHolder class
 *
 * @author TransientBa
 * @date 2018/03/01
 */
public class CurrentUserHolder {
    //維護多線程間局部變量
    private static final ThreadLocal<String> holder = new ThreadLocal<>();

    public static String get(){
        //holder.get() 返回局部變量的當前線程值
        return holder.get() == null ? "unknown" : holder.get();
    }

    public static void set(String user){
        holder.set(user);
    }
}

2.3 身份驗證Service

package com.march.aop.service;

import com.march.aop.security.CurrentUserHolder;
import org.springframework.stereotype.Component;

/**
 * AuthService class
 *
 * @author TransientBa
 * @date 2018/03/01
 */
@Component
public class AuthService {
    public void checkAccess(){
        String user = CurrentUserHolder.get();
        String role = "admin";
        //若當前用戶不是admin則拋出異常不允許操作
        if(!role.equals(user)){
            throw new RuntimeException("operation not allow");
        }
    }
}

2.4 產品Service

package com.march.aop.service;

import com.march.aop.domain.Product;
import com.march.aop.security.AdminOnly;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * ProductService class
 *
 * @author TransientBa
 * @date 2018/03/01
 */
@Service
public class ProductService {
    @Autowired
    AuthService authService;

    public void insert(Product product){
    //插入之前進行身份校驗
        authService.checkAccess();
        System.out.println("insert product");
    }

    public void delete(Long id){
        authService.checkAccess();
        System.out.println("delete product");
    }
}

2.5 測試啓動類

package com.march.aop;

import com.march.aop.security.CurrentUserHolder;
import com.march.aop.service.ProductService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class DemoApplicationTests {
    @Autowired
    ProductService productService;

    @Test(expected = Exception.class)
    public void annoInsertTrst() {
        CurrentUserHolder.set("TransientBa");
        productService.delete(1L);
    }

    @Test
    public void adminInsert(){
        CurrentUserHolder.set("admin");
        productService.delete(1L);
    }
}

第一個annoInsertTrst方法因爲插入的管理員叫TransientBa而不是admin,理論上會跑拋異常。我們加上expected = Exception.class,運行測試:
這裏寫圖片描述
我們看到測試是通過了的。
第二個adminInsert插入了admin,理論上不會拋異常,直接運行測試:
這裏寫圖片描述

通過

三 aop織入

以aop範式的規則織入,來替代產品Service中的調用

3.1 管理員註解

我們用註解來控制織入的位置,創建管理員註解

package com.march.aop.security;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * class
 *
 * @author TransientBa
 * @date 2018/3/1
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AdminOnly {
}

自定義註解的方法可查看我的另一片文章:
@interface 自定義註解:http://blog.csdn.net/zyb2017/article/details/78827525

3.2 @Aspect

package com.march.aop.security;

import com.march.aop.service.AuthService;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * SecurityAspect     class
 *
 * @author TransientBa
 * @date 2018/3/1
 */
@Aspect
@Component
public class SecurityAspect {
    @Autowired
    AuthService authService;

    /**切入點爲帶有AdminOnly註解的方法**/
    @Pointcut("@annotation(AdminOnly)")
    public void adminOnly(){
    }

    /**在adminOnly方法執行前  做checkAccess的校驗**/
    @Before("adminOnly()")
    public void check(){
        authService.checkAccess();
    }
}

同時通過@Component註解將SecurityAspect 類交給Spring進行託管

3.3 修改產品Service

取消原本的用戶校驗,改爲應用@AdminOnly註解來標註接入點

package com.march.aop.service;

import com.march.aop.domain.Product;
import com.march.aop.security.AdminOnly;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * ProductService class
 *
 * @author TransientBa
 * @date 2018/03/01
 */
@Service
public class ProductService {
    @Autowired
    AuthService authService;

    @AdminOnly
    public void insert(Product product){
        System.out.println("insert product");
    }
    @AdminOnly
    public void delete(Long id){
        System.out.println("delete product");
    }
}

3.4 測試

回到DemoApplicationTest,進行測試
運行結果如下
annoInsertTrst:
這裏寫圖片描述

adminInsert:
這裏寫圖片描述

四 總結

以aop的方式解決一些非功能性的需求,例如日誌、事務、異常等。可以很好的將非業務性的需求拿出來,減少代碼的侵入性,當我們修改時做到不影響原本的業務邏輯。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章