0%

java注解写一个简单路由示例

注解

注解是放在Java源码的类、方法、字段、参数前的一种特殊“注释”。是附加在代码中的一些元信息,用于编译和运行时进行解析和使用,起到说明、配置的功能。注解不会影响代码的实际逻辑,仅仅起到辅助性的作用。包含在java.lang.annotation包中。

​ 注释不会打包到class文件中,注解则可以被编译器打包进入class文件,因此,注解是一种用作标注的“元数据”。

注解大致分为三类

  • 第一类是由编译器使用的注解【这类注解不会被编译进入.class文件,它们在编译后就被编译器扔掉了。】
  • @Override:让编译器检查该方法是否正确地实现了覆写;
  • @SuppressWarnings:告诉编译器忽略此处代码产生的警告
  • 第二类是由工具处理.class文件使用的注解

    有些工具会在加载class的时候,对class做动态修改,实现一些特殊的功能。这类注解会被编译进入.class文件,但加载结束后并不会存在于内存中。这类注解只被一些底层库使用,一般我们不必自己处理。

  • 第三类是在程序运行期能够读取的注解【使用@Retention(RetentionPolicy.RUNTIME) 注解修饰】

    它们在加载后一直存在于JVM中,这也是最常用的注解。例如,一个配置了@PostConstruct的方法会在调用构造方法后自动被调用(这是Java代码读取该注解实现的功能,JVM并不会识别该注解)。

注解的使用

Java语言使用@interface语法来定义注解(Annotation),它的格式如下.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//定义注解路由
package route;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//描述注解的作用域
@Target({ElementType.METHOD,ElementType.TYPE})
//定义注解的使用期间
@Retention(RetentionPolicy.RUNTIME)
public @interface pathMap {
//给注解定义一个参数
String uri() default "";
}

注解的参数类似无参数方法,可以用default设定一个默认值(强烈推荐).

  • 元注解

    有一些注解可以修饰其他注解,这些注解就称为元注解(meta annotation)。Java标准库已经定义了一些元注解,我们只需要使用元注解,通常不需要自己去编写元注解。

    最常用的元注解是@Target。使用@Target可以定义Annotation能够被应用于源码的哪些位置:

    • 类或接口:ElementType.TYPE

    • 字段:ElementType.FIELD

    • 方法:ElementType.METHOD

    • 构造方法:ElementType.CONSTRUCTOR

    • 方法参数:ElementType.PARAMETER

    另一个重要的元注解@Retention定义了Annotation的生命周期:

    • 仅编译期:RetentionPolicy.SOURCE

    • 仅class文件:RetentionPolicy.CLASS

    • 运行期:RetentionPolicy.RUNTIME

如果@Retention不存在,则该Annotation默认为CLASS。因为通常我们自定义的Annotation都是RUNTIME,所以,务必要加上@Retention(RetentionPolicy.RUNTIME)这个元注解.

处理注解

Java的注解本身对代码逻辑没有任何影响。根据@Retention的配置:

  • SOURCE类型的注解在编译期就被丢掉了;
  • CLASS类型的注解仅保存在class文件中,它们不会被加载进JVM;
  • RUNTIME类型的注解会被加载进JVM,并且在运行期可以被程序读取。

如何使用注解完全由工具决定。SOURCE类型的注解主要由编译器使用,因此我们一般只使用,不编写。CLASS类型的注解主要由底层工具库使用,涉及到class的加载,一般我们很少用到。只有RUNTIME类型的注解不但要使用,还经常需要编写。

​ 因为注解定义后也是一种class,所有的注解都继承自java.lang.annotation.Annotation,因此,读取注解,需要使用反射API。

Java提供的使用反射API读取Annotation的方法包括。

判断某个注解是否存在于ClassFieldMethodConstructor

  • Class.isAnnotationPresent(Class)

  • Field.isAnnotationPresent(Class)

  • Method.isAnnotationPresent(Class)

  • Constructor.isAnnotationPresent(Class)

使用反射API读取Annotation:

  • Class.getAnnotation(Class)
  • Field.getAnnotation(Class)
  • Method.getAnnotation(Class)
  • Constructor.getAnnotation(Class)

简单的路由设计

第一步编写路由注解:上边的pathMap,用来在controller里使用。

第二步编写使用注解的controller:

