引言

在这里记录一下在初学SpringMVC中遇到的一些问题或编码习惯。强调一下,这主要是一些个人习惯,不是教程,更不适合初学者看。现在的新开始的网站基本都会前后分离,Web后台的开发重心基本都转向了API开发,所以我就在这方面死磕了2333

Controller类注解

API控制器类的注解
1
2
3
4
5
@Controller
@RequestMapping(value = "/api/main/sub",
produces = { "application/json;charset=UTF-8" },
method = RequestMethod.POST)
@ResponseBody

@Controller

用来说明这是一个控制器,需要在SpringMVC的配置文件中指定自动扫描
指定base-package之后,该包及下面的所有子孙包都会被自动扫描

1
<context:component-scan base-package="com.xinsane.controller"/>

@RequestMapping.value

指定该控制器的基本路径,我一般习惯以api开头,这样完整路径为:/api/{模块}/{控制器}/{方法},没错,我可能ThinkPHP学傻了,但是不得不承认,这种设计方式在很多时候还是很不错的

@RequestMapping.produces

这个属性是用来指定返回数据的格式的,开发API的时候一般是返回JSON,所以这里指定为”application/json;charset=UTF-8”

@RequestMapping.method

我习惯于用POST来请求所有API,所以这里设置成了POST

@RequestBody的使用和请求数据接收器

@RequestBody会自动解析请求体的Body内容到指定的变量,这里是一个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public String login(@RequestBody RequestData data) {
if (data.code == null || data.code.isEmpty())
return error(101, "缺少参数");
TokenTransfer transfer = userService.loginByWxCode(data.code);
if (transfer.getError() != 0)
return error(transfer.getError(), transfer.getMsg());
JsonObject obj = new JsonObject();
obj.addProperty("error", 0);
obj.addProperty("token", transfer.getToken());
return obj.toString();
}
static class RequestData extends User {
String code; // 小程序传来的登陆code
String token; // 小程序传来的用户token,用于识别用户
public void setCode(String code) {
this.code = code;
}
public void setToken(String token) {
this.token = token;
}
}

在这段代码中,User是一个pojo实体类,我基于它派生了一个请求数据类,并给它加上@RequestBody注解,前端传来的数据会解析到这个类中。

一般在一个控制器中,像新增数据、修改数据时一般都会用到对应的实体类中的字段,同时还会有其他字段,比如识别用户的token,小程序登陆是要用的code等,所以基于对应实体类派生一个专用的数据接收器还是挺妥的。这样做还有一个好处是,因为是基于pojo派生的,可以直接传递这个派生类到Service层,然后Service用基类pojo类接收(为什么不直接接收派生类?因为这样会破坏不同层之间的独立性,不便于维护,详见下一节)。

值得一提的是,在同一个控制器中,虽然有很多方法,但不必为每个方法单独派生数据接收器,实体类中有的字段可以直接用,没有的字段全部写入派生类中,SpringMVC不会因为一个你有的属性前端没传而产生不良后果。

这里我使用了一个名词:数据接收器,这不是专业说法,仅仅是为了叙述方便引入的

Controller层与Service层之间的数据传输

注:这里只讨论通过方法的参数列表和返回值来传递数据

在传输数据方面,我习惯遵循一个原则:尽可能降低不同层之间的耦合程度。怎么说呢,举个例子,上一节创建的数据接收器是一个控制器的内部类,虽然只要声明为public也是可以在Service层引用的,但是一旦这么做了,对应的Controller和Service的耦合程度就会大大提高,设想如果另一个控制器想要调用同一个Service方法,还得找一个不相关的Controller下面的内部类,喵喵喵???

Controller -> Service

从Controller传数据到Service其实很容易,因为Controller调用的Service的方法,需要多少参数直接写在参数列表就好了。这里有一个Service方法设计的基本原则:方法参数不宜过长。很容易理解,如果Service提供一个有十几个甚至几十个参数的方法,你能记住参数的含义和顺序吗?网上大多推荐用一个包装类,但其实大多数情况下只需要一个对应实体类和其他的少数参数就足够了。上一节基于实体类派生了一个数据接收器来接收前端数据,在这里正好用上,把数据接收器直接传入Service,在Service用基类接收,然后其他参数以参数列表传入。如果遇到除了实体类还有一大堆参数的,就要考虑提取一个公用包装器了,这种情况我暂时还没遇到,不再赘述。

Service -> Controller

但是从Service返回数据就不那么方便了,如果Service要返回的东西很简单,简单到可以用一个基本类型或公用实体来表示的话,那没什么可说的,直接返回就好了。但如果要返回很复杂的东西,就不好办了,一方面Java中方法只能返回一个值,另一方面又不能让不同层之间耦合太过于严重。其实说难也不难解决,前面Controller传数据给Service时,不能传递自己类下面的内部类给Service,是因为一个Service可能会被多个Controller调用,但细细来考虑Controller和Service之间的关系,会发现总是由Service来提供服务,然后Controller来调用,我们也是一直这样做的,不可能出现Service调用Controller的情况。既然如此,在Controller使用Service的服务时,返回一个对应Service的专用返回数据包装器是合理的,“你要使用我提供的服务,就得遵守我的规则”。

引入Transfer

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
// 基本接口,大多数情况下Controller只需要知道Service执行的有没有错误,有的话错误代号和错误信息是什么
public interface Transfer {
int getError();
String getMsg();
}

// 基本实现
public class BaseTransfer implements Transfer {
private int error = 0;
private String msg;
public int getError() {
return error;
}
public BaseTransfer setError(int error) {
this.error = error;
return this;
}
public String getMsg() {
return msg;
}
public BaseTransfer setMsg(String msg) {
this.msg = msg;
return this;
}
}

// 如果还需要更多的信息,就需要自定义一个类来实现基本接口了
public class TokenTransfer implements Transfer {
private int error = 0;
private String token;
private String msg;
public int getError() {
return error;
}
public TokenTransfer setError(int error) {
this.error = error;
return this;
}
public String getToken() {
return token;
}
public TokenTransfer setToken(String token) {
this.token = token;
return this;
}
public String getMsg() {
return msg;
}
public TokenTransfer setMsg(String msg) {
this.msg = msg;
return this;
}
}

至此,Controller与Service两层之间的数据传输问题算是解决了。