bookStore續篇【新增許可權:動態代理和註解】

weixin_34253539發表於2018-01-21

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層呼叫該方法的時候就需要判斷使用者有沒有該許可權....

要點總結

  1. 外界呼叫Service層的方法是代理呼叫invoke()方法,我們在invoke()方法可以對其進行增強!
  2. invoke()方法內部就是在查詢呼叫該方法上有沒有註解,如果沒有註解,就可以直接呼叫。如果有註解,那麼就得到註解的資訊,判斷該使用者有沒有許可權來訪問這個方法
  3. 在反射具體方法的時候,必須記得要給出相對應的引數!
  4. 在invoke()方法丟擲的編譯時期異常,java會自動轉換成執行期異常進行丟擲...
  5. 使用contains()方法時,就要重寫該物件的hashCode()和equals()

如果您覺得這篇文章幫助到了您,可以給作者一點鼓勵

相關文章