年前入手Spring boot in action一书,因之前读过craig walls 的 Spring in action,所以Spring boot上手容易些,其实这些framework万变不离其宗,核心思想不变只是越来越对developer 友好了而已,Spring boot的新特性应该就是起步依赖(spring-boot-starter-*)和自动配置(auto-configuration)了。就书中的例子总结一下,也是为了加深自己的印象:
首先介绍这两个新特性:
- 起步依赖:我是在maven中导入包的,dependency中:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
起步依赖规范是spring-boot-starter-*,后边加你想要的导入类型,比如spring-boot-starter-web;这个dependency就是导入构建web项目的所有依赖包,比如内置tomcat等,所以起步依赖的意思就是导入一个开始的依赖,它会向下依赖将所有相关的包导入。
- 自动配置:顾名思义,减少了你的配置文件的撰写,从而使你专注于项目的开发,开启自动配置一般是在main类中class名字上加@springbootapplication注解,其包括三个子注解(@configuration:注明其为配置类;@componentscan:自动扫描该类及同级目录或子目录下的文件;@enbaleautoconfiguration:新特性之重,它开启了spring boot的自动配置)
见代码及说明:
1、project结构:https://start.spring.io或者用spring tool suite IDE可以结构化的开始项目的构建,结构如下:
2、Book实体类:领域模型,该项目主要操作的实体类
package readinglist;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class Book {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String reader;
private String isbn;
private String title;
private String author;
private String description;
public Long getId(){
return id;
}
public void setId(Long id){
this.id = id;
}
public String getReader(){
return reader;
}
public void setReader(String reader){
this.reader = reader;
}
public String getIsbn(){
return isbn;
}
public void setIsbn(String isbn){
this.isbn = isbn;
}
public String getTitle(){
return title;
}
public void setTitle(String title){
this.title = title;
}
public String getAuthor(){
return author;
}
public void setAuthor(String author){
this.author = author;
}
public String getDescription(){
return description;
}
public void setDescription(String description){
this.description = description;
}
}
3、ReadingListApplication.class : 开启自动配置、自动扫面功能,并启动项目:main method
package readinglist;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication //自动扫描、自动配置
public class ReadingListApplication {
public static void main(String[] args) { //以该配置类为主启动项目
SpringApplication.run(ReadingListApplication.class, args);
}
}
4、ReadingListController.class: spring mvc 中的controller层,接受请求并mapping到相关方法上处理,涉及到数据库的crud操作,所以自动注入一个repository bean
package readinglist;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
@RequestMapping("/readingList")
public class ReadingListController {
private ReadingListRepository readingListRepository;
@Autowired //自动注入一个repository,其为jpaRepository interface的一个实现,interface稍后给出
public ReadingListController(ReadingListRepository readingListRepository){
this.readingListRepository = readingListRepository;
}
@RequestMapping(value="/{reader}", method=RequestMethod.GET)
public String readersBooks(@PathVariable("reader") String reader, Model model){
List<Book> readingList = readingListRepository.findByReader(reader);
if(readingList != null){
model.addAttribute("books", readingList);
}
return "readingList"; //返回一个该名称的view,稍后给出
}
@RequestMapping(value="/{reader}", method=RequestMethod.POST)
public String addToReadingList(@PathVariable("reader") String reader, Book book){
book.setReader(reader);
readingListRepository.save(book);
return "redirect:/readingList/{reader}";
}
}
5、ReadingListRepository.class 继承自JPARepository的一个接口文件,只定义crud操作的方法名称,其实现交给spring data实现
package readinglist;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ReadingListRepository extends JpaRepository<Book, Long>{
List<Book> findByReader(String reader);
}
6、视图文件readingList.html 其为thymeleaf,有th:标签来处理传过来的model,其路径为src/main/resources/templates/,spring boot会在此文件中找寻相对应的视图,相当于spring中的视图解析器定义的找寻路径
<!DOCTYPE html>
<html>
<head>
<title>Reading List</title>
<link rel="stylesheet" th:href="@{/style.css}"></link>
</head>
<body>
<h2>your reading list</h2>
<div th:unless="${#lists.isEmpty(books)}">
<dl th:each="book : ${books}">
<dt class="bookHeadline">
<span th:text="${book.title}">Title</span>
<span th:text="${book.author}">Author</span>
(ISBN: <span th:text="${book.isbn}">ISBN</span>
</dt>
<dd class="bookDescription">
<span th:if="${book.desription}" th:text="${book.description}">Description</span>
<span th:if="${book.description eq null}">No description available</span>
</dd>
</dl>
</div>
<hr/>
<h3>add a book</h3>
<form method="POST">
<lable for="title">Title:</lable>
<input type="text" name="title" size="50"></input><br/>
<lable for="author">Author:</lable>
<input type="text" name="author" size="50"></input><br/>
<lable for="isbn">ISBN:</lable>
<input type="text" name="isbn" size="15"></input><br/>
<lable for="description">Description:</lable><br/>
<textarea name="description" cols="80" rows="5"></textarea><br/>
<input type="submit" />
</form>
</body>
</html>
当然还有一个style.css在src/main/resources/static下,改路径存放一些css文件、图片等静态资源
7、启动项目,我是在STS中开发的,所以可以选中项目,run–>run as–>spring boot app 或者在eclipse中的ReadingListApplication.java中run as a java application
8、pom.xml文件:数据库选用的是in-memory database H2 , 视图选择的是thyme leaf,起步依赖的好处就在这了,不需要太多的依赖,导入这几个关键的依赖即可,其余相关的会随之依赖导入,然后自动配置作用就来了,会根据这些jar包自动配置相关的配置,比如配置数据库:因为classpath路径下有H2,则自动配置就会将其作为持久层,在spring data jpa的帮助下建立起项目到持久层的一个连接及事务管理。并不需要你显示的去配置。当然有其优势必有其劣势,人为可控操作少了,那么不可控的风险也就大了
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.github.linfujian</groupId> <artifactId>readinglist</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>Reading List</name> <description>Reading List Demo</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.1.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
———————————————————–更新线
起初只是想用内置的数据库和tomcat在IDE sts上自己玩玩,craig walls的spring boot in action 一书后边介绍了把项目部署到云服务器上,考虑到外部访问project需要一个入口(之前是人工添加url:/readingList/fujian)所以接下来总结一下再添加一个登陆验证功能模块并把项目部署到免费60天的PWS上:
项目结构如下图:
1、添加登录模块:
1.1 LoginController.class 登陆控制模块 拦截“/”并映射到login.html,登陆信息准确则重定向到“redirect:/readingList/” + reader.getUsername()”该请求在之前的ReadinglistController拦截并处理
package readinglist.login;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import readinglist.reader.Reader;
import readinglist.reader.ReaderRepository;
@Controller
@RequestMapping(value="/")
public class LoginController {
@Autowired
ReaderRepository readerRepository;
@RequestMapping(method=RequestMethod.GET)
public String login() {
return "login";
}
@RequestMapping(method=RequestMethod.POST)
public String loginResult(Reader reader, Model model){
String result;
if(readerRepository.findOne(reader.getUsername()).getPassword().equals(reader.getPassword())) {
//result = "redirect:/readingList/" + reader.getUsername();
result = "redirect:/readingList/" + reader.getUsername();
}
else {
model.addAttribute("loginName", reader.getUsername());
result = "redirect:/";
}
return result;
}
}
1.2 登陆需要验证后台数据库里的reader信息,故需要一个Reader实体和JPARepository:
1.2.1 Reader.java实体:
package readinglist.reader;
import java.util.Arrays;
import java.util.Collection;
import javax.persistence.Entity;
import javax.persistence.Id;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
@Entity
public class Reader implements UserDetails{
/** * 实现了UserDetails是原本想研究spring security的内容,不展开所以这里略过,可以不用实现直接定义一个POJO的Reader */
private static final long serialVersionUID = 1L;
@Id
private String username;
private String fullname;
private String password;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
// TODO Auto-generated method stub
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getFullname() {
return fullname;
}
public void setFullname(String fullname) {
this.fullname = fullname;
}
public Collection<? extends GrantedAuthority> getAuthorities() { //security的内容,这里不做讲解,忽略
return Arrays.asList(new SimpleGrantedAuthority("READER"));
}
public boolean isAccountNonExpired() { //security的内容,这里不做讲解,忽略
return true;
}
public boolean isAccountNonLocked() {
// security的内容,这里不做讲解,忽略
return true;
}
public boolean isCredentialsNonExpired() {
//security的内容,这里不做讲解,忽略
return true;
}
public boolean isEnabled() {
//security的内容,这里不做讲解,忽略
return true;
}
}
1.2.2 定义一个JPARepository(ReaderRepository.interface)操作数据库: 之所以喜欢spring-data-jpa就是spring-data把具体的操作步骤替你解决了,所以你只需要定义一个只有方法签名的repository接口,它会替你实现
package readinglist.reader;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ReaderRepository extends JpaRepository<Reader, String>{
}
1.3 configuration package里的java文件是研究spring security的东西,现在玩的不是很深入,这里略过
1.4 将打成war包的项目上传到服务器(不管是应用服务器还是云服务器)都要启动项目,在之前的栗子中只需启动ReadinglistApplication.java里的main method,但部署到服务器上就不能在IDE里每次run 这个main方法了,所以要添加一个SpringBootServletInitializer,这是个重点,目的是部署项目后可以自行启动,本例是继承了该类的ReadingListServletInitializer .java,并将ReadinglistApplication.class(项目的启动类,包括了配置和启动)作为参数传给它:
package readinglist;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.support.SpringBootServletInitializer;
public class ReadingListServletInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder){
return builder.sources(ReadinglistApplication.class);
}
}
1.5 当然添加一个login.html
<!DOCTYPE html>
<html>
<head>
<title>login your details</title>
<link rel="stylesheet" th:href="@{/style.css}"></link>
</head>
<body>
<form method="POST">
<label>UserName: </label>
<input type="text" name="username" size="50"></input><br/>
<label>FullName: </label>
<input type="text" name="fullname" size="50"></input><br/>
<label>PassWord: </label>
<input type="password" name="password" size="50"></input><br/>
<input type="submit" />
</form>
</body>
</html>
1.6 打包
不知为何,用STS构建的spring boot 项目无法正常export为web项目,导出后lib缺少jar文件,这里用mven构建:
1.6.1
STS和eclipse自带内置的maven,但由于我需要使用maven的命令行功能,又苦于找不到内置的maven安装路径添加到环境变量,所以我用了外置的maven,下载–>安装到路径–>将路径添加到环境变量的path下–>cmd命令行就能用maven的功能了.
1.6.2
cmd命令行下cd到readinglist项目带pom.xml的目录下:maven的打包命令 mvn package 需要在存在pom.xml的路径下打包
最后成功后会在项目readinglist目录下的target文件夹中有一个项目的war包
这里容易出现一个问题就是提示因缺少web.xml文件而打包失败,解决办法是在pom.xml中将failOnMissingWebXml改为false
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
</plugins>
</build>
1.7部署到云服务器:
好像国内没被墙掉………哎…..大家都懂的
这里用cloud foundry,pivotal公司的产品,即Paas。旗下托管的PWS(pivotal web service)就是一个cloud foundry。注册账号可以买免费使用60天,存储为1G 内存 + 1G硬盘,对于实验性的小项目足够了,注册后下载cf tool用于本地上传项目文件,
连接云端及上传项目参考官方命令
连接你云端,并输入你注册时的email和密码
$ cf login -a api.run.pivotal.io
上传你的war包 第一个参数是上传到云服务器后应用的名称,第二个参数是要上传的war包路径
$ cf push linfujian-readinglist -p /target/readinglist-0.0.1-SNAPSHOT
ok后登陆你的PWS端就可以看到正在running的应用了
这里注意的是在本地玩时,数据库是用的内置的H2,但内存数据库的特征就是数据只保存在内存中,应用关闭后之前的数据就没有了,所以这里要改一下数据库配置,在PWS的Maketplace中选择mySql并绑定到我的应用,具体dataSource的配置就不需管了,PWS会做这一切……
当然云端提供的database我们需要在其中创建两个table:reader 和 book 对应项目中的Reader和Book实体类
这里就需要用第三方jdbc工具(我用的DbVisualizer 9.5.6)去操作云端的数据库了:
1、点击manage:
2、Allow后到如下页面,点击database名称:
3、在如下页面上选择EndPoint Information,就有第三方工具连接需要的database的URL(即Hostname)和username、password了,端口是mysql默认的3306
4、第三方工具连接并创建表
好了,到这里基本上就自始至终的把一个spring boot的用户登陆和添加书单功能做过并部署到云服务了:
访问地址:https://linfujian-readinglist.cfapps.io/
username:fujian
fullname:linfujian
password:19891109
由于云服务免费使用60天,所以后续可能打不开了