今天看了一下黑馬程序員的視頻,上面講到一個使用spring AOP + 自定義註解的方式來實現權限控制的一個小例子,個人覺得還是可以借鑑,整理出來與大家分享。
需求:service層有一些方法,這些方法需要不同的權限才能訪問。
實現方案:自定義一個PrivilegeInfo的註解,使用這個註解爲service層中的方法進行權限配置,在aop中根據PrivilegeInfo註解的值,判斷用戶是否擁有訪問目標方法的權限,有則訪問目標方法,沒有則給出提示。
關鍵技術:自定義註解及註解解析,spring aop
最終實現後的目錄結構:
具體步驟:
下面我們來具體實現這個需求。
首先來實現這個自定義註解,爲了簡單起見,我們演示的這個註解,只是給了一個權限名的屬性。
package privilege.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 權限註解
* @author Minhellic
*
*/
@Target(ElementType.METHOD)//這個註解是應用在方法上
@Retention(RetentionPolicy.RUNTIME)
public @interface PrivilegeInfo {
/**
* 權限的名稱
* @return
*/
String value() default "";
}
爲這個自定義的註解,寫一個解析器,主要是用於返回目標方法上的註解PrivilegeInfo設置的value值
package privilege.annotation;
import java.lang.reflect.Method;
/**
* 權限註解解析器
* 這個解析器的主要功能,是解析目標方法上如果有PrivilegeInfo註解,那麼解析出這個註解中的value值(權限的值)
* @author Minhellic
*
*/
public class PrivilegeAnnotationParse {
/**
* 解析註解
* @param targetClass 目標類的class形式
* @param methodName 在客戶端調用哪個方法,methodName就代表哪個方法
* @return
* @throws Exception
*/
public static String parse(Class targetClass, String methodName) throws Exception {
String methodAccess = "";
/*
* 爲簡單起見,這裏考慮該方法沒有參數
*/
Method method = targetClass.getMethod(methodName);
//判斷方法上是否有Privilege註解
if (method.isAnnotationPresent(PrivilegeInfo.class)) {
//得到方法上的註解
PrivilegeInfo privilegeInfo = method.getAnnotation(PrivilegeInfo.class);
methodAccess = privilegeInfo.value();
}
return methodAccess;
}
}
自定義的註解和解析器有了,我們把對應的Service層寫出來,並在需要使用這個註解配置權限的方法上,添加上這個註解。我們知道Service層是由接口和實現類組成,這是規範,雖然對我們這次的演示沒有什麼作用,但我們還是遵守一下:
接口源碼:
package privilege.service;
/**
* 用戶業務接口
* @author Minhellic
*
*/
public interface FirmService {
/**
* 在需要權限的目標方法上,使用PrivilegeInfo註解,配置權限爲save
*/
public void save();
/**
* 在需要權限的目標方法上,使用PrivilegeInfo註解,配置權限爲update
*/
public void update();
/**
* 不需要權限的目標方法上,則不添加PrivilegeInfo註解
* 在切面中,默認用戶擁有權限
*/
public void get();
}
實現類源碼:
package privilege.service.impl;
import privilege.annotation.PrivilegeInfo;
import privilege.service.FirmService;
/**
* 用戶業務實現
* @author Minhellic
*
*/
public class FirmServiceImpl implements FirmService {
/**
* 在需要權限的目標方法上,使用PrivilegeInfo註解,配置權限
*/
@Override
@PrivilegeInfo("save")
public void save() {
System.out.println("FirmServiceImpl.save()");
}
/**
* 在需要權限的目標方法上,使用PrivilegeInfo註解,配置權限
*/
@Override
@PrivilegeInfo("update")
public void update() {
System.out.println("FirmServiceImpl.update()");
}
/**
* 不需要權限的目標方法上,則不添加PrivilegeInfo註解
* 在切面中,默認用戶擁有權限
*/
@Override
public void get() {
System.out.println("FirmServiceImpl.get()");
}
}
爲了更好地管理權限,我們專門建立一個權限類,當然,爲了簡單,這裏還是隻封裝了權限的名稱
package privilege.userprivilege;
/**
* 封裝用戶權限
* 爲簡單,只封裝了權限的名稱
* @author Minhellic
*
*/
public class FirmPrivilege {
/**
* 用戶權限的名稱
*/
private String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public FirmPrivilege(String value) {
this.value = value;
}
public FirmPrivilege() {
}
}
現在Service層和註解都已經有了,就需要寫切面的代碼了。
在切面中,我們使用環繞通知,在調用目標方法之前,我們先用目標方法上的PrivilegeInfo註解配置的權限,與用戶擁有的權限進行匹配,如果匹配成功,則認爲用戶擁有這個目標方法的權限,則調用目標方法,否則,給出提示信息,不調用目標方法。
這裏之所以不使用前置通知的方式來匹配權限,就是因爲前置通知雖然也可以檢查,但是無論檢查是否通過,目標方法都會被調用,起不到根據權限來控制目標方法調用的目的。
package privilege.aspect;
import java.util.List;
import org.aspectj.lang.ProceedingJoinPoint;
import privilege.annotation.PrivilegeAnnotationParse;
import privilege.userprivilege.FirmPrivilege;
/**
* 權限檢查切面
* 根據用戶原有的權限,與目標方法的權限配置進行匹配,
* 如果目標方法需要的權限在用戶原有的權限以內,則調用目標方法
* 如果不匹配,則不調用目標方法
* @author Minhellic
*
*/
public class PrivilegeAspect {
/**
* 用戶本身的權限
*/
private List<FirmPrivilege> privileges;
public List<FirmPrivilege> getPrivileges() {
return privileges;
}
public void setPrivileges(List<FirmPrivilege> privileges) {
this.privileges = privileges;
}
/**
* aop中的環繞通知
* 在這個方法中檢查用戶的權限和目標方法的需要的權限是否匹配
* 如果匹配則調用目標方法,不匹配則不調用
* @param joinPoint 連接點
* @throws Throwable
*/
public void isAccessMethod(ProceedingJoinPoint joinPoint) throws Throwable {
/**
* 1.獲取訪問目標方法應該具備的權限
* 爲解析目標方法的PrivilegeInfo註解,根據我們定義的解析器,需要得到:目標類的class形式 方法的名稱
*/
Class targetClass = joinPoint.getTarget().getClass();
String methodName = joinPoint.getSignature().getName();
//得到該方法的訪問權限
String methodAccess = PrivilegeAnnotationParse.parse(targetClass, methodName);
/*
* 2.遍歷用戶的權限,看是否擁有目標方法對應的權限
*/
boolean isAccessed = false;
for (FirmPrivilege firmPrivilege : privileges) {
/*
* 如果目標方法沒有使用PrivilegeInfo註解,則解析出來的權限字符串就爲空字符串
* 則默認用戶擁有這個權限
*/
if ("".equals(methodAccess)) {
isAccessed = true;
break;
}
/*
* 用戶原有權限列表中有的權限與目標方法上PrivilegeInfo註解配置的權限進行匹配
*/
if (firmPrivilege.getValue() != null &&
firmPrivilege.getValue().equalsIgnoreCase(methodAccess)) {
isAccessed = true;
break;
}
}
/*
* 3.如果用戶擁有權限,則調用目標方法 ,如果沒有,則不調用目標方法,只給出提示
*/
if (isAccessed) {
joinPoint.proceed();//調用目標方法
} else {
System.out.println("你沒有權限");
}
}
}
最後,配置好spring的配置文件,要使用spring aop,配置文件中,必須包含有aop的命名空間,並引入相應的xsd
配置文件的源碼爲:
<?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"
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="firmService" class="privilege.service.impl.FirmServiceImpl"></bean>
<bean id="privilegeAspect" class="privilege.aspect.PrivilegeAspect"></bean>
<!-- 配置切面 -->
<aop:config>
<!--
切入點表達式,確認目標類
privilege.service.impl包中的所有類中的所有方法
-->
<aop:pointcut expression="execution(* privilege.service.impl.*.*(..))" id="perform"/>
<!-- ref指向的對象就是切面 -->
<aop:aspect ref="privilegeAspect">
<!-- 環繞通知 -->
<aop:around method="isAccessMethod" pointcut-ref="perform"/>
</aop:aspect>
</aop:config>
</beans>
所有的工作都已做好,我們來測試一下:
package privilege.test;
import java.util.ArrayList;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import privilege.aspect.PrivilegeAspect;
import privilege.service.FirmService;
import privilege.userprivilege.FirmPrivilege;
/**
* aop+註解權限控制測試類
*
* @author Minhellic
*
*/
public class PrivilegeTest {
/**
* 客戶端直接調用這個Service的方法,而不需要關心權限問題
*/
private FirmService firmService;
/**
* 在初始化方法中,初始化firmService
* 同時爲用戶賦上原始權限,這個在項目中,會使用別的方式實現,這裏只是模擬,就不搞那麼複雜了
*/
@Before
public void init() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
firmService = (FirmService) context.getBean("firmService");
/*
* 給用戶添加默認權限
*/
PrivilegeAspect privilegeAspect = (PrivilegeAspect) context.getBean("privilegeAspect");
List<FirmPrivilege> privileges = new ArrayList<FirmPrivilege>();
//privileges.add(new FirmPrivilege("save"));
privileges.add(new FirmPrivilege("update"));
privilegeAspect.setPrivileges(privileges);
}
/**
* 客戶端直接調用Service中的方法,而不需要關心權限問題,會有切面去做
*/
@Test
public void test() {
firmService.save();
firmService.update();
firmService.get();
}
}
運行test方法,根據控制檯的輸出結果,就可以看到權限控制是起到了作用,因爲用戶初始權限中的save權限被註釋掉,則用戶不會擁有save權限,調用save方法時,提示沒有權限。
從上面的測試方法可以看出,使用了aop之後,我們只需要關心主要業務,而不需要再分心去管理權限問題。
這篇博客,雖是我本人整理,但所有的思路及實現方式,都來源於黑馬程序員的視頻,算是半原創吧。如寫得很爛,歡迎大神們噴,但請不要辱罵。