1
2
3
4
5
6
7
8
9
10
11
package route;
//使用注解定义路径
@pathMap(uri = "/test")
public class Controller {
@pathMap(uri = "hello")
public void hello()
{
System.out.println("hello");
}
}

第三步由于需要使用反射,或者自动加载某些controller类,所以需要扩展一个自己的自动加载类,用来加载controller类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
package route;

import java.io.FileInputStream;
import java.util.ArrayList;

public class loadClass extends ClassLoader {

private ArrayList<String> filePathArr;

public loadClass()
{
this.filePathArr = new ArrayList<String>();
}

public loadClass addFilePath(String filePath)
{
this.filePathArr.add(filePath);
return this;
}


/**
* Class.forName(className)方法,内部实际调用的方法是 Class.forName(className,true,classloader);
*
* 第2个boolean参数表示类是否需要初始化, Class.forName(className)默认是需要初始化。
*
* 一旦初始化,就会触发目标对象的 static块代码执行,static参数也也会被再次初始化。
*
*
*
* ClassLoader.loadClass(className)方法,内部实际调用的方法是 ClassLoader.loadClass(className,false);
*
* 第2个 boolean参数,表示目标对象是否进行链接,false表示不进行链接,由上面介绍可以,
*
* 不进行链接意味着不进行包括初始化等一些列步骤,那么静态块和静态对象就不会得到执行
* @param fileName
* @return
* @throws ClassNotFoundException
*/

public Class<?> findClass(String fileName) throws ClassNotFoundException
{
try {
byte[] data = this.loadByte(fileName);
return defineClass(fileName, data, 0, data.length);
} catch (Exception e) {
e.printStackTrace();
throw new ClassNotFoundException();
}

}


/**
*
* @param name
* @return
* @throws Exception
*/
private byte[] loadByte(String name) throws Exception
{
name = name.replaceAll("\\.", "/");
byte[] data;
FileInputStream fis = new FileInputStream(name + ".class");
int len = fis.available();
data = new byte[len];
fis.read(data);
fis.close();
return data;

}
}

第四步:编写路由类,加载和解析使用注解的controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
//处理注解的route类
package route;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashMap;
public class route {

private HashMap<String,Object> controllers = new HashMap<>();

private HashMap<String,Action> action = new HashMap<>();

protected class Action
{
private Object object;

private Method method;

public Action(Object object,Method method) {
this.method = method;
this.object = object;
}

public Object call() {
try {
method.invoke(object);
}catch (Exception e){

}
return null;
}
}

public void add(String classPath) throws Exception
{
Class<?> cls = (new loadClass()).addFilePath("./").loadClass(classPath);
// 反射class中所有方法
Method[] methods = cls.getDeclaredMethods();
Annotation[] annotations;
String rootPath = "";
//获取类的注解
annotations = cls.getAnnotations();
for (Annotation annotation : annotations) {
// 如果注解类型是RouteMapping, 解析其URI
if (annotation.annotationType() == pathMap.class) {
pathMap anno = (pathMap) annotation;
// 路由uri
rootPath = anno.uri();
}
}
String startWith,endWith;
if(!rootPath.startsWith("/")) {
rootPath = "/" + rootPath;
}
if(!rootPath.endsWith("/")) {
rootPath = rootPath + "/";
}
//遍历方法的所有注解
for (Method method : methods) {
// 反射方法所有注解
annotations = method.getAnnotations();
for (Annotation annotation : annotations) {
// 如果注解类型是RouteMapping, 解析其URI
if (annotation.annotationType() == pathMap.class) {
pathMap anno = (pathMap)annotation;
// 路由uri
String uri = anno.uri();
// 保存Bean单例
if (!controllers.containsKey(cls.getName())) {
controllers.put(cls.getName(), cls.newInstance());
}
// 保存uri -> (obj,method)
System.out.println(rootPath + uri);
action.put(rootPath + uri, new Action(controllers.get(cls.getName()), method));
}
}
}
}

public void test(String urlPath) {
Action ac = action.get(urlPath);
if (ac != null) {
ac.call();
} else {
System.out.println(urlPath + " is not found");
}
}

public static void main(String[] argv) {
try {
route r = new route();
r.add("route.Controller");
r.test("/test/hello");
}catch (Exception e) {
System.out.println(e.getMessage());
}
}
}