最大堆的JAVA实现

最大堆是一种数据结构,使用完全二叉树来实现,每一个节点都比它的左右孩子节点要大(有点类似于金字塔,数值最大的在塔顶,数值小的在塔底,高度越高,数值越大)

这种结构最适合做什么呢?它,最适合于在一坨数据中找最大值。为甚?大家想想,既然在任何时候塔顶的数都是最大的,这不就意味着我们在任何时候都可以直接抓取最大值了吗,完事了塔顶又是最大值,我们又一次拥有了抓取最大值的机会…如此循环反复,取之不尽用之不竭,子子孙孙无穷无尽也。

堆是怎么实现的呢?

堆的创建

一般来说,堆的物理数据结构是数组(毕竟堆使用了完全二叉树,数组的最经济最划算的选择,根据孩子找父母或是根据父母找孩子都异常简单与方便),数组的第一个元素被放弃使用,因为我们使用child / 2的公式找父母,使用parent * 2parent * 2 + 1找左右孩子。

我们首先将所有元素存入数组中,之后从最后一个非叶子节点开始进行调整(如果最大那个孩子比它大,就将它与那个孩子交换,交换后也许它又比新孩子小,就又要调整…如此循环不断),直到调整至根节点。

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
// 堆的创建
private void create(List<Integer> data) {
if (!this.data.isEmpty()) {
this.data.clear();
}

// 舍弃第一个数组元素
this.data.add(0);

// 新元素全部加入数组
this.data.addAll(data);

int parent, child;
// 从最后一个非叶子节点(size / 2)开始调整,直到根
for (int i = (this.data.size() - 1) / 2; i >= 1; i--) {
parent = i;

// 不断地与大于parent的child交换,直到不可交换为止
while (parent <= (this.data.size() - 1) / 2) {
child = parent * 2;

// 右孩子更大
if (child + 1 <= (this.data.size() - 1) && this.data.get(child) < this.data.get(child + 1)) {
child = child + 1;
}

// parent与child交换,并查看是否要继续调整
if (this.data.get(parent) < this.data.get(child)) {
swap(parent, child);
parent = child;
} else {
// parent与child之间已经符合要求,无须交换
break;
}
}

}
}

从上面分析可知,平均复杂度是O(n),最差情况是O(nLogn)

往堆中插入一个数据

由于堆使用了完全二叉树,往堆中插入一个数据时,最好插入到数组尾部(插到其它地方需要移动数组,效率会被拉低),之后将该数据与父母节点比较,并相应做交换,使其逐渐地上浮到相应的位置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//  往堆中插入一个数据
private void insert(Integer element) {
this.data.add(element);

int parent, child = this.data.size() - 1;

// 从最后一个非叶子节点(size / 2)开始调整,直到根
for (int i = (this.data.size() - 1) / 2; i >= 1; ) {
parent = i;
if (this.data.get(parent) < this.data.get(child)) {
swap(parent, child);
child = parent;
i = i / 2;
}
else {
break;
}
}
}

时间复杂度是O(LogN)

删除堆中数组某个下标的数据

想要删除,直接将最后一个数据覆盖欲删除的数据,之后进行调整即可。

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
// 删除堆中数组某个下标的数据
private void delete(int index) {
this.data.set(index, this.data.get(this.data.size() - 1));
this.data.remove(this.data.size() - 1);

int parent = index, child;

for (; parent >= (this.data.size() - 1) / 2; ) {
child = parent * 2;

// 没有孩子
if (child > this.data.size() - 1) {
return;

// 有左右两个孩子
} else if (child < this.data.size() - 1) {
// 右孩子更大
if (this.data.get(child) < this.data.get(child + 1))
child = child + 1;

// 只有左孩子
} else if (child == this.data.size() - 1) {

}

// 交换parent和child
swap(parent, child);

// 可能要继续下沉
parent = child;
}
}

时间复杂度是O(LogN)

装饰器(设计模式)

既不改变原有的类,也不直接继承原有的类,却能给原有的类加上新的功能,是装饰器模式所要实现的。

我有一辆死飞自行车,我希望不改变自行车的内部结构,也能让它拥有夜间照明系统,该怎么做呢?最好的做法是,直接自行车的车把上“装”一个强力的手电筒。(也可以再加上一些控制电路,与手机连通,之后就可以通过APP来控制死飞的照明系统了哦,所谓的车联网不过如此嘛-_-)

