spring study notes advanced

概述

在之前简单的登录注册实现的基础上,接下来会尝试较完整地实现一个个人博客网站。

intellij idea上spring的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
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"file:src/main/webapp/WEB-INF/mvc-dispatcher-servlet.xml"})
public class UserRepositoryTest {
@Autowired
private UserRepository userRepository;
private UsersEntity usersEntity=new UsersEntity();
@Test
public void findByUsername() throws Exception {
usersEntity=userRepository.findByUsername("abc");
System.out.println(usersEntity.getUsername());
}
@Test
// @Transactional
// @Rollback
public void addUser() throws Exception{
UsersEntity newUser = new UsersEntity();
newUser.setBlognum(0);
newUser.setPassword("456");
newUser.setUsername("def");
userRepository.saveAndFlush(newUser);
}
}

代码说明

  • @RunWith: 让代码段运行与spring测试环境。
  • @ContextConfiguration: 用来指定加载的Spring配置文件的位置,会加载默认配置文件。该注解有以下两个常用的属性:locations:可以通过该属性手工指定 Spring 配置文件所在的位置,可以指定一个或多个 Spring 配置文件用,分开。如下所示: @ContextConfiguration(locations={“aa/aa.xml”,” aa/bb.xml”}) inheritLocations:是否要继承父测试用例类中的 Spring 配置文件,默认为 true。前缀若为“classpath:”则搜索默认路径下的配置文件,由于我的配置文件不在默认路径下,所以前缀为“file:”
  • @Transactional 和 @Rollback: 分别用于 开启事务 以及 事务回滚。在测试中加上这两个标签,则测试中对数据库所做的操作将会操作一次后默认回滚。

2017-07-21 更新

spring和spring MVC的区别

简而言之,spring是容器,而springMVC是框架。打个比方,打篮球的时候可以在水泥地可以在木质板地,这就是容器,而在水泥地或木质板地上打篮球或羽毛球,这就是框架。(框架部分可能比喻不太恰当)

spring

spring有两个主要的概念:IoC(Inverse of Control), AOP(Aspect Oriented Programming)

spring IoC

我看百度百科上说,依赖注入是其实现方式之一。

spring AOP

面向切面编程,我没用到。

springMVC

springMVC Control Flow

其控制流程如上所示。这就构成了MVC的框架结构。

myblog的简单实现

  • 前端用的vue.js框架,用了点jsp。后台是spring+springMVC+Hibernate,数据库操作的实现用了spring data接口。

  • 因为只是简单实现,我将业务逻辑都放在Controller里了,所以在接下来的具体介绍,我按Model(数据库操作),View(前端页面操作),Control(具体业务逻辑及数据转发响应)来说明(并不是严格按照真正的MVC概念来介绍)。

  • 网站仅实现了用户登录,及登录用户对博文增删改查的简单操作。

Model

数据库配置参加上一篇文章,这里我们介绍具体代码部分。

1
2
3
4
5
6
7
8
9
@Repository
public interface BlogRepository extends JpaRepository<BlogsEntity, Integer> {
...
@Query(value = "select title from blogs LIMIT ?1,?2", nativeQuery = true)
List<String> findByBlogLimit(int param1, int param2);
...
}

代码已经很清晰了,带上标签@Query使用原生的mysql语句进行数据库操作。

顺带一提,这里的LIMIT可以返回指定序数的数据。

View

这个部分我使用了最近比较流行的vue.js框架,基本介绍我看心情更新博文。这里我先截取几个我认为比较好用的功能来说明下。

登录部分代码

这里拿登录部分的代码做例子。

以下是html部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<div id="sidebar" class="sidebar" v-model="isLogin">
<div v-if="!isLogin">
<div class="form-group">
<input type="text" placeholder="username" style="color:#000000" v-model="user.username"><br>
</div>
<div class="form-group">
<input type="password" placeholder="password" style="color:#000000" v-model="user.password"><br>
</div>
<div class="form-group">
<button style="color:#000000" @click="login">login</button>
</div>
</div>
<div v-else>
<h3>welcome!</h3><br>
<form action="/newblog" method="get" style="color:#000000">
<button @click="logout">logout</button>
<button type="submit">new blog</button>
</form>
</div>
</div>

