java中不太常見的東西(5) - 註解

引言

在日常的開發過程中,其實每個人都用到了註解,最常見的就是重寫@Override。既然這麼常見爲什麼還要放入不常見的模塊中呢?在本篇博文中會詳細介紹關於註解的概念和各個組成部分,同時會寫出一個demo來說明自定義註解使用的一種情況。筆者目前整理的一些blog針對面試都是超高頻出現的。大家可以點擊鏈接:http://blog.csdn.net/u012403290

技術點

1、註解
註解也叫元數據,是一種形式化的方法,目的是爲了在代碼中添加信息,使我們可以想用這些信息的時候能快速的使用。註解可以簡化代碼。比如下面就是一個簡單的註解:

package com.brickworkers;

@Retention(RetentionPolicy.RUNTIME)//在運行時期保留,只有這樣纔可以在反射的時候拿到註解信息
@Target(ElementType.METHOD)//註解針對與方法
public @interface UserRole {

}

從上面可以看到,註解看上去很像接口的定義,要注意辨別。註解也會編譯成class文件的。

2、元註解
在java中有四種元註解,上面例子中的@Retention和@Target就是兩個元註解。他們的名稱和用途如下:

這裏寫圖片描述

@Target源碼:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    /**
     * Returns an array of the kinds of elements an annotation type
     * can be applied to.
     * @return an array of the kinds of elements an annotation type
     * can be applied to
     */
    ElementType[] value();
}

@Retention源碼:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    /**
     * Returns the retention policy.
     * @return the retention policy
     */
    RetentionPolicy value();
}

註解的讀取

註解其實就是一個狀態標識,可以標明註解所聲明的範圍狀態。所以我們必須要有一套機制,這套機制用於讀取註解的值。在java中,我們一般都用反射來作爲註解查找器。我們下面用一個例子來標明如何查找註解:

UserRole自定義註解類,標明該用戶的身份:

package com.brickworkers;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * 
 * @author Brickworker
 * Date:2017年5月2日下午1:06:24 
 * 關於類UserRole.java的描述:自定義註解,用於權限控制
 * Copyright (c) 2017, brcikworker All Rights Reserved.
 */
@Retention(RetentionPolicy.RUNTIME)//在運行時期保留,只有這樣纔可以在反射的時候拿到註解信息
@Target(ElementType.METHOD)//註解針對於方法
public @interface UserRole {

    static enum User{
        FATHER, MATHER, SON;
    }

    User value() default User.SON;
}

大家仔細觀察上面的代碼,我定義了一個內部枚舉,也是爲了偷懶,其實最好是把這個枚舉定義在外部。我賦予了3種身份權限,分別是爸爸,媽媽,兒子。同時這個自定義枚舉的運行規則請參照上面介紹的元註解信息,表示這個註解是定義在方法上的,在運行時期保留的,可以通過反射獲取到註解信息。
同時,如果在方法上使用此註解,如果不添加詳細的身份信息,那麼就默認是SON,比如下面這段代碼:

    // 騎玩具車
    @UserRole()
    public void rideToyCar() {
        System.out.println(this.name+"正在騎玩具車");
    }

在括號中不添加詳細的身份,那麼按照定義會默認SON。

Family類,家人類,用於表示家人對象和一些操作:

package com.brickworkers;

import com.brickworkers.UserRole.User;

/**
 * 
 * @author Brickworker 
 * Date:2017年5月2日下午1:35:09 
 * 關於類Family.java的描述:家人類 
 * Copyright(c) 2017, brcikworker All Rights Reserved.
 */
public class Family {

    private String name;

    private UserRole.User role;

    public Family(String name, UserRole.User role) {
        this.name = name;
        this.role = role;
    }

    public Family() {
        this.name = "爸爸";
        this.role = User.FATHER;
    }

    public String getName() {
        return name;
    }

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

    public UserRole.User getRole() {
        return role;
    }

    public void setRole(UserRole.User role) {
        this.role = role;
    }

    // 騎玩具車
    @UserRole(UserRole.User.SON)
    public void rideToyCar() {
        System.out.println(this.name+"正在騎玩具車");
    }

    // 吸菸
    @UserRole(UserRole.User.FATHER)
    public void smoke() {
        System.out.println(this.name+"正在吸菸");

    }

    // 打麻將
    @UserRole(UserRole.User.MATHER)
    public void playMahjong() {
        System.out.println(this.name+"正在打麻將");
    }
}

上面就是家人類,在這個類中標明瞭家人的身份和狀態,同時提供了3種操作方法,但是不同的人有不同的操作權限。

AnnotationTest類,註解測試類

package com.brickworkers;

import java.lang.reflect.Method;

import com.brickworkers.UserRole.User;

/**
 * 
 * @author Brickworker
 * Date:2017年5月2日下午1:07:37 
 * 關於類AnnotationTest.java的描述:註解測試類
 * Copyright (c) 2017, brcikworker All Rights Reserved.
 */