上面的“装”就是“装饰器”的意思,给死飞装饰一个手电筒,死飞不就有前大灯了嘛。

装饰器模式的原理,就如上面死飞的例子,若换到代码中,也是差不多的。

首先有个待装饰的类(Bike),它实现了IBike接口。

1
2
3
4
5
6
7
8
9
public interface IBike {
void run();
}

public class Bike implements IBike {
run() {
System.out.println("原装死飞并没有照明系统");
}
}

之后有个装饰器类(AddLight),它的内部以IBike的形式保存了待装饰类Bike,也实现了Bike的所有函数(每个函数中,AddLight除了调用Bike的同名函数外,也添加了额外的代码,也就是额外装饰上去的内容)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

public class AddLight implements IBike {

IBike iBike;

public AddLight(IBike iBike) {
super();
this.iBike = iBike;
}

@Override
public void run() {
System.out.println("在装饰器里,死飞被装饰上了车头激光大灯");
iBike.run();
System.out.println("在装饰器里,死飞被装饰上了车头激光大灯");
}

}

什么时候我们想要一辆有前灯的死飞,就将它托管给装饰器,装饰器自动地给我们装饰好带激光大灯的死飞了。

HTML引用JSP中的变量

  • 首先呢,我在浏览器里存了一个cookie,cookie的值是用户名。

  • 然后呢,我要在网页中读取cookie,并在网页上显示出来。

第一次尝试

因为我采用了SpringMVC框架,必不可免地,使用JSP语言来读取cookie:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<p>
<%
Cookie cookie = null;
Cookie[] cookies = null;

cookies = request.getCookies();
if (cookies != null) {
for (Cookie cooky : cookies) {
if (cooky.getName().equals(StudentConst.STUDENT_NAME)) {
System.out.println(cooky.getName() + " : " + cooky.getValue());
break;
}
}
} else {
System.out.println("cookie为空!!!!!");
}
%>
</p>

咱本以为,上面的代码嵌入在html页面里,那么,System.out.println(cooky.getName() + " : " + cooky.getValue())应该会在网页上输出用户名,不想运行后,用户名出现于服务器的output窗口。

我这才想起,JSP是一种服务器端的语言,它会由服务器编译并生成相应的网页,System.out.println方法自然会输出内容到服务器而不是网页。(JSP和JavaScript根本不是同一种东西)

第二次尝试

经过学习,我才知道,JSP可以定义全局变量,可以对全局变量进行修改,有特殊的语法将变量输出到html中。
经过修改,网页代码如下:

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
<p>
<%--定义JSP中的全局变量name--%>
<%!
String name;
%>

<%--寻找cookie内容并赋值给全局变量name--%>
<%
Cookie cookie = null;
Cookie[] cookies = null;

cookies = request.getCookies();
if (cookies != null) {
for (Cookie cooky : cookies) {
if (cooky.getName().equals(StudentConst.STUDENT_NAME)) {
System.out.println(cooky.getName() + " : " + cooky.getValue());
name = cooky.getValue();
break;
}
}
} else {
System.out.println("cookie为空!!!!!");
}
%>

<%--html引用全局变量name的值--%>
<%=name%>
</p>

效果:![此处输入图片的描述][1]

SpringMVC文件上传模块失败

BUG

在编写文件上传模块时,服务器报了个状态码为500的错误:Unable to process parts as no multi-part configuration has been provided,由于之前没有处理过SpringMVC的图片上传,所以也看不懂是什么玩意儿(后来我才知道,我的配置里少了一行代码,直接导致这个错误)。

背景知识

我在前端写了个表单,用来选择文件和上传:

1
2
3
4
5
<form method="post" action="<c:url value="/student/commit"/>" enctype="multipart/form-data">
<input type="text" name="name"/>
<input type="file" name="file"/>
<input type="submit"/>
</form>

在选择了文件,并按submit按钮后,前端就会将文件POST到后台服务器(路径是localhost:8080/student/commit)。

后台服务器的controller里有对应的处理方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
@RequestMapping(value = "/commit", method = RequestMethod.POST)
public String commit(@RequestParam("name") String name,
@RequestParam("file") MultipartFile file) {

logger.info(name);
try {
logger.info(file.getInputStream().toString());
} catch (IOException e) {
e.printStackTrace();
}

return "/student/commit";
}