再看看对应的js代码:

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
var sideBar = new Vue({
el: '#sidebar',
data: {
isLogin: false,
user: {
username: '',
password: ''
}
},
mounted: function () {
this.islogin()
},
methods: {
islogin: function () {
this.$http.get("/islogin").then(function (response) {
this.isLogin = response.data.islogin;
}, function (response) {
})
},
login: function () {
this.$http.get("/login", {params: {'username': this.user.username, 'password': this.user.password}})
.then(function (response) {
this.isLogin = response.data.islogin;
this.isLogin == true ? alert("welcome!") : alert("login failed!");
}, function (response) {
alert(response.data.islogin + "login failed!" + this.user.username + this.user.password)
})
},
logout: function () {
this.$http.get("/logout").then(function (response) {
alert("logout success!");
this.isLogin = false;
}, function (response) {
alert("network error!")
})
},
newBlog: function () {
this.$http.get("/newblog").then(function (response) {
alert(response.status)
}, function (response) {
alert(response.status)
})
}
}
})

代码说明

首先,在js代码中新建Vue对象,Vue对象中主要有三个部分:el,data,methods。

  • el:将对象与html页面中的某一个组件绑定。
  • data:Vue对象的数据部分。
  • methods:在方法里面定义方法函数。

除了上面这三个,还有created,mounted之类的钩子函数以及watch等。我用到了mounted这个钩子函数,是要在页面初始化时同时向服务器取一些数据动态地显示在页面上。

其次,上面向后台动态交换数据使用了vue-resource库。具体使用参见如下代码:

1
2
3
4
5
6
this.$http.get("/login", {params: {'username': this.user.username, 'password': this.user.password}})
.then(function (response) {
//这里是请求成功后的代码,可以从response中取得返回数据
}, function (response) {
//请求失败的代码
})

由上可见,”/login”是请求的url地址,params是附带的参数。

最后在html中用到的vue.js标签有:v-if,v-else,v-model,v-on。

  • v-if和v-else:就是在html页面做条件判断显示。
  • v-model:用于将页面组件与数据做动态绑定。
  • v-on: v-on:click=”” 缩写为 @click=””, 事件响应函数。

业务逻辑几点说明

  • 加载页面时,由钩子函数mounted向服务器查询登录状态,若已经登录将isLogin置为true,反之false。
  • html页面相应组件中由v-if和v-else对isLogin进行条件判断并选择渲染对应<div>组件显示给用户。
  • <div>组件通过v-model="isLogin"与Vue对象中的isLogin进行双向绑定并实时感应该值变化并作出相应改变。
  • 其他的话,就是正常的登录流程,这里不再详述。

最后还有几点说明

  • 用到的都是vue.js最基本的功能,更高级点的像自定义组件,vue-router,vuex都没有用到。
  • 方便起见,我用到的全部request请求都是get方式的。
  • 官方表示不再更新维护vue-resource,并给我们推荐了axios。
  • 在点击博文标题查看文章具体内容的时候,其实是向后台发送一个链接请求,后台收到请求,同时返回一个空的页面和博文标题及内容的数据。我在做这个处理的时候不知道如何用vue.js接收这个数据,卡了好久,最后只好用jsp页面实现这一块功能。jsp页面可以用request来获取后台返回ModelAndView中附带的数据。
  • 分页功能借鉴网上某位博主的,但是页面关了之后就找不到了>.<,见谅,然后我会找时间把这一功能实现分享下。

Control

我在控制器中包含了全部的业务逻辑处理。但都是些基本操作(上一篇都能找到用法)。以下就提几点上一篇没提到的用法。

后台返回json数据

