一直想了解一下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元钱,感谢!
知识星球有更多干货内容,对我认可欢迎加入: