文章目录
入门之入门
简单介绍
Spring介绍
Spring是为了解决企业级应用开发的复杂性而创建的开发框架。
Spring为了简化开发,采取了4个关键策略:
1,基于POJO的轻量级和最小侵入性编程。
2,通过IOC(控制反转),依赖注入和面向接口实现松耦合。
3,通过AOP(面向切面)和惯例进行声明式编程。
4,通过切面和模板减少样式代码。
Springboot介绍
框架之框架,比Spring更简化,约定大于配置。
其实仍然就是一个javaweb的开发框架。
微服务架构
微服务架构即打破从前all in one的架构方式,把每个功能元素独立出来,可以动态地再进行整合。
微服务架构是对功能元素进行复制,而非对整个应用进行复制。
Hello Springboot
快速上手
新建一个工程,简单地选一下:
先勾选web-Spring web,点finish:
以下几个多余的可以删掉:
等它加载完就可以运行了,如果报错Web server failed to start. Port 8080 was already in use.
就可以在resource下的application里改以下端口号,如:server.port=8081
下图表示运行成功,端口号为8081:
下面写个controller测试一下,建立好这个controller的包和类:
写一个hello/springboot页面的controller,返回文本“hello springboot”:
@Controller
@RequestMapping("/hello")
public class HelloController {
@GetMapping("/springboot")
@ResponseBody
public String Hello(){
return "hello springboot";
}
}
进入http://localhost:8081/hello/springboot,看到网页上打出hello springboot,运行成功。
彩蛋功能
这里介绍一个彩蛋功能,
在resource目录下面新建一个banner.txt,可以修改程序启动时的图片,效果如下:
入门之理论
自动配置原理
pom文件
在pom.xml文件中,主要存放着核心依赖和启动器。
核心依赖:在父工程中,有版本仓库,所以有些依赖不需要我们指定版本。
启动器:即springboot的启动场景,如spring-boot-starter-web,会帮我们自动导入web环境的所有依赖。
springboot会将所有的功能场景都变成一个个的启动器,我们要使用什么功能,只需要找到对应的启动器。
主程序的注解
在Application中:
@SpringBootApplication
public class DemoPackApplication {
public static void main(String[] args) {
SpringApplication.run(DemoPackApplication.class, args);
}
}
其中注解@SpringBootApplication标明这是一个springboot程序,点进去可以看到一些组合注解:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
//上面几个是每一级都有的元注解,其他的注解要一级一级往下点:
@SpringBootConfiguration
//springboot的配置
@Configuration
//spring配置类
@Component
//说明这是一个spring的组件
@EnableAutoConfiguration
//自动配置
@AutoConfigurationPackage
//自动配置包
@Import({Registrar.class})
//包注册
@Import({AutoConfigurationImportSelector.class})
//自动配置导入选择,点进这个类,找到这个:
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
//这个就是获取所有的配置
getCandidateConfigurations是一个核心方法,点进去:
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = new ArrayList(SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader()));
ImportCandidates.load(AutoConfiguration.class, this.getBeanClassLoader()).forEach(configurations::add);
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you are using a custom packaging, make sure that file is correct.");
return configurations;
}
其中提到的META-INF/spring.factories是自动配置的核心文件,这东西在External Libraries里面:
可以点进其中的spring.factories看一下,里面就是所有的自动配置。
用一个流程图概括一下前述的结构:
结论:
1,springboot在启动的时候,从META-INF/spring.factories获取指定的值。
2,将这些自动配置的类导入容器,自动配置就会生效,帮我们进行配置。
3,以前我们需要手动配置的东西,springboot帮我们做了。
4,整合javaEE,解决方案和自动配置都在spring-boot-autoconfigure这个包下。
5,它会把所有需要的导入的组件以类名的方式返回,这些组件就会被添加进容器。
6,容器中也会存在非常多的xxxAutoConfigure文件(@Bean),就是这些类给容器中导入了这个场景需要的所有组件,并自动配置。
7,有了这些自动配置类,免去了我们手动编写配置文件的工作。
yaml语法
springboot自动生成的配置文件是application.properties,但官方推荐同yaml文件,
基本语法:
- 大小写敏感
- 使用缩进表示层级关系
- 禁止使用tab缩进,只能使用空格键
- 缩进长度没有限制,只要元素对齐就表示这些元素属于一个层级。
- 使用#表示注释
- 字符串可以不用引号标注
我们可以把拓展名改成yml:
server:
port: 8081
这是改变端口号的代码。
# 普通的key-value
name: Rosemary
# 对象
student:
name: Rosemary
age: 23
# 行内写法
student2: {name: Rosemary,age: 23}
# 数组
pet:
-cat
-dog
-pig
pet2: [cat,dog,pig]
注意yaml语法对空格极其严格。
可以看到yaml可以写对象,不过一般不用这个写,到此打住。
实战准备
要解决的问题:
1,导入静态资源
2,首页定制
3,模板引擎Thymeleaf
4,装配扩展SpringMVC
5,增删改查
6,拦截器
7,国际化
静态资源导入
在resources下的static显然可以用来存放静态资源,
此外,还可以新建public和resources文件夹用来存放静态资源:
我们在这些地方都建一个1.js,分别写:
hello,public
hello,resources
hello,static
运行并打开网页,结果在网页上显示的是hello,resources,说明它的优先级最高,
将hello,resources删掉,重新运行,显示的是hello,static,说明它的优先级次之,
将hello,static删掉,重新运行,显示的是hello,public,说明它的优先级最低。
一般顾名思义,public放些公告资源,static放些图片之类的,resources放些大家上传的文件。
首页定制
首页也是放在静态资源里面,但网址是http://localhost:8081,后面不再杠什么了,
这个时候要用index.html作为文档名,就会自动映射到首页:
在resources/public下建立一个index.html,写上“首页”,运行并打开网页:
没有任何问题。通过对index.html进行定制,就可以定制首页了。
这里我顺便试了一下,将一个html5制作的魔塔直接扔到resources目录下,改文件夹名为public,
并覆盖掉原来的,就能在首页直接运行了:
Thymeleaf模板引擎
模板引擎入门
模板引擎是干什么的呢?在web开发中,前端会为我们提供HTML页面,其中的元素是静态的,
而模板引擎可以接管其中的任意元素,将动态的元素(如变量)插进HTML页面中,实现数据交互。
以往的模板引擎是JSP,springboot推荐用较新的Thymeleaf。
@Controller
@RequestMapping()
public class IndexController {
@GetMapping("/test")
@ResponseBody
public String test(){
return "test";
}
}
首先添加一个indexController,@RequestMapping后面不加东西,@GetMapping后面是/test,
这样我们可以在http://localhost:8081/test中看到页面返回test,说明这个controller添加成功。
根据启动器一般的命名规则,引入Thymeleaf依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
再修改controller:
@Controller
public class TestController {
@GetMapping("/test")
public String test(){
return "test";
}
}
注意:注解要使用@Controller而非@RestController,也不能有@ResponseBody。
因为@RestController就等于@Controller加上@ResponseBody,
而@ResponseBody表示着这个返回体是直接交给浏览器处理而非映射到某个页面,
而现在我们要做的就是用模板引擎映射到test页面上。
test页面:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>TEST</h1>
</body>
</html>
运行测试,发现http://localhost:8081/test中打出来的不再是字符串“test”,而是标题1格式的“TEST”,
这说明“test”已经从字符串映射成我们的test.html页面了。
页面中加入了命名空间xmlns:th=“http://www.thymeleaf.org”,这让我们可以通过th来接管html的所有元素,
下面我们来试一下,给controller的方法加一个model参数,它可以用来携带后端传给前端的消息:
@Controller
public class TestController {
@GetMapping("/test")
public String test(Model model){
model.addAttribute("msg","Hello,Thymeleaf");
return "test";
}
}
然后改一下test.html,用th元素来取这个msg值:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div th:text="${msg}"></div>
</body>
</html>
这里msg会爆红但是不影响,不知道为啥。由于h1标签本身没有text元素,所以改成了div标签。
访问http://localhost:8081/test,打出Hello,Thymeleaf,测试成功。
模板引擎语法
转译文本:
th元素可以接管所以html标签,而取值时用${}括起来,链接用@{}括起来。
上面用div标签打出了目标文本,但是想用h1怎么办呢?我们可以用th:utext来转译这个文本,
先小改一下testcontroller的一行代码:
model.addAttribute("msg","<h1>Hello,Thymeleaf</h1>");
就是给文本内容加了个h1标签,然后修改test.html:
<body>
<div th:text="${msg}"></div>
<div th:utext="${msg}"></div>
</body>
就是在原来的div标签下加一个标签,使用可以转译的utext元素,看看效果:
可以看到,不转译的text直接将h1标签当作文本输出了,转译的utxet则识别到了h1标签。
遍历:
下面介绍一个常用的遍历方法:
public String test(Model model){
model.addAttribute("msg","<h1>Hello,Thymeleaf</h1>");
model.addAttribute("users", Arrays.asList("张三","李四","王五"));
return "test";
}
控制器加了一个用户列表。下面在test.html中用th:each来遍历它:
<body>
<div>[[${msg}]]</div>
<div th:utext=${msg}></div>
<div th:each="user:${users}">[[${user}]]</div>
</body>
结果:
测试成功。
其他语法跟java差不多,相当于直接在html里面写java。
装配扩展SpringMVC
看看SpringMVC支持的一些东西:
Spring Boot provides auto-configuration for Spring MVC that works well with most applications.
(大多场景我们都无需自定义配置)
The auto-configuration adds the following features on top of Spring’s defaults:
● Inclusion of ContentNegotiatingViewResolver and BeanNameViewResolver beans.
○ 内容协商视图解析器和BeanName视图解析器
● Support for serving static resources, including support for WebJars (covered later in this document)).
○ 静态资源(包括webjars)
● Automatic registration of Converter, GenericConverter, and Formatter beans.
○ 自动注册 Converter,GenericConverter,Formatter
● Support for HttpMessageConverters (covered later in this document).
○ 支持 HttpMessageConverters (后来我们配合内容协商理解原理)
● Automatic registration of MessageCodesResolver (covered later in this document).
○ 自动注册 MessageCodesResolver (国际化用)
● Static index.html support.
○ 静态index.html 首页支持
● Custom Favicon support (covered later in this document).
○ 自定义 Favicon 图标
● Automatic use of a ConfigurableWebBindingInitializer bean (covered later in this document).
○ 自动使用 ConfigurableWebBindingInitializer (DataBinder负责将请求数据绑定到JavaBean上)
扩展MVC(保持原有配置并添加一些配置):
If you want to keep those Spring Boot MVC customizations and make more MVC customizations (interceptors, formatters, view controllers, and other features), you can add your own @Configuration class of type WebMvcConfigurer but without @EnableWebMvc.
不用@EnableWebMvc注解。使用 @Configuration + WebMvcConfigurer 自定义规则
首先新建一个mvcConfig类:
按照官方文档,这个类要添加@Configuration注解并实现WebMvcConfigurer 接口:
//扩展MVC
@Configuration
public class mvcConfig implements WebMvcConfigurer {
}
并且不能标注@EnableWebMvc注解,否则MVC会全面接管这个类。
@EnableWebMvc:从容器中获取所有的webmvcconfige
快捷键Alt+insert可以查看这个类能重写的方法:
例如我们要自定义视图解析器,只需要写一个类继承视图解析器,再通过@Bean注入容器:
@Bean
public ViewResolver MyViewResolver(){
return new MyViewResolver();
}
public static class MyViewResolver implements ViewResolver{
@Override
public View resolveViewName(String viewName, Locale locale) throws Exception {
return null;
}
}
下面我们来自定义一个视图跳转:
@Configuration
public class mvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/view").setViewName("test2");
}
}
这里执行的是,当我们访问/view请求时,进入test2页面,我们再写一个test2.html放在templates下测试即可。
至此,我们已大致了解了实战前需要了解的一些原理。
员工管理系统
准备工作
pojo层
将controller都删掉,将mvcConfig弄干净点:
@Configuration
@EnableWebMvc
public class mvcConfig implements WebMvcConfigurer {
}
将页面资源导入templates,将静态资源导入static:
导入lombok的依赖:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
数据库后面再讲,这里先伪造一下数据。建立pojo类,下面建立部门表和员工表:
部门表:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Department {
private Integer id;
private String departmentName;
}
这三个注解是lombok自带的,可以自动引入getter、setter、有参和无参构造方法等:
员工表:
@Data
@NoArgsConstructor
public class Employee {
private Integer id;
private String lastName;
private String email;
private Integer gender;
private Department department;
private Date birth;
public Employee(Integer id, String lastName, String email, Integer gender, Department department) {
this.id = id;
this.lastName = lastName;
this.email = email;
this.gender = gender;
this.department = department;
this.birth = new Date();
}
}
没有有参构造的注解,而是自定义一个有参构造,让他内部生成一个Date给birth。
DAO层
建立部门和员工的DAO:
部门的DAO:
@Repository
public class DepartmentDAO {
//模拟数据库中的数据
private static Map<Integer, Department> departments = null;
static {
departments = new HashMap<Integer, Department>();
departments.put(101,new Department(101,"教学部"));
departments.put(102,new Department(102,"市场部"));
departments.put(103,new Department(103,"技术部"));
}
//获得所有部门信息
public Collection<Department> getDepartments(){
return departments.values();
}
//根据id获得部门
public Department getDepartmentById(Integer id){
return departments.get(id);
}
}
员工的DAO:
@Repository
public class EmployeeDAO {
//模拟数据库中的数据
private static Map<Integer, Employee> employees = null;
@Autowired
private DepartmentDAO departmentDAO;
static {
employees = new HashMap<Integer, Employee>();
employees.put(1001,new Employee(1001,"Duo","12345@qq.com",1,new Department(101,"教学部")));
employees.put(1002,new Employee(1002,"Rose","123456@qq.com",0,new Department(102,"市场部")));
employees.put(1003,new Employee(1003,"Lily","1234567@qq.com",0,new Department(103,"技术部")));
}
//主键自增
private static Integer initId = 1004;
//增加一个新员工
public void save(Employee employee){
if (employee.getId() == null){
employee.setId(initId++);
}
employee.setDepartment(departmentDAO.getDepartmentById(employee.getDepartment().getId()));
employees.put(employee.getId(),employee);
}
//查询全部员工
public Collection<Employee> getEmployees(){
return employees.values();
}
//根据id查询员工
public Employee getEmployeeById(Integer id){
return employees.get(id);
}
//删除员工
public void delEmpolyee(Integer id){
employees.remove(id);
}
}
完成这两个DAO,准备工作就结束了。
首页实现
我们的静态资源要被Thymeleaf接管的话,必须添加命名空间xmlns:th=“http://www.thymeleaf.org”,
然后在要使用css的地方,href改成th:href,链接用@{}括起来。要修改的三行代码如下:
<!-- Bootstrap core CSS -->
<link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
<!-- Custom styles for this template -->
<link th:href="@{/css/signin.css}" rel="stylesheet">
<img class="mb-4" th:src="@{/img/bootstrap-solid.svg}" alt="" width="72" height="72">
然后我们映射到首页,可以直接在config里面写,注意把@EnableWebMvc去掉:
@Configuration
public class mvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("/index");
registry.addViewController("/index.html").setViewName("/index");
}
}
这样在访问/或者/index.html的时候都可以映射到我们的首页,我们来试一下:
成功来到了首页,并且图标和css样式都加载成功了。
如果不成功的话,可以清除浏览器缓存与Thymeleaf缓存:
spring.thymeleaf.cache = false
登录实现
简单登录
由于目前数据库是伪造的,我们让他点击登录时直接跳转页面就好了,
首先来到index页面,改一行代码:
<form class="form-signin" th:action="@{/user/login}">
然后写个控制器实现这个请求:
@Controller
public class LoginController {
@ResponseBody
@RequestMapping("user/login")
public String login(){
return "OK";
}
}
先测试一下,打出OK,再进行后面的操作。
然后实现登录功能,由于要提交表单,要给用户名和密码标签加个name:
<label class="sr-only">Username</label>
<input type="text" name="username" class="form-control" placeholder="Username" required="" autofocus="">
<label class="sr-only">Password</label>
<input type="password" name="password" class="form-control" placeholder="Password" required="">
然后在控制器接收用户名和密码,以及用于返回消息的model:
@RequestMapping("user/login")
public String login(@RequestParam("username") String username,
@RequestParam("password") String password,
Model model){
if(!StringUtils.isEmpty(username) && "123456".equals(password)){
return "dashboard";
}else {
model.addAttribute("msg","用户名或密码错误");
return "index";
}
}
现在的密码验证是用户名不为空,密码为“123456”就能登陆成功。
测试后发现msg消息没能成功返回,以及dashboard页面没能加载css样式,我们修改一下,
首先在h1标签下面加个p标签:
<p style="color: red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p>
这样就能在密码错误时返回消息了,
接下来打开dashboard页面,加入Thymeleaf的命名空间,并对所有链接和文本标签进行接管,
就可以加载css样式了(路径里的assert都删掉)。
最后,由于登录后的url有点难看,可以在mvcConfig里面加一个映射:
registry.addViewController("user/main.html").setViewName("/dashboard");
然后将控制器return的dashboard改为重定向到main:
return "redirect:main.html";
这样登录成功后的页面就是http://localhost:8081/user/main.html了,比较好看。
登录拦截器
拦截器需要写一个loginHandlerInterceptor,实现HandlerInterceptor中的preHandle方法,放在config下,
为此先在控制器添加一个session:
@RequestMapping("user/login")
public String login(@RequestParam("username") String username,
@RequestParam("password") String password,
Model model, HttpSession session){
if(!StringUtils.isEmpty(username) && "123456".equals(password)){
session.setAttribute("LoginUser",username);
return "redirect:main.html";
}else {
model.addAttribute("msg","用户名或密码错误");
return "index";
}
}
然后实现preHandle方法:
public class loginHandlerInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Object loginUser = request.getSession().getAttribute("loginUser");
if(loginUser == null){
request.setAttribute("msg","没有权限,请先登录");
request.getRequestDispatcher("index.html").forward(request,response);
return false;
}else {
return true;
}
}
}
拦截器写好之后,还要在mvcConfig中放进去:
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new loginHandlerInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/","/index","/user/login","/css/**","/js/**","/img/**");
}
可以看到,要拦截哪些请求、要放行哪些请求都是可选的,十分方便。
不登录直接进入http://localhost:8081/user/main.html,就会被返回登录页面:
既然有了session,我们也可以在登录成功后将用户名打在页面上了,修改dashboard中的一行:
<a class="navbar-brand col-sm-3 col-md-2 mr-0" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">[[${session.loginUser}]]</a>
用双中括号取出session中loginUser的值即可。
页面与展示
抽取公共元素
回顾一下我们的主要任务:
1,首页配置:Thymeleaf接管所有静态页面
2,登录+拦截器
3,核心功能:增删改查
我们登录后的页面是dashboard,里面有一栏是Customers,我们用这一栏来展示员工列表:
<li class="nav-item">
<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
员工管理
</a>
</li>
这里把图标给删了,改成中文。跳转的地址要改成自己写的controller,为此先写一个员工管理controller:
@Controller
public class EmployeeController {
@Autowired
EmployeeDAO employeeDAO;
@RequestMapping("/emps")
public String list(Model model){
Collection<Employee> employees = employeeDAO.getEmployees();
model.addAttribute("emps",employees);
return "emps/list";
}
}
注意在templates下面新建一个emps,用于存放list。
写好这个controller之后就可以改dashboard了:
<li class="nav-item">
<a class="nav-link" th:href="@{/emps}">
员工管理
</a>
</li>
相应的,对list.html作出修改。
注意到我们页面的侧边栏和顶部菜单都是一样的,他们可以作为公共元素被抽取出来,
为此我们使用Thymeleaf自带的写法,将他们变成组件:
<nav class="col-md-2 d-none d-md-block bg-light sidebar" th:fragment="sidebar">
找到侧边栏标签,给他加个th:fragment=“sidebar”,意思是把这个标签定义为侧边栏组件,
在其他页面中,就可以使用这个名称来引用这整个标签,
例如在list.html中,删掉原来的侧边栏而引用这个:
<div th:insert="~{dashboard::sidebar}"></div>
测试一下,发现list页面中成功抽取了侧边栏,接下来对导航栏如法炮制:
<!-- dashboard.html-->
<nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0" th:fragment="topbar">
<!-- list.html-->
<div th:insert="~{dashboard::topbar}"></div>
再测试,发现抽取成功。但是这样写不好引用,
一般写法是把组件写在一个独立的页面commons里,在所有页面中要使用时都从commons中引用,
这样字在修改这些组件时就只需要在一个地方修改:
注意我们在引用时需要重新声明引用的路径:
<div th:replace="~{commons/commons::sidebar}"></div>
点击时高亮
我们在点击“员工管理”时,高亮的还是首页,这显然不合理,要将代码修改成点哪个哪个就高亮,
为此我们在不同的页面(dashboard和list)调用commons时,可以给他传一个参数:
<div th:replace="~{commons/commons::sidebar(active='main.html')}"></div>
意思是这个sidebar是被main页面调用了,在commons里面就可以接收这个参数:
<!-- 首页-->
<a th:class="${active=='main.html'?'nav-link active':'nav-link'}" th:href="@{/user/main.html}">
<!-- 员工管理-->
<a th:class="${active=='emps.html'?'nav-link active':'nav-link'}" th:href="@{/emps}">
这样就可以实现点击时高亮的功能了,注意Thymeleaf中单引号与三元表达式的用法。
展示员工列表
list.html里面有个默认的写死的表单,按照自己的员工表修改表头:
<thead>
<tr>
<th>id</th>
<th>lastName</th>
<th>email</th>
<th>gender</th>
<th>department</th>
<th>birth</th>
</tr>
</thead>
将tbody标签里默认的数据删掉,这里的数据应当是遍历出来的,可以用th:each遍历:
<tbody>
<tr th:each="emp:${emps}">
<td th:text="${emp.getId}"></td>
<td th:text="${emp.getLastName()}"></td>
<td th:text="${emp.getEmail()}"></td>
<td th:text="${emp.getGender()==1?'男':'女'}"></td>
<td th:text="${emp.getDepartment().getDepartmentName()}"></td>
<td th:text="${emp.getBirth()}"></td>
</tr>
</tbody>
这样就能成功遍历出我们的数据了(虽然是在DAO层编的):
然后可以加两个按钮用于编辑和删除:
<td>
<button class="btn btn-sm btn-primary">编辑</button>
<button class="btn btn-sm btn-danger">删除</button>
</td>
效果如图:
如果觉得日期比较丑,可以格式化一下:
<td th:text="${#dates.format(emp.getBirth(),'yyyy-MM-dd HH:mm:ss')}"></td>
编辑和删除的具体实现见下一章。
编辑与删除
增加员工实现
要实现增加员工,首先应该给个添加员工按钮,放在Section title的下面(或者干脆把Section title替换掉):
<h2><a class="btn btn-sm btn-success" th:href="@{/emp}">添加员工</a></h2>
点击这个按钮,应当跳转到对应的添加员工页面,所以还需要写这个页面,为此先实现页面的跳转,
在EmployeeController那里加个GetMapping:
@Autowired
DepartmentDAO departmentDAO;
@GetMapping("/emp")
public String toAddPage(Model model){
Collection<Department> departments = departmentDAO.getDepartments();
model.addAttribute("departments",departments);
return "emps/add";
}
这里将后端所有的部门传给前端,用于添加员工时显示。接下来写添加员工页面,
将list页面复制一份,叫做add,然后进行一些修改。头部、公共部分是不用改的,只需要改main标签内部,
先将原来的删掉,然后写一份添加员工表单,样式可以再bootstrap上面找,这里直接放:
<form th:action="@{/emp}" method="post">
<div class="form-group">
<label>LastName</label>
<input type="text" name="lastName" class="form-control" placeholder="">
</div>
<div class="form-group">
<label>Email</label>
<input type="email" name="email" class="form-control" placeholder="">
</div>
<div class="form-group">
<label>Gender</label><br>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="gender" value="1">
<label class="form-check-label">男</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="gender" value="0">
<label class="form-check-label">女</label>
</div>
</div>
<div class="form-group">
<label>department</label>
<select class="form-control" name="department.id">
<option th:each="dept:${departments}" th:text="${dept.getDepartmentName()}" th:value="${dept.getId()}"></option>
</select>
</div>
<div class="form-group">
<label>Birth</label>
<input type="date" name="birth" class="form-control" placeholder="">
<button class="btn btn-primary">添加</button>
</div>
</form>
可以看到,表单提交地址依然是/emp,不过前面的添加员工按钮是get请求,这里是post,所以能识别。
下面要在控制器接收这个返回的表单:
@PostMapping ("/emp")
public String addEmp(Employee employee){
employeeDAO.save(employee);
return "redirect:/emps";
}
由于我们的日期格式是yyyy-MM-dd,而系统默认是斜杠分隔的,还要加一条配置:
spring.mvc.format.date=yyyy-MM-dd
由于我们表单中生日的type=“date”,所以会自动转化成配置的格式:
可以看到,添加的员工id是自增的。
显示出来的日期带有时分秒,比较奇怪,可以在list.html里面删掉。
修改员工实现
要给编辑按钮一个跳转的连接,先要将button改成a标签,并加href:
<a class="btn btn-sm btn-primary" th:href="@{/emp/}+${emp.getId}">编辑</a>
控制器:
@GetMapping("/emp/{id}")
public String toUpdatePage(@PathVariable("id")Integer id,Model model){
Employee employee = employeeDAO.getEmployeeById(id);
model.addAttribute("emp",employee);
Collection<Department> departments = departmentDAO.getDepartments();
model.addAttribute("departments",departments);
return "emps/update";
}
复制add页面,命名为update,修改其中的表单:
<form th:action="@{/updateEmp}" method="post">
<div class="form-group">
<label>LastName</label>
<input th:value="${emp.getLastName()}" type="text" name="lastName" class="form-control" placeholder="">
</div>
<div class="form-group">
<label>Email</label>
<input th:value="${emp.getEmail()}" type="email" name="email" class="form-control" placeholder="">
</div>
<div class="form-group">
<label>Gender</label><br>
<div class="form-check form-check-inline">
<input th:checked="${emp.getGender()==1}" class="form-check-input" type="radio" name="gender" value="1">
<label class="form-check-label">男</label>
</div>
<div class="form-check form-check-inline">
<input th:checked="${emp.getGender()==0}" class="form-check-input" type="radio" name="gender" value="0">
<label class="form-check-label">女</label>
</div>
</div>
<div class="form-group">
<label>department</label>
<select class="form-control" name="department.id">
<option th:selected="${dept.getId()==emp.getDepartment().getId()}" th:each="dept:${departments}" th:text="${dept.getDepartmentName()}" th:value="${dept.getId()}"></option>
</select>
</div>
<div class="form-group">
<label>Birth</label>
<input th:value="${#dates.format(emp.getBirth(),'yyyy-MM-dd')}" type="date" name="birth" class="form-control" placeholder="">
<button class="btn btn-primary">修改</button>
</div>
</form>
主要是每项添加了一个默认值,显示这个被选中的员工原来的值。要注意生日的格式化。
测试发现加载不了css:
发现是路径的css前面没有加斜杠,之前不出现这个错误可能是这里的路径多了一层/id,系统找不到css了,
添加css路径前的斜杠后样式加载正常:
<title>Dashboard Template for Bootstrap</title>
<!-- Bootstrap core CSS -->
<link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
<!-- Custom styles for this template -->
<link th:href="@{/css/dashboard.css}" rel="stylesheet">
<style type="text/css">
/* Chart.js */
添加接收修改的控制器:
@PostMapping("/updateEmp")
public String updateEmp(Employee employee){
employeeDAO.save(employee);
return "redirect:/emps";
}
可以看到,逻辑跟添加员工是一样的。测试一下:
发现修改之后直接新增了一个多儿,而非替换掉原来的Duo,是底层员工id自增的问题,
只需要在update的表单里加一个隐藏域,传一下当前id,即可解决这个问题:
<input th:type="hidden" name="id" th:value="${emp.getId()}">
测试一下:
没有问题,此时如果新增员工,也是id=1004。
删除员工实现
list的链接:
<a class="btn btn-sm btn-danger" th:href="@{/del/}+${emp.getId}">删除</a>
控制器:
@GetMapping("/del/{id}")
public String delete(@PathVariable("id")Integer id){
employeeDAO.delEmpolyee(id);
return "redirect:/emps";
}
这就写完了,测试成功。
小结:如何写一个网站
(前端)
模板:别人写好的,我们拿来改成自己需要的。
组件:自己手动组合拼接。
资源:bootstrap、semantic-ui
要掌握的:栅格系统、导航栏、侧边栏、表单
1,前端搞定:页面长什么样子
2,数据库设计
3,让前端能够自己运行,独立化工程
4,数据接口如何对接
5,前后端联调测试