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拦截器共同使用。

希望对大家有所帮助。

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