爲bookStore添加權限【動態代理和註解】

前言

目前爲止,咱們已經學習了動態代理技術和註解技術了。因而咱們想要爲以前的bookStore項目添加權限控制…..java

只有用戶有權限的時候,後臺管理才能夠進行相對應的操做…..web


實現思路

這裏寫圖片描述

以前咱們作權限管理系統的時候,是根據用戶請求的URI來判斷該連接是否須要權限的。此次咱們使用動態代理的技術和註解來判斷:用戶調用該方法時,檢查該方法是否須要權限…sql

根據MVC模式,咱們在web層都是調用service層來實現功能的。那麼咱們具體的思路是這樣的:數據庫

  • web層調用service層的時候,獲得的並非ServiceDao對象,而是咱們的代理對象
  • 在service層中的方法添加註解,若是方法上有註解,那麼說明調用該方法須要權限…
  • 當web層調用代理對象方法的時候,代理對象會判斷該方法是否須要權限,再給出相對應的提示….

設計實體、數據庫表

上次咱們作的權限管理系統是引入了角色這個概念的,此次主要爲了練習動態代理和註解技術,就以簡單爲主,不引入角色這個實體。直接是用戶和權限之間的關係了。markdown

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是多對多的關係,因而使用第三方表來維護他們的關係ide

  • 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層方法中須要權限的地方添加註解
@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是相似的】this

ServiceDao

@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

當用戶沒有登錄或者沒有權限的時候,咱們應該給用戶一些友好的提示….因而咱們自定義了PrivilegeExceptionspa

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層調用該方法的時候就須要判斷用戶有沒有該權限….

要點總結

  1. 外界調用Service層的方法是代理調用invoke()方法,咱們在invoke()方法能夠對其進行加強!
  2. 在反射具體方法的時候,必須記得要給出相對應的參數!
  3. 在invoke()方法拋出的編譯時期異常,java會自動轉換成運行期異常進行拋出…
  4. 使用contains()方法時,就要重寫該對象的hashCode()和equals()
相關文章
相關標籤/搜索