它会打印前端传来的文件信息。

同时我还在SpringMVC的配置文件(spring-web.xml)里配置了对文件的支持:

1
2
3
4
<!--配置文件上传-->
<bean id="multipartResolver"
class="org.springframework.web.multipart.support.StandardServletMultipartResolver">
</bean>

它令SpringMVC具有了识别文件上传的功能。

问题之谜

这么配置后,我运行项目,选择文件,点击上传按钮,浏览器里显示状态码为500,内容为Unable to process parts as no multi-part configuration has been provided的错误:

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
HTTP Status 500 - Request processing failed; nested exception is org.springframework.web.multipart.MultipartException: Could not parse multipart servlet request; nested exception is java.lang.IllegalStateException: Unable to process parts as no multi-part configuration has been provided

type Exception report

message Request processing failed; nested exception is org.springframework.web.multipart.MultipartException: Could not parse multipart servlet request; nested exception is java.lang.IllegalStateException: Unable to process parts as no multi-part configuration has been provided

description The server encountered an internal error that prevented it from fulfilling this request.

exception

org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.springframework.web.multipart.MultipartException: Could not parse multipart servlet request; nested exception is java.lang.IllegalStateException: Unable to process parts as no multi-part configuration has been provided
org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:979)
org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:869)
javax.servlet.http.HttpServlet.service(HttpServlet.java:648)
org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:843)
javax.servlet.http.HttpServlet.service(HttpServlet.java:729)
org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
root cause

org.springframework.web.multipart.MultipartException: Could not parse multipart servlet request; nested exception is java.lang.IllegalStateException: Unable to process parts as no multi-part configuration has been provided
org.springframework.web.multipart.support.StandardMultipartHttpServletRequest.parseRequest(StandardMultipartHttpServletRequest.java:100)
org.springframework.web.multipart.support.StandardMultipartHttpServletRequest.<init>(StandardMultipartHttpServletRequest.java:78)
org.springframework.web.multipart.support.StandardServletMultipartResolver.resolveMultipart(StandardServletMultipartResolver.java:76)
org.springframework.web.servlet.DispatcherServlet.checkMultipart(DispatcherServlet.java:1089)
org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:928)
org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:893)
org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:967)
org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:869)
javax.servlet.http.HttpServlet.service(HttpServlet.java:648)
org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:843)
javax.servlet.http.HttpServlet.service(HttpServlet.java:729)
org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
root cause

java.lang.IllegalStateException: Unable to process parts as no multi-part configuration has been provided
org.apache.catalina.connector.Request.parseParts(Request.java:2742)
org.apache.catalina.connector.Request.getParts(Request.java:2708)
org.apache.catalina.connector.RequestFacade.getParts(RequestFacade.java:1096)
org.springframework.web.multipart.support.StandardMultipartHttpServletRequest.parseRequest(StandardMultipartHttpServletRequest.java:85)
org.springframework.web.multipart.support.StandardMultipartHttpServletRequest.<init>(StandardMultipartHttpServletRequest.java:78)
org.springframework.web.multipart.support.StandardServletMultipartResolver.resolveMultipart(StandardServletMultipartResolver.java:76)
org.springframework.web.servlet.DispatcherServlet.checkMultipart(DispatcherServlet.java:1089)
org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:928)
org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:893)
org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:967)
org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:869)
javax.servlet.http.HttpServlet.service(HttpServlet.java:648)
org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:843)
javax.servlet.http.HttpServlet.service(HttpServlet.java:729)
org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
note The full stack trace of the root cause is available in the Apache Tomcat/9.0.0.M6 logs.

Apache Tomcat/9.0.0.M6

搜寻其他人的方案,我发现我的方案并没有什么特殊的,到底问题出在哪里?

错误里的no multi-part configuration has been provided是什么意思,未提供文件上传的配置?

可我分明在spring-web.xml里加入了相应的配置。

我再仔细看,Could not parse multipart servlet request,没有servlet的配置?

谜题之解

我这才意识到,SpringMVC是依赖Servlet的,虽然SpringMVC能识别Servlet,但它底层的Servlet也许还不能识别文件上传。

