tags: JavaWeb小專案
前言
目前為止,我們已經學習了動態代理技術和註解技術了。於是我們想要為之前的bookStore專案新增許可權控制.....
只有使用者有許可權的時候,後臺管理才可以進行相對應的操作.....
實現思路
之前我們做許可權管理系統的時候,是根據使用者請求的URI來判斷該連結是否需要許可權的。這次我們使用動態代理的技術和註解來判斷:使用者呼叫該方法時,檢查該方法是否需要許可權...
根據MVC模式,我們在web層都是呼叫service層來實現功能的。那麼我們具體的思路是這樣的:
- web層呼叫service層的時候,得到的並不是ServiceDao物件,而是我們的代理物件
- 在service層中的方法新增註解,如果方法上有註解,那麼說明呼叫該方法需要許可權...
- 當web層呼叫代理物件方法的時候,代理物件會判斷該方法是否需要許可權,再給出相對應的提示....
設計實體、資料庫表
上次我們做的許可權管理系統是引入了角色這個概念的,這次主要為了練習動態代理和註解技術,就以簡單為主,不引入角色這個實體。直接是使用者和許可權之間的關係了。
Privilege實體
public class Privilege {
private String id ;
private String name;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
複製程式碼
資料庫表
- privilege表
CREATE TABLE privilege (
id VARCHAR(40) PRIMARY KEY,
name VARCHAR(40)
);
複製程式碼
privilege和user是多對多的關係,於是使用第三方表來維護他們的關係
- user_privilege表
CREATE TABLE user_privilege (
privilege_id VARCHAR(40),
user_id VARCHAR(40),
PRIMARY KEY (privilege_id, user_id),
CONSTRAINT privilege_id_FK FOREIGN KEY (privilege_id) REFERENCES privilege(id),
CONSTRAINT user_id_FK1 FOREIGN KEY (user_id) REFERENCES user(id)
);
複製程式碼
新增測試資料
為了方便,直接新增資料了。就不寫詳細的DAO了。
- 在資料庫中新增了兩個許可權
- 為id為1的user新增了兩個許可權
編寫DAO
後面在動態代理中,我們需要檢查該使用者是否有許可權...那麼就必須查詢出該使用者擁有的哪些許可權。再看看使用者有沒有相對應的許可權
//查詢使用者的所有許可權
public List<Privilege> findUserPrivilege(String user_id) {
QueryRunner queryRunner = new QueryRunner(Utils2DB.getDataSource());
String sql = "SELECT p.* FROM privilege p, user_privilege up WHERE p.id = up.privilege_id AND up.user_id = ?";
try {
return (List<Privilege>) queryRunner.query(sql, new Object[]{user_id}, new BeanListHandler(Privilege.class));
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
複製程式碼
抽取到介面上
List<Privilege> findUserPrivilege(String user_id);
複製程式碼
註解模組
- 編寫註解
@Retention(RetentionPolicy.RUNTIME)
public @interface permission {
String value();
}
複製程式碼
- 在Service層方法中需要許可權的地方新增註解CategoryServiceImpl
@permission("新增分類")
/*新增分類*/
public void addCategory(Category category) {
categoryDao.addCategory(category);
}
/*查詢分類*/
public void findCategory(String id) {
categoryDao.findCategory(id);
}
@permission("查詢分類")
/*檢視分類*/
public List<Category> getAllCategory() {
return categoryDao.getAllCategory();
}
複製程式碼
抽取Service
把Service的方法抽取成ServiceDao。在Servlet中,也是通過ServiceFactory來得到Service的物件【和DaoFactory是類似的】
CategoryService
@permission("新增分類")
/*新增分類*/ void addCategory(Category category);
/*查詢分類*/
void findCategory(String id);
@permission("查詢分類")
/*檢視分類*/ List<Category> getAllCategory();
複製程式碼
ServiceFactory
public class ServiceDaoFactory {
private static final ServiceDaoFactory factory = new ServiceDaoFactory();
private ServiceDaoFactory() {
}
public static ServiceDaoFactory getInstance() {
return factory;
}
//需要判斷該使用者是否有許可權
public <T> T createDao(String className, Class<T> clazz, final User user) {
System.out.println("新增分類進來了!");
try {
//得到該類的型別
final T t = (T) Class.forName(className).newInstance();
//返回一個動態代理物件出去
return (T) Proxy.newProxyInstance(ServiceDaoFactory.class.getClassLoader(), t.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, PrivilegeException {
//判斷使用者呼叫的是什麼方法
String methodName = method.getName();
System.out.println(methodName);
//得到使用者呼叫的真實方法,注意引數!!!
Method method1 = t.getClass().getMethod(methodName,method.getParameterTypes());
//檢視方法上有沒有註解
permission permis = method1.getAnnotation(permission.class);
//如果註解為空,那麼表示該方法並不需要許可權,直接呼叫方法即可
if (permis == null) {
return method.invoke(t, args);
}
//如果註解不為空,得到註解上的許可權
String privilege = permis.value();
//設定許可權【後面通過它來判斷使用者的許可權有沒有自己】
Privilege p = new Privilege();
p.setName(privilege);
//到這裡的時候,已經是需要許可權了,那麼判斷使用者是否登陸了
if (user == null) {
//這裡丟擲的異常是代理物件丟擲的,sun公司會自動轉換成執行期異常丟擲,於是在Servlet上我們根據getCause()來判斷是不是該異常,從而做出相對應的提示。
throw new PrivilegeException("對不起請先登陸");
}
//執行到這裡使用者已經登陸了,判斷使用者有沒有許可權
Method m = t.getClass().getMethod("findUserPrivilege", String.class);
List<Privilege> list = (List<Privilege>) m.invoke(t, user.getId());
//看下許可權集合中有沒有包含方法需要的許可權。使用contains方法,在Privilege物件中需要重寫hashCode和equals()
if (!list.contains(p)) {
//這裡丟擲的異常是代理物件丟擲的,sun公司會自動轉換成執行期異常丟擲,於是在Servlet上我們根據getCause()來判斷是不是該異常,從而做出相對應的提示。
throw new PrivilegeException("您沒有許可權,請聯絡管理員!");
}
//執行到這裡的時候,已經有許可權了,所以可以放行了
return method.invoke(t, args);
}
});
} catch (Exception e) {
new RuntimeException(e);
}
return null;
}
}
複製程式碼
PrivilegeExcetption
當使用者沒有登陸或者沒有許可權的時候,我們應該給使用者一些友好的提示....於是我們自定義了PrivilegeException
public class PrivilegeException extends Exception {
public PrivilegeException() {
super();
}
public PrivilegeException(String message) {
super(message);
}
public PrivilegeException(String message, Throwable cause) {
super(message, cause);
}
public PrivilegeException(Throwable cause) {
super(cause);
}
}
複製程式碼
我們繼承的是Exception,通過方法名丟擲去。但是我們是通過代理物件呼叫方法的,於是sun公司的策略就是把它們轉換成執行期異常丟擲去。
因此,我們就在Servlet上得到異常,再給出友好的提示。。
效果:
- 沒有登陸的時候:
- 登陸了,但是沒有相對應的許可權的時候
- 登陸了,並且有許可權
總結
該許可權控制是十分優雅的,只要我在Service層中新增一個註解...那麼當web層呼叫該方法的時候就需要判斷使用者有沒有該許可權....
要點總結
- 外界呼叫Service層的方法是代理呼叫invoke()方法,我們在invoke()方法可以對其進行增強!
- invoke()方法內部就是在查詢呼叫該方法上有沒有註解,如果沒有註解,就可以直接呼叫。如果有註解,那麼就得到註解的資訊,判斷該使用者有沒有許可權來訪問這個方法
- 在反射具體方法的時候,必須記得要給出相對應的引數!
- 在invoke()方法丟擲的編譯時期異常,java會自動轉換成執行期異常進行丟擲...
- 使用contains()方法時,就要重寫該物件的hashCode()和equals()
如果您覺得這篇文章幫助到了您,可以給作者一點鼓勵