public class AnnotationTest {

    //我們通過反射判斷某一個方法是否具有權限
    public static boolean permission(String methodName, UserRole.User user) throws ClassNotFoundException, NoSuchMethodException, SecurityException{
        Class<?> clazz = Class.forName("com.brickworkers.Operate");
        Method method = clazz.getMethod(methodName);
        UserRole userRole = method.getAnnotation(UserRole.class);
        if(user == userRole.value()){//判斷這個身份是否能操作這個方法
            return true;
        }
        return false;
    }

    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, SecurityException {
        //定義一個兒子
        Family son = new Family("兒子", UserRole.User.SON);
        //定義一個爸爸
        Family father = new Family("爸爸", UserRole.User.FATHER);
        //兒子去嘗試抽菸
        //增加判斷權限
        if(permission("smoke", son.getRole())){
            son.smoke();
        }else{
            System.out.println("該身份的用戶不允許抽菸!!");
        }

        //爸爸嘗試抽菸
        if(permission("smoke", father.getRole())){
            father.smoke();
        }else{
            System.out.println("該身份的用戶不允許抽菸!!");
        }
    }
}

//輸出結果
//該身份的用戶不允許抽菸!!
//爸爸在吸菸

在反射中,我們獲取到即將要執行的方法的註解信息,和目前要執行的用戶身份進行判斷,如果身份一致則返回true,如果身份不一致,那麼就返回false。這也是一種很好的權限驗證機制。

進一步反思

我們前面已經實現了用註解+反射的機制進行簡單的權限控制。回想我以前有一篇文章,詳細介紹了兩種代理方式:JDK動態代理與CGlib代理。(文章鏈接:http://blog.csdn.net/u012403290/article/details/64443021)我們可以控制到方法執行之前和方法執行之後,那麼運用到註解中來,豈不美哉?下面就是我們修改之後的實現。

思考:
①我們沒有使用抽象的接口來描述方法,所以我們直接用CGLib動態代理。
②要實現CGLib動態代理,我們需要實現MethodInterceptor 接口。

如果你嘗試實現本博文的代碼,但是沒有依賴的話,可以去上面提到的博文鏈接中去尋找,我在那篇文章中留了下載CGLib動態代理的依賴jar包的地址。

以下是MyCglib類,實現了MethodInterceptor 接口

package com.brickworkers;

import java.lang.reflect.Method;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class MyCglib implements MethodInterceptor {

    //目標對象
    private Object obj = null;

    public Object getProxy(Object obj){
        this.obj = obj;
        Enhancer enhancer = new Enhancer();  
        enhancer.setSuperclass(obj.getClass());
        // 回調方法  
        enhancer.setCallback(this);  
        // 創建代理對象  
        return enhancer.create();

    }

    @Override
    public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        Object result = null;
        //在執行方法之前進行身份判斷
        if(obj instanceof Family){
            Family family = (Family) obj;
            //如果用戶權限符合方法的權限要求
            if(method.getAnnotation(UserRole.class).value().equals(family.getRole())){
                result = methodProxy.invoke(obj, args);
            }else{//如果不符合權限要求則拋出一個錯誤或者生產一個提示
                System.err.println(family.getName()+"不具有操作"+method.getName()+"方法的權限");
            }
        }
                return result;
    }

}

AnnotationTest類,測試結果:

package com.brickworkers;

import com.brickworkers.UserRole.User;

/**
 * 
 * @author Brickworker
 * Date:2017年5月2日下午1:07:37 
 * 關於類AnnotationTest.java的描述:註解測試類
 * Copyright (c) 2017, brcikworker All Rights Reserved.
 */
public class AnnotationTest {


    public static void main(String[] args){
        MyCglib myCglib = new MyCglib();
        Family son = (Family) myCglib.getProxy(new Family("兒子", User.SON));
        son.smoke();
        son.rideToyCar();
        Family father = (Family) myCglib.getProxy(new Family("爸爸", User.FATHER));
        father.smoke();
        father.rideToyCar();
    }
}

//運行結果
//兒子不具有操作smoke方法的權限
//兒子正在騎玩具車
//爸爸不具有操作rideToyCar方法的權限
//爸爸正在吸菸

尾記

註解的知識其實是博大精深的,尤其是在現代的項目開發中。比如說Spring中,hibernate中等等,都用了大量的註解來簡化代碼量。註解使用起來非常方便,比如我們文章中的例子,以後又來一個新的方法,爲了保護方法,我們可以定義好這個方法的執行權限,沒有達到權限的一律屏蔽,可以達到增強系統安全性的作用。
在RestFul框架的開發過程中,我們用HTTP協議暴露接口,我們就可以在每一個接口上定義好一種註解,每種註解可以標明這個方法是給誰提供的,比如說用戶即使知道了管理員的一個接口,但是他仍舊不具備權限訪問。因爲基於HTTP協議,我們通常把註解結合url攔截器共同使用。

希望對大家有所幫助。

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