java反射获得注释_JAVA反射+运行时注解实现URL路由

一直想了解一下Spring mvc这套框架,但是每次看完就头大,大量的注解、XML配置让人捉摸不透。

为了减少一些痛苦,我决定简单的学习一下JAVA的反射与注解,实现一个简单的URL路由来验证这些高级功能。

目标

最终我可以写一些Controller类,在里面通过注解配置URL路由:

Java

package cc.yuerblog.Controller;

import cc.yuerblog.Router.RouteMapping;

public class IndexController {

@RouteMapping(uri = "/login")

public void actionLogin() {

System.out.println("/login called");

}

@RouteMapping(uri = "/logout")

public void actionLogout() {

System.out.println("/logout called");

}

private void someFunc() {

}

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

packagecc.yuerblog.Controller;

importcc.yuerblog.Router.RouteMapping;

publicclassIndexController{

@RouteMapping(uri="/login")

publicvoidactionLogin(){

System.out.println("/login called");

}

@RouteMapping(uri="/logout")

publicvoidactionLogout(){

System.out.println("/logout called");

}

privatevoidsomeFunc(){

}

}

访问/login时,路由应该调用IndexController.actionLogin方法;访问/logout时,路由应该调用IndexController.actionLogout方法。

对于一个强类型语言来说,如何实现这样的动态效果呢?答案就是:注解,反射。

预备知识

我是通过学习这篇博客对注解有了基本的掌握,建议大家也先学习一下:《自己动手实现JAVA注解》。

注解

上面使用的@RouteMapping是一个注解,它声明了action方法对应的URI地址是什么。

注解相当于对方法附加了一些描述信息,后续可以通过反射机制获得注解里的信息,从而获知action对应的uri是/login还是/logout。

Java

package cc.yuerblog.Router;

import java.lang.annotation.ElementType;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

import java.lang.annotation.Target;

/**

* 运行时可以反射类方法得到注解信息

*/

@Target(ElementType.METHOD)

@Retention(RetentionPolicy.RUNTIME)

public @interface RouteMapping {

String uri(); // 注解需要声明路由的uri

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

packagecc.yuerblog.Router;

importjava.lang.annotation.ElementType;

importjava.lang.annotation.Retention;

importjava.lang.annotation.RetentionPolicy;

importjava.lang.annotation.Target;

/**

* 运行时可以反射类方法得到注解信息

*/

@Target(ElementType.METHOD)

@Retention(RetentionPolicy.RUNTIME)

public@interfaceRouteMapping{

Stringuri();// 注解需要声明路由的uri

}

定义注解需要使用@interface语法,Target说明注解可以用于一个类方法还是一个类成员变量。

Retention说明注解在什么阶段可以被访问,这里我的需求是在程序运行时通过反射解析注解里的uri变量,所以配置了RUNTIME。

注册路由

Spring是通过XML配置指定controller类存储的目录,这样Spring框架会在初始化时扫描下面的所有class,解析其中的注解并生成路由表。

为了简化,我这里需要显式的向路由添加controller的完整class名称,避免扫描目录的步骤:

Java

package cc.yuerblog;

import cc.yuerblog.Router.Router;

public class App

{

public static void main( String[] args ) {

// 注册路由

Router router = new Router("target/classes/");

router.addRouter("cc.yuerblog.Controller.IndexController");

// 测试路由

router.testRoute("/login");

router.testRoute("/logout");

}

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

packagecc.yuerblog;

importcc.yuerblog.Router.Router;

publicclassApp

{

publicstaticvoidmain(String[]args){

// 注册路由

Routerrouter=newRouter("target/classes/");

router.addRouter("cc.yuerblog.Controller.IndexController");

// 测试路由

router.testRoute("/login");

router.testRoute("/logout");

}

}

Router是路由器类,它查找controller的根路径是target/classes,这个目录存放了所有controller编译后的.class文件。

接下来通过addRouter注册一个controller到路由器,Router会根据类名在target/classes目录下找到对应的.class文件进行类加载。

此后,通过testRoute方法进行测试,传入一个URI会触发某个action被调用。

路由器

Java

public class Router {

private class Action {

public Action(Object object, Method method) {

this.object = object;

this.method = method;

}

public void call() {

try {

method.invoke(object);

} catch (IllegalAccessException e) {

e.printStackTrace();

} catch (InvocationTargetException e) {

e.printStackTrace();

}

}

private Object object;

private Method method;

}

private ControllerLoader controllerLoader;

private Map controllerBeans = new HashMap();

private Map uri2Action = new HashMap();

public Router(String basePath) {

controllerLoader = new ControllerLoader(basePath);

}

public void addRouter(String controllerClass) {

try {

// 加载class

Class> cls = controllerLoader.loadClass(controllerClass);

// 反射class中所有方法

Method[] methods = cls.getDeclaredMethods();

for (Method method : methods) {

// 反射方法所有注解

Annotation[] annotations = method.getAnnotations();

for (Annotation annotation : annotations) {

// 如果注解类型是RouteMapping, 解析其URI

if (annotation.annotationType() == RouteMapping.class) {

RouteMapping anno = (RouteMapping)annotation;

// 路由uri

String uri = anno.uri();

// 保存Bean单例

if (!controllerBeans.containsKey(cls.getName())) {

controllerBeans.put(cls.getName(), cls.newInstance());

}

// 保存uri -> (obj,method)

uri2Action.put(uri, new Action(controllerBeans.get(cls.getName()), method));

}

}

}

} catch (Exception e) {

e.printStackTrace();

}

}

public void testRoute(String uri) {

Action action = uri2Action.get(uri);

if (action != null) {

action.call();

} else {

System.out.println(uri + " is not found");

}

}

}

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

publicclassRouter{

privateclassAction{

publicAction(Objectobject,Methodmethod){

this.object=object;

this.method=method;

}

publicvoidcall(){

try{

method.invoke(object);

}catch(IllegalAccessExceptione){

e.printStackTrace();

}catch(InvocationTargetExceptione){

e.printStackTrace();

}

}

privateObjectobject;

privateMethodmethod;

}

privateControllerLoadercontrollerLoader;

privateMapcontrollerBeans=newHashMap();

privateMapuri2Action=newHashMap();

publicRouter(StringbasePath){

controllerLoader=newControllerLoader(basePath);

}

publicvoidaddRouter(StringcontrollerClass){

try{

// 加载class

Class>cls=controllerLoader.loadClass(controllerClass);

// 反射class中所有方法

Method[]methods=cls.getDeclaredMethods();

for(Methodmethod:methods){

// 反射方法所有注解

Annotation[]annotations=method.getAnnotations();

for(Annotationannotation:annotations){

// 如果注解类型是RouteMapping, 解析其URI

if(annotation.annotationType()==RouteMapping.class){

RouteMappinganno=(RouteMapping)annotation;

// 路由uri

Stringuri=anno.uri();

// 保存Bean单例

if(!controllerBeans.containsKey(cls.getName())){

controllerBeans.put(cls.getName(),cls.newInstance());

}

// 保存uri -> (obj,method)

uri2Action.put(uri,newAction(controllerBeans.get(cls.getName()),method));

}

}

}

}catch(Exceptione){

e.printStackTrace();

}

}

publicvoidtestRoute(Stringuri){

Actionaction=uri2Action.get(uri);

if(action!=null){

action.call();

}else{

System.out.println(uri+" is not found");

}

}

}

addRouter首先调用controllerLoader对象从磁盘上的.class文件加载对应的controller类。

这里用到了JAVA的另外一个重要概念就是class loader,我是通过这篇博客学习的:《深入探讨JAVA类加载器》。

无论如何,在这里IndexController类在运行时从.class文件被加载到运行时环境中,也就是得到了对应的class对象。

通过class对象可以进行反射,得到这个类的方法,再解析方法上的注解。

如果注解对象的class类型等于RouteMapping.class,就获取注解对象的uri属性,也就是之前@RouteMapping(uri=…)配置的属性。

通过controllerBeans维护controller单例对象,通过uri2Action维护uri -> action的关联关系。

Action

所谓action就是一个接口,它使用单例controller对象作为调用主体,通过method反射可以完成方法的调起:

Java

method.invoke(object)

1

method.invoke(object)

相当于object.method(),应该不难理解

最后,testAction方法根据uri在uri2Action中找到对应的Action对象,通过action.call()唤起对应的处理方法。

class loader

实现自己的class loader,只需要覆写findClass方法,在该方法中将对应的类文件读取进来,通过defineClass方法完成加载,最后返回即可。

Java

package cc.yuerblog.Router;

import java.io.ByteArrayOutputStream;

import java.io.FileInputStream;

import java.io.IOException;

import java.io.InputStream;

// 为了加载.class文件中的类

public class ControllerLoader extends ClassLoader {

private String basePath;

public ControllerLoader(String basePath) {

this.basePath = basePath;

}

@Override

protected Class> findClass(String name) throws ClassNotFoundException {

// 找到.class文件

String path = basePath + name.replaceAll("\\.", "/");

// 读取.class文件

byte[] classBytes;

InputStream ins = null;

try {

ins = new FileInputStream(path);

ByteArrayOutputStream bout = new ByteArrayOutputStream();

byte[] buffer = new byte[4096];

int bytesNumRead = 0;

while ((bytesNumRead = ins.read(buffer)) != -1) {

bout.write(buffer, 0, bytesNumRead);

}

classBytes = bout.toByteArray();

} catch (Exception e) {

throw new ClassNotFoundException();

} finally {

try {

if (ins != null) {

ins.close();

}

} catch (Exception e) {

throw new ClassNotFoundException();

}

}

// 生成class类

return defineClass(name, classBytes, 0, classBytes.length);

}

}

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

packagecc.yuerblog.Router;

importjava.io.ByteArrayOutputStream;

importjava.io.FileInputStream;

importjava.io.IOException;

importjava.io.InputStream;

// 为了加载.class文件中的类

publicclassControllerLoaderextendsClassLoader{

privateStringbasePath;

publicControllerLoader(StringbasePath){

this.basePath=basePath;

}

@Override

protectedClass>findClass(Stringname)throwsClassNotFoundException{

// 找到.class文件

Stringpath=basePath+name.replaceAll("\\.","/");

// 读取.class文件

byte[]classBytes;

InputStreamins=null;

try{

ins=newFileInputStream(path);

ByteArrayOutputStreambout=newByteArrayOutputStream();

byte[]buffer=newbyte[4096];

intbytesNumRead=0;

while((bytesNumRead=ins.read(buffer))!=-1){

bout.write(buffer,0,bytesNumRead);

}

classBytes=bout.toByteArray();

}catch(Exceptione){

thrownewClassNotFoundException();

}finally{

try{

if(ins!=null){

ins.close();

}

}catch(Exceptione){

thrownewClassNotFoundException();

}

}

// 生成class类

returndefineClass(name,classBytes,0,classBytes.length);

}

}

最后

如果文章帮助了你,请帮我点击1次谷歌广告,或者微信赞助1元钱,感谢!

c68972f84f7c4f47f59a1f69f0608e10.png

知识星球有更多干货内容,对我认可欢迎加入:

6c9a48ad74e3675cedd6ca98c1fd0a1f.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值