于是我赶紧翻开SpringMVC官网的refference去看文档,果然,我发现了一些猫腻:

Once Servlet 3.0 multipart parsing has been enabled in one of the above mentioned ways you can add the StandardServletMultipartResolver to your Spring configuration

在配置SpringMVC的StandardServletMultipartResolver之前必须先配置Servlet,官网提供的方案有:

  1. mark the DispatcherServlet with a “multipart-config” section in web.xml(在web.xml文件中的DispatcherServlet元素里,加上一个multipart-config)
  2. with a javax.servlet.MultipartConfigElement in programmatic Servlet registration
  3. in case of a custom Servlet class possibly with a javax.servlet.annotation.MultipartConfig annotation on your Servlet class

我选择了第一种方案,打开webapp/WEB-INFO目录下的web.xml,往DispatcherServlet里加了一行

1
<multipart-config/>

再运行项目,就可以正常地上传文件了。

web.xml全部内容如下:

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
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1"
metadata-complete="true">


<!--配置DispatcherServlet-->
<servlet>
<servlet-name>my-dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

<!--让Servlet支持文件上传-->
<multipart-config/>

<!--springMVC需要加载的文件-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/spring-*.xml</param-value>
</init-param>
</servlet>

<servlet-mapping>
<servlet-name>my-dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>


</web-app>

直到最后,我才发现,解决这个BUG,只需要一行代码。

使用session和SpringMVC拦截器实现登录权限认证

在之前的博文里,我解决了前端和后台的通讯问题,也解决了Ajax取得验证结果后直接跳转到主页面的问题,在实际的使用后,我又发现了一个问题:登录后可以跳转至主页面,这很合理,但没有登录的人也可以访问主页面,这就有点尴尬了。

所以现在的问题是,如何才能阻止那些没有登录的人访问主页面呢

开门的钥匙

经过一段时间的学习,我发现了一种被称为session的东西。它由服务器生成,由服务器保存,可以用来识别出那些已经登录的人。举个例子,用户A登录后,服务器给他生成一条他自己独有的session,他每次尝试访问主页时,服务器都会检查他之前是否获得过session,如果没有session,说明他是没有登录过的人,必须将他重定向到登录页面。

所以,session是一种开门的钥匙,拥有session的人才有资格访问主页面。

SpringMVC的审核门

既然我们使用session机制可以识别已经登录的人,那么SpringMVC中有没有什么关卡,可以将那些没有钥匙的人挡在外面?

答案是:有,拦截器!拦截器可以审核URL请求,合法的就放行让服务器进行响应,不合法的就拦下来。

session的使用

在接受登录请求的控制器里,当用户的密码正确时,在代码里加入两行:

1
2
httpSession.setAttribute(StudentConst.STUDENT_EMAIL, email);
httpServletResponse.addCookie(new Cookie(StudentConst.STUDENT_EMAIL, email));

它会在服务器中为该用户生成一条键为StudentConst.STUDENT_EMAIL(该常量代码的字符串是“EMAIL”)值为email变量所指向字符串的session,同时也命令浏览器生成相应的cookie来保存session。

如此一来,登录过的用户就拥有session记录了,没登录的自然没有。

在拦截器里,我们将“是否拥有session”作为审核通过的唯一因素,就能将未登录的人挡在门外咯。

拦截器的使用

首先我们在Spring的WEB层配置文件(spring-web.xml)中声明一个拦截器的拦截范围:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<mvc:interceptors>
<mvc:interceptor>
<!--拦截以localhost:8080/student/开头的URL请求,包括localhost:8080/student/dashboard和localhost:8080/student/dashboard/detail类型-->
<mvc:mapping path="/student/**"/>
<!--排除在拦截名单外的URL请求-->
<!--首先是注册请求,这个在任何时候都不能拦截-->
<mvc:exclude-mapping path="/student/register"/>
<!--然后是登录请求-->
<mvc:exclude-mapping path="/student/login"/>
<!--下面的是一些资源文件,比如图片、CSS文件、JS文件等-->
<mvc:exclude-mapping path="/student/common/**"/>
<mvc:exclude-mapping path="/student/css/**"/>
<mvc:exclude-mapping path="/student/js/**"/>
<!--当有URL被拦截后,调用下面这个拦截器StudentSessionInterceptor来处理,StudentSessionInterceptor决定了是放行还是拦截-->
<bean class="com.ilovecl.interceptor.StudentSessionInterceptor"/>
</mvc:interceptor>