方式有好几种,我这边就介绍我用的这种。

  • 首先要配置相关的环境。
  • 然后在方法上边加上@ResponseBody标签。
  • 如果只是单纯返回数据,可以新建Map<String,Object> modelMap对象,将数据put到Map里面,最后返回,会自动解析为json格式的数据。
  • 如果同时返回页面和数据,可以新建ModelAndView对象,.setViewName()方法设置返回的页面,.addObject()方法添加返回的数据。

保存用户登录状态

这个东西卡了我好久好久。最后我用了@SessionAttributes配合@ModelAttribute两个标签,将用户登录状态保存到session中。具体用法如下。

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
@Controller
@SessionAttributes(value = "user")
public class UserController {
@Autowired
private UserRepository userRepository;
@ModelAttribute("user")
public UsersEntity initUsersEntity() {
UsersEntity usersEntity = new UsersEntity();
return usersEntity;
}
@RequestMapping(value = "/login", method = RequestMethod.GET)
@ResponseBody
public Map<String, Object> login(@RequestParam("username") String username,
@RequestParam("password") String password,
HttpSession httpSession,
@ModelAttribute("user") UsersEntity curUser) {
Map<String, Object> modelMap = new HashMap<>();
UsersEntity usersEntity = userRepository.findByUsername(username);
if (usersEntity == null || !password.equals(usersEntity.getPassword())) {
modelMap.put("islogin", false);
} else {
modelMap.put("islogin", true);
curUser.setUsername(username);
curUser.setPassword(password);
}
return modelMap;
}
...
}

代码说明:

  • @SessionAttributes标签只能放在类上,指定对象名value="user"
  • 类中用public UsersEntity initUsersEntity()函数对user对象进行初始化,并在该函数上加上@ModelAttribute标注。
  • 在函数中使用时,使用@ModelAttribute标签在参数中引入。引入的该对象是类,则在函数中对类的数据进行修改也会直接覆盖存在session中的该类对象。
  • 一开始只想用boolean对象存入session中,但是如上引入函数,却无法修改其值,我想可能是@SessionAttributes标签里需要指明数据类型吧(我没试过)。
  • 需要在函数参数中加入HttpSession对象,不然浏览器那边就报错了。不知道什么原因。
  • 需要清空登录状态时,参数引入SessionStatus对象,使用SessionStatus.setComplete()方法,清空session中的数据即可。

最后再说几句

  • 整个网站我只实现了最基本的功能,用到的框架等也只使用了最基本的功能。进阶的话,需要再系统地学习框架知识及原理,规范编码格式、目录结构等,再参加参加大型的项目研发才行。
  • 整个网站的实现并没有具体的项目参考,都是按照我自己的想法设计并实现的。但是具体的实现知识细节参考了很多百度谷歌上的东西,由于东西零碎且没有保存相关网站,因此这里就不列出参考链接了。
  • vue.js和javascript是好东西啊。我以前基本没接触过,稍微看看也能拿来写(渣)项目了,难怪那么多人学。
  • 注意! 注意! 注意! 重要的事情说三遍。我写这个网站的时候,吃了很多环境配置的亏。虽然使用的maven环境,通过xml导入相关依赖的包。但是我之前参考的那个环境配置,它导入的包就很有问题,导致我编写代码时候有些功能思来想去找不到问题所在,最后发现是导包的错。!!!
  • 网站整体的逻辑及实现都是很简单的但是就是在有些细节方面卡了好久百度不到谷歌不到这时候就好希望有个人能教我就好了!!
  • 网站页面设计使用了RapidWeaver 7,这个软件还不错,提供了框架和样式,但是不够灵活,不能自由地加入按钮啊之类的。我还试过AxureRP 8,这个相对就灵活多了,但是没有提供基本的框架样式(或者是收费的?没注意),就放弃了。
  • 网站还有很多BUG之类的,因为只是当做兴趣学习写的,就不管了。:P

我的github地址

以上