引言
在日常的开发过程中,其实每个人都用到了注解,最常见的就是重写@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拦截器共同使用。
希望对大家有所帮助。