当用户请求localhost:8080/student/dashboard时就将该请求转交给StudentSessionInterceptor进行处理,该拦截器的代码如下:

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
package com.ilovecl.interceptor;

import com.ilovecl._const.StudentConst;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
* 登录验证的拦截器
*
* @author qiuyongchen
* email:qiuych3@mail2.sysu.edu.cn
* copyRight:The MIT License (MIT)
* @since 2016-06-01 20:18
*/
public class StudentSessionInterceptor implements HandlerInterceptor {
private Logger logger = LoggerFactory.getLogger(this.getClass());

@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
Object email = httpServletRequest.getSession().getAttribute(StudentConst.STUDENT_EMAIL);
if (email == null) {
logger.info("用户尚未登录,将其重定向至登录页面");
httpServletResponse.sendRedirect("/student/login");
return false;
}
return true;
}

@Override
public void postHandle(HttpServletRequest hsr, HttpServletResponse hsr1, Object o, ModelAndView mav) throws Exception {
}

@Override
public void afterCompletion(HttpServletRequest hsr, HttpServletResponse hsr1, Object o, Exception excptn) throws Exception {
}

}

拦截器里有个名为preHandle的方法,它在服务器处理URL请求之前,先审核URL请求,以便决定是否进行拦截。如果该用户没有session,那就将其重定向httpServletResponse.sendRedirect("/student/login");,并返回false表示不放行,否则返回true表示放行。

Ajax验证登录后跳转到主页面

问题

在“物业报修系统”的登录页面里,我用Ajax的POST方法给服务器提交用户邮箱和密码,服务器验证后给我返回一条JSON数据,里边有验证的结果。

一般地,在登录验证成功后,网站会自动跳转到主页面,这应该怎么实现呢?(问题可以描述为:JS是如何让页面自动跳转的呢)

方案

只要在Ajax的success函数中加入这么一条:

1
2
// ,登录验证通过,自动跳转到主页面
window.location.href = "/student/dashboard";

只要服务器验证成功,前端的JS就会自动跳转到主页面(localhost:8080/student/dashboard),非常的方便。

背景

Ajax函数的全部代码如下:

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
$.ajax({
url: "/student/login",
type: "POST",
data: {
email: a,
password: b
},
cache: !1,
timeout: 3e4,
dataType: "json",
success: function (data) {
if (data.resultInfo == "invalid user") {
$("#login-btn").removeAttr("disabled"), warnings.eq(0).text("该用户不存在").addClass("warning-password-show")
}
else if (data.resultInfo == "invalid password") {
$("#login-btn").removeAttr("disabled"), warnings.eq(1).text("密码错误").addClass("warning-password-show")
} else
// ,登录验证通过,自动跳转到主页面
window.location.href = "/student/dashboard";
// window.location.href("/student/dashboard");

var f = b.code;
0 == f ? ("undefined" != typeof mixpanel && (mixpanel.identify(a), mixpanel.people.set({$email: a})), setTimeout(function () {
window.location.href = d
}, 500)) : 1 == f && ($("#login-btn").removeAttr("disabled"), warnings.eq(1).text("Email或密码错误").addClass("warning-password-show"))
}
},
error:function () {
alert("fail\n");
$("#login-btn").removeAttr("disabled"), warnings.eq(1).text("Email或密码错误").addClass("warning-password-show")
}
});

在返回的JSON数据里,提取出验证结果,分别提示“用户不存在”和“密码错误”,若成功,就自动跳转。

用JQuery的Ajax与SpringMVC通信

在项目“物业报修系统”中,当项目进行到登录模块时,遇到了一个瓶颈:前端与后台之间的通信。系统重构后使用了前后端彻底分离的机制,采用RestFul的接口设计,前端代码中不再嵌入后端的代码,所以就不能通过JSP的表单submit方法提交用户名与密码了。所以,到底应该怎么办呢?

经过考虑和相应的学习,我决定使用JQuery中的Ajax方法,原因是它的使用非常方便和简洁。

前端

在前端的网页中,写出这么一个函数供登录按钮调用(网页需引入JQuery):

