java注解原生模仿@ModelAttribute功能

Screenshot from 2017-12-19 22-46-25.png

对于做Java Web开发的人来说,肯定都遇到过表单提交,并且,每一个表单的提交后面至少跟一个对象,比如说一个登录的表单,后面一定跟着一个登录对象(也有可能直接就是User对象),既然有对象,势必需要数据绑定到对象的属性了,在原生的Servlet开发中,我们用HttpServletRequest对象的getParamter(String s)的方法去获取表单中对应name属性的值;如果是使用Srping框架的,那么一定用过@ModelAttribute这个注解来实现表单的数据绑定。原生的Servlet方法我们就不讲了,这个太easy了,今天我们要来说的是如何原生实现@ModelAttribute注解的数据绑定功能

首先,我们先创建两个Java文件,类型为Annotation

Screenshot from 2017-12-19 22-48-12.png

一个我们命名为SetMethod,另一个命名为GetMethod,代码如下:

GetMethod 方法

1
2
3
4
5
6
7
8
import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface GetMethod {
public String value();
}

SetMethod 方法

1
2
3
4
5
6
7
8
import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SetMethod {
public String value();
}

之所以定义SetMethod和GetMethod,是因为在Java Web开发中,我们为实体对象的属性设置了get和set方法用于获取实体的属性。现在,我们来说说这两个注解中的@Target、@Retention、@Document是什么意思:

  • @Target :指示注释类型适用的上下文,在这里,我们设定适用的上下文为方法——Method
  • @Retention:指示要注释具有注释类型的注释的保留时间。 如果注释类型声明中没有保留注释,则保留策略默认为RetentionPolicy.CLASS 。保留元注释仅在元注释类型直接用于注释时才起作用。 如果元注释类型用作另一注释类型的成员类型,则它不起作用。在这里,我们将注解的保留时间为运行时
  • @Document:表示具有类型的注释默认情况下由javadoc和类似工具记录
    现在,我们成功的写了两个annotation——SetMethod 和 GetMethod,但是才一个方法呢?
1
public String value()

别小看这个方法,在GetMethod类中,我们这个方法通过GetMethod(String s)可以直接获取对象的属性值,String s的值就是对象过的属性名;在SetMethod类中,我们可以通过SetMethod(String s)来为你的set方法进行注解,而String s的值就是对象属性名,就这样,我们轻松的实现了数据绑定的前期工作;使用这两个方法如下:

GetMethod、SetMethod使用方法

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
101
102
103
104
package com.work.entity;

import java.util.Date;

public class User {

private String username;
private String password;
private String role;
private int id;
private String link;
private String adress;
private Date birthday;
private String depart;

public User() {
}

@GetMethod("username")
public String getUsername() {
return username;
}

@SetMethod("username")
public void setUsername(String username) {
this.username = username;
}

@GetMethod("password")
public String getPassword() {
return password;
}

@SetMethod("password")
public void setPassword(String password) {
this.password = password;
}

public String getRole() {
return role;
}

@SetMethod("role")
public void setRole(String role) {
this.role = role;
}

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public String getLink() {
return link;
}

public void setLink(String link) {
this.link = link;
}

public String getAdress() {
return adress;
}

@SetMethod("address")
public void setAdress(String adress) {
this.adress = adress;
}

public Date getBirthday() {
return birthday;
}

@SetMethod("birthday")
public void setBirthday(Date birthday) {
this.birthday = birthday;
}

public String getDepart() {
return depart;
}

@SetMethod("depart")
public void setDepart(String depart) {
this.depart = depart;
}

@Override
public String toString() {
return "User{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
", role='" + role + '\'' +
", id=" + id +
", link='" + link + '\'' +
", adress='" + adress + '\'' +
", birthday=" + birthday +
", depart='" + depart + '\'' +
'}';
}
}

(这里给个小建议,为你的每一个对象都生成toString方法,这会为后期调试提供巨大便利)

现在,我们完成了百分之五十的工作,那么,怎么实现从HttpServletRequest对象将表单数据绑定到我们定义的对象中呢?我们还需要创建一个类,就叫做DataBind吧,先附上代码:

数据绑定代码

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
package com.work.entity;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;

/**
* @author chuntaojun
*/
public class DataBind<T> {

public static Object Request2Bean(HttpServletRequest request, Object object) {
if (object == null) {
return null;
}
Method[] methods = object.getClass().getMethods();
for (Method method : methods) {
if (method.isAnnotationPresent(SetMethod.class)) {
SetMethod sm = method.getAnnotation(SetMethod.class);
String filedName = sm.value();
Object fieldValue = request.getParameter(filedName);
Type[] params = method.getGenericParameterTypes();
try {
if (params[0].getTypeName().contains("Date")) {
DateFormat fmt = new SimpleDateFormat("yyyy-MM-dd");
fieldValue = fmt.parse((String) fieldValue);
}
method.invoke(object, fieldValue);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (ParseException e) {
e.printStackTrace();
}
}
}
return object;
}
}

我们在这个DataBind类中写了一个方法——Request2Bean,这个方法里面有两个参数,一个是HttpServletRequest,另一个是我们Object;我们都过Java提供的反射机制,获取Object对象的所有方法

1
Method[] methods = object.getClass().getMethods();

然后,我们遍历方法数组,在循环中,首先我们先判断这个方法是后被SetMethod这个注解作用了,如果是,我们就获取这个SetMethod这个对象实例,并获取SetMethod(String s)中s的值

1
2
3
method.isAnnotationPresent(SetMethod.class)
SetMethod sm = method.getAnnotation(SetMethod.class);
String filedName = sm.value();

这个时候,我们通过fileName这个String对象从HttpServletRequest对象的getParamter方法取值

1
Object fieldValue = request.getParameter(filedName);

由于我们的User对象中有Date对象,如果我们直接

1
method.invoke(object, fieldValue);

会直接报 IllegalAccessException 错误,因为我们的getParamter方法返回的是一个String类型的对象,因此,我们还需要对方法中的参数类型做一个判断,在数据绑定的时候进行数据类型转换:

1
Type[] params = method.getGenericParameterTypes();

这样我们就获取了方法中所有参数的类型,接着,我们获取参数类型的name,进行字符串的比较来实现数据类型的手动转换:

1
2
3
4
if (params[0].getTypeName().contains("Date")) {
DateFormat fmt = new SimpleDateFormat("yyyy-MM-dd");
fieldValue = fmt.parse((String) fieldValue);
}

随后,我们只要调用

1
method.invoke(object, fieldValue);

就可以完成set方法的调用,将表单的value绑定到我们的User对象属性。那么如何使用?这个就很简单啦,就像下面这样调用即可:

1
User user = (User) Request2Bean(request, new User());

我们通过视频来看看效果吧!

YouTube 站点

[youtube https://www.youtube.com/watch?v=Ze2_GJvrF3U&w=560&h=315]

哔哩哔哩站点

https://www.bilibili.com/video/av17431081/