1
2
3
4
5
6
7
8
9
10
11
12
13
<script>
function myLogin() {
$.post("/student/login",
{
email: "qiuych3@mail2.sysu.edu.cn",
password: "qiuyongchen"
},
function (data) {
alert("Data: " + data.name + "\n");
});
}
;
</script>

它使用post方法,以json格式将email和password两个参数传给服务器,服务器以json格式返回一个data对象。

后台

在SpringMVC的控制器中写这么一个函数:

1
2
3
4
5
6
7
8
9
@RequestMapping(value = "/login", method = RequestMethod.POST)
public @ResponseBody Student login(String email, String password, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) {

logger.info("***************************************************************************");
logger.info("email : " + email + " password : " + password);
logger.info("***************************************************************************");

return new Student("邱永臣", "sdfsdfa");
}

SpringMVC将前端传来的email和password传给该函数。该函数的@ResponseBody注解表明函数会以json的形式返回一个student对象。

SpringMVC中自定义JS/CSS引用出现404错误的解决方案

问题

在“物业报修系统”项目里,我写前端时自定义了一些CSS文件,在JSP中引用它们。当我运行项目时,浏览器上并没有显示相应的样式,内容全部挤在一坨(我那个郁闷哪)。

分析

当我开启浏览器的开发者工具后,发现相应的CSS并没有成功下载,get出现了404错误,比如下面这条URL:

1
http://localhost:8080/student/css/bootstrap.min.css

访问这条URL时显示404错误。

我仔细想了想,似乎不太对,既然可以获取JSP文件,为什么不可以获取CSS文件呢?

我再想了想,恍然明悟,我并没有主动获取JSP文件,而是,在我输入URL后服务器才给我返回一个JSP。比如我输入:

1
http://localhost:8080/student/login

服务器返回给我的JSP文件是/WEB-INF/student/login.jsp,也就是说,访问JSP是访问RestFul API后,服务器的控制器自动给我返回JSP。现在我要明晃晃地访问CSS文件,应该要让SpringMVC给我开个门路吧?

解决

我上网一查,果不其然,我需要在spring-servlet.xml中加入相应的映射配置:

1
<mvc:resources mapping="/student/css/**" location="WEB-INF/jsp/student/css/"/>

其中location指服务器上的具体位置,而mapping则是URL上的参数(使用了Ant的通配符)。

在JSP中引用CSS

我的login.jsp放在WEB-INF/student/下,而CSS文件放在WEB-INF/student/css/下,在JSP中引用CSS我可以这么写:

1
<link href="css/my/signin.css" rel="stylesheet">

运行起来显示就正常了。

效果

(复制BootStrap的示例):

c3p0的连接池优化导致的异常

在项目“物业报修系统”里,框架是SpringMVC/Spring/MyBatis,而MyBatis在底层调用的是c3p0,我在试图优化c3p0时犯了一些错误,访问数据库时有时会超时,有时不会,超时会抛出以下异常:

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
org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.exceptions.PersistenceException:
### Error querying database. Cause: org.springframework.jdbc.CannotGetJdbcConnectionException: Could not get JDBC Connection; nested exception is java.sql.SQLException: An attempt by a client to checkout a Connection has timed out.
### The error may exist in file [C:\Users\DELL\Documents\propertyManagement\target\classes\mapper\AdminDao.xml]
### The error may involve com.ilovecl.dao.AdminDao.queryById
### The error occurred while executing a query
### Cause: org.springframework.jdbc.CannotGetJdbcConnectionException: Could not get JDBC Connection; nested exception is java.sql.SQLException: An attempt by a client to checkout a Connection has timed out.

at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:76)
at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:399)
at com.sun.proxy.$Proxy13.selectOne(Unknown Source)
at org.mybatis.spring.SqlSessionTemplate.selectOne(SqlSessionTemplate.java:165)
at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:69)
at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:53)
at com.sun.proxy.$Proxy14.queryById(Unknown Source)
at com.ilovecl.dao.AdminDaoTest.queryById(AdminDaoTest.java:30)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:483)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:73)
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:82)
at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:73)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:224)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:83)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:68)
at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:163)
at org.junit.runner.JUnitCore.run(JUnitCore.java:160)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:119)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:42)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:234)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:74)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:483)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
Caused by: org.apache.ibatis.exceptions.PersistenceException:
### Error querying database. Cause: org.springframework.jdbc.CannotGetJdbcConnectionException: Could not get JDBC Connection; nested exception is java.sql.SQLException: An attempt by a client to checkout a Connection has timed out.
### The error may exist in file [C:\Users\DELL\Documents\propertyManagement\target\classes\mapper\AdminDao.xml]
### The error may involve com.ilovecl.dao.AdminDao.queryById
### The error occurred while executing a query
### Cause: org.springframework.jdbc.CannotGetJdbcConnectionException: Could not get JDBC Connection; nested exception is java.sql.SQLException: An attempt by a client to checkout a Connection has timed out.
at org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:30)
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:122)
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:113)
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectOne(DefaultSqlSession.java:73)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:483)
at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:386)
... 39 more
Caused by: org.springframework.jdbc.CannotGetJdbcConnectionException: Could not get JDBC Connection; nested exception is java.sql.SQLException: An attempt by a client to checkout a Connection has timed out.
at org.springframework.jdbc.datasource.DataSourceUtils.getConnection(DataSourceUtils.java:80)
at org.mybatis.spring.transaction.SpringManagedTransaction.openConnection(SpringManagedTransaction.java:82)
at org.mybatis.spring.transaction.SpringManagedTransaction.getConnection(SpringManagedTransaction.java:68)
at org.apache.ibatis.executor.BaseExecutor.getConnection(BaseExecutor.java:315)
at org.apache.ibatis.executor.SimpleExecutor.prepareStatement(SimpleExecutor.java:75)
at org.apache.ibatis.executor.SimpleExecutor.doQuery(SimpleExecutor.java:61)
at org.apache.ibatis.executor.BaseExecutor.queryFromDatabase(BaseExecutor.java:303)
at org.apache.ibatis.executor.BaseExecutor.query(BaseExecutor.java:154)
at org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:102)
at org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:82)
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:120)
... 46 more
Caused by: java.sql.SQLException: An attempt by a client to checkout a Connection has timed out.
at com.mchange.v2.sql.SqlUtils.toSQLException(SqlUtils.java:106)
at com.mchange.v2.sql.SqlUtils.toSQLException(SqlUtils.java:65)
at com.mchange.v2.c3p0.impl.C3P0PooledConnectionPool.checkoutPooledConnection(C3P0PooledConnectionPool.java:527)
at com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource.getConnection(AbstractPoolBackedDataSource.java:128)
at org.springframework.jdbc.datasource.DataSourceUtils.doGetConnection(DataSourceUtils.java:111)
at org.springframework.jdbc.datasource.DataSourceUtils.getConnection(DataSourceUtils.java:77)
... 56 more
Caused by: com.mchange.v2.resourcepool.TimeoutException: A client timed out while waiting to acquire a resource from com.mchange.v2.resourcepool.BasicResourcePool@290222c1 -- timeout at awaitAvailable()
at com.mchange.v2.resourcepool.BasicResourcePool.awaitAvailable(BasicResourcePool.java:1317)
at com.mchange.v2.resourcepool.BasicResourcePool.prelimCheckoutResource(BasicResourcePool.java:557)
at com.mchange.v2.resourcepool.BasicResourcePool.checkoutResource(BasicResourcePool.java:477)
at com.mchange.v2.c3p0.impl.C3P0PooledConnectionPool.checkoutPooledConnection(C3P0PooledConnectionPool.java:525)
... 59 more

我先是检查代码逻辑,没发现哪个地方会出错,再看Spring配置里关于c3p0的部分:

1
2
3
4
5
6
7
8
9
<!--c3p0连接池的私有属性-->
<property name="maxPoolSize" value="4000"/>
<property name="minPoolSize" value="1"/>
<property name="autoCommitOnClose" value="false"/>
<!--获取连接超时时间-->
<property name="checkoutTimeout" value="1024"/>
<!--获取失败的重试次数-->
<property name="acquireRetryAttempts" value="30"/>

咋一眼看过去,找不到错误,我这时想起我的数据库放在阿里云上,我想:“会不会是运行数据库的机器配置不好,没法及时响应我请求?”。于是我加了一条initialPoolSize优化选项,在建立与数据库的连接时,试图建100个(我想,建立这么多连接,怎么地也会成功一个吧),配置如下:

1
2
3
4
5
6
7
8
9
10
11
<!--试图进行的优化-->
<property name="initialPoolSize" value="100"/>

<!--c3p0连接池的私有属性-->
<property name="maxPoolSize" value="4000"/>
<property name="minPoolSize" value="1"/>
<property name="autoCommitOnClose" value="false"/>
<!--获取连接超时时间-->
<property name="checkoutTimeout" value="1024"/>
<!--获取失败的重试次数-->
<property name="acquireRetryAttempts" value="30"/>

优化之后,虽然效果好了一些,但偶尔依旧会有超时的情况,于是我再仔细研究代码和配置,直到我发现了上面那些配置里有一条:

1
<property name="checkoutTimeout" value="1024"/>

将获取连接的超时时间设置为1024毫秒,在1秒多一点的时间里如果没获取到连接就返回失败并抛出异常。

此时,我突然反应过来:远程机器那么渣,在10秒内能和远在千里之外的它建立连接我就该嗨皮了,居然还要求1秒就建立成功,失败是正常的,能成功都是运气。

于是我将它注释掉,也注释掉之前加上去的initialPoolSize优化,如下:

1
2
3
4
5
6
7
8
9
<!--<property name="initialPoolSize" value="100"/>-->
<!--c3p0连接池的私有属性-->
<property name="maxPoolSize" value="4000"/>
<property name="minPoolSize" value="1"/>
<property name="autoCommitOnClose" value="false"/>
<!--获取连接超时时间-->
<!--<property name="checkoutTimeout" value="1024"/>-->
<!--获取失败的重试次数-->
<property name="acquireRetryAttempts" value="30"/>

如此,再也不会超时了。

JUnit4整合Spring出现的包不兼容问题

在创建“物业报修系统”项目时,我第一次尝试使用了Maven来创建(基于InteliJ IDEA),在配置Spring的依赖时,手动逐个import各个包,结果在启动JUnit4单元测试时出现以下错误:

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
java.lang.NoSuchMethodError: org.springframework.core.annotation.AnnotationUtils.isInJavaLangAnnotationPackage(Ljava/lang/annotation/Annotation;)Z

at org.springframework.test.util.MetaAnnotationUtils.findAnnotationDescriptor(MetaAnnotationUtils.java:117)
at org.springframework.test.util.MetaAnnotationUtils.findAnnotationDescriptor(MetaAnnotationUtils.java:88)
at org.springframework.test.context.support.ActiveProfilesUtils.resolveActiveProfiles(ActiveProfilesUtils.java:80)
at org.springframework.test.context.support.AbstractTestContextBootstrapper.buildMergedContextConfiguration(AbstractTestContextBootstrapper.java:367)
at org.springframework.test.context.support.AbstractTestContextBootstrapper.buildMergedContextConfiguration(AbstractTestContextBootstrapper.java:305)
at org.springframework.test.context.DefaultTestContext.<init>(DefaultTestContext.java:67)
at org.springframework.test.context.TestContextManager.<init>(TestContextManager.java:103)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTestContextManager(SpringJUnit4ClassRunner.java:124)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.<init>(SpringJUnit4ClassRunner.java:115)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:408)
at org.junit.internal.builders.AnnotatedBuilder.buildRunner(AnnotatedBuilder.java:29)
at org.junit.internal.builders.AnnotatedBuilder.runnerForClass(AnnotatedBuilder.java:21)
at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:59)
at org.junit.internal.builders.AllDefaultPossibilitiesBuilder.runnerForClass(AllDefaultPossibilitiesBuilder.java:26)
at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:59)
at org.junit.internal.requests.ClassRequest.getRunner(ClassRequest.java:26)
at org.junit.internal.requests.FilterRequest.getRunner(FilterRequest.java:31)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:98)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:42)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:234)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:74)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:483)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)


Process finished with exit code -1

我调试了半天,没发现代码里有错误,于是谷歌一下,有人说是包不兼容云云,我心里真是狗粮咧(那么多包都认识我,但我不认识它们,要在几十个包里解决包不兼容问题,这也太难了吧-_-)。

于是乎,我重新创建了一个项目,一次性编写完Spring的依赖,让InteliJ IDEA用Maven的方式帮我引入各个包,再次启动JUnit4的单元测试,得到了测试通过的提示…