SpringBoot学习
一、介绍
[学习资料]官方文档介绍
[学习视频]:b站狂神springboot篇
二、第一个SpringBoot项目
2.1、新建spring项目
2.1.1、第一种方法:官网配置导出rar包:
2.1.2、idea新建项目选择Spring Initializer
按第一个配置内容就行了
等待配置文件、jar包导入好就行了
2.1.3、项目结构
2.2、建好src下目录文件结构
注意:controller、dao、pojo、service都必须和HelloworldApplication同级
2.2.1Run一下HelloworldApplication的main方法,就可以启动项目了,访问http://localhost:8080/hello
2.2.2application.properties文件可以配置springboot项目的一些功能:
//更改端口号
server.port=8081
2.2.3修改banner图方法(比较好玩的):
在resource目录下新建一个banner.txt文件(可以各种各样,随心所欲):
// _ooOoo_ //
// o8888888o //
// 88" . "88 //
// (| ^_^ |) //
// O\ = /O //
// ____/`---'\____ //
// .' \\| |// `. //
// / \\||| : |||// \ //
// / _||||| -:- |||||- \ //
// | | \\\ - /// | | //
// | \_| ''\---/'' | | //
// \ .-\__ `-` ___/-. / //
// ___`. .' /--.--\ `. . ___ //
// ."" '< `.___\_<|>_/___.' >'"". //
// | | : `- \`.;`\ _ /`;.`/ - ` : | | //
// \ \ `-. \_ __\ /__ _/ .-` / / //
// ========`-.____`-.___\_____/___.-`____.-'======== //
// `=---=' //
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //
// 佛祖保佑 永不宕机 永无BUG //
重启就行了
nice~
三、原理初探
3.1、自动配置:
pom.xml
- spring-boot-dependencies:核心依赖在父工程中
- 我们在写或者引入一些Springboot依赖的时候,不需要指定版本,因为这些在版本仓库中
- springboot在启动的时候,从类路径下/META-INF/spring.factories获取指定的值;
- 将这些自动配置的类导入容器,自动配置就会生效,帮我们进行自动配置!
- 以前我们需要的东西,现在springboot帮我们自动配置!
- 整合javaEE,解决方案和自动配置的东西都在spring-boot-autoconfigure-2.2.0.RELEASE.jar这个包下
- 它会把所有需要导入的组件,以类名的方式返回,这些组件就会被添加到容器中;
- 容器中也会存在非常多的xxxAutoConfiguration的文件(@bean),就是这些类给容器中导入这个场景需要的所有的组件;并自动配置,@Configuration,JavaConfig!
- 有了自动配置类
- springboot自动配置图解
3.2、启动器
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
-
启动器:就是Springboot的启动场景;
-
比如spring-boot-start-web,它就会帮我们自动导入web环境下的所有依赖!
-
springboot会将所有的功能场景,都变成一个个的启动器
-
我们需要使用什么功能,就只需要找到对应的启动器就可以了,
start
-
springboot-boot-starter-xxx:就是spring-boot的场景启动器
spring-boot-starter-web:帮我们导入了web模块正常运行所依赖的组件;
SpringBoot将所有的功能场景都抽取出来,做成一个个的starter (启动器),只需要在项目中引入这些
starter即可,所有相关的依赖都会导入进来 , 我们要用什么功能就导入什么样的场景启动器即可 ;我
们未来也可以自己自定义 starter;
3.3、主程序(Springboot01HelloworldApplication.java)
package com.lxf;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
//@SpringBootApplication:标注这个类是一个springboot的应用
@SpringBootApplication
public class Springboot01HelloworldApplication {
public static void main(String[] args) {
//将springboot应用启动
SpringApplication.run(Springboot01HelloworldApplication.class, args);
}
3.4、启动过程
SpringApplication.run分析分析该方法主要分两部分,一部分是SpringApplication的实例化,二是run方法的执行;
SpringApplication这个类主要做了以下四件事情:
1、推断应用的类型是普通的项目还是Web项目
2、查找并加载所有可用初始化器 , 设置到initializers属性中
3、找出所有的应用程序监听器,设置到listeners属性中
4、推断并设置main方法的定义类,找到运行的主类
四、yaml语法
删除application.properties文件,新建一个application.yaml文件
4.1、基础语法:
# k=v
#普通的key-value
name: lxf
#yaml语法(对空格要求十分高):
#可以注入到我们的配置类中
#对象
student:
name:lxf
age:18
#行内写法
student:{name:lxf,age:20}
#数组
pets:
-cat
-dog
-pig
#行内写法
pets:[cat,dog,pig]
4.2、yml给实体类赋值:
person:
name: lxf
age: 18
happy: true
birth: 2002/02/02
maps: {k1:v1,k2:v2}
list:
-code
-music
-girl
dog:
name:旺财
age:3
实体类:
person类:
package com.lxf.pojo;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.List;
import java.util.Map;
@Component
@ConfigurationProperties(prefix = "person")
public class Person {
private String name;
private Integer age;
private Boolean happy;
private Date birth;
private Map<String,Object> maps;
private List<Object> lists;
private Dog dog;
public Person() {
}
public Person(String name, Integer age, Boolean happy, Date birth, Map<String, Object> maps, List<Object> lists, Dog dog) {
this.name = name;
this.age = age;
this.happy = happy;
this.birth = birth;
this.maps = maps;
this.lists = lists;
this.dog = dog;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Boolean getHappy() {
return happy;
}
public void setHappy(Boolean happy) {
this.happy = happy;
}
public Date getBirth() {
return birth;
}
public void setBirth(Date birth) {
this.birth = birth;
}
public Map<String, Object> getMaps() {
return maps;
}
public void setMaps(Map<String, Object> maps) {
this.maps = maps;
}
public List<Object> getLists() {
return lists;
}
public void setLists(List<Object> lists) {
this.lists = lists;
}
public Dog getDog() {
return dog;
}
public void setDog(Dog dog) {
this.dog = dog;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", happy=" + happy +
", birth=" + birth +
", maps=" + maps +
", lists=" + lists +
", dog=" + dog +
'}';
}
}
dog类:
package com.lxf.pojo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class Dog {
@Value("旺财")
private String name;
@Value("3")
private Integer age;
public Dog() {
}
public Dog(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
测试:
package com.lxf;
import com.lxf.pojo.Dog;
import com.lxf.pojo.Person;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class Springboot01HelloworldApplicationTests {
@Autowired
Person person;
@Test
void contextLoads() {
System.out.println(person);
}
}
配置ConfigurationProperties的时候会报错,点击open documentation进入官网拿依赖写入就好
进不去就换低的版本文档如:配置元数据
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
4.3、yaml高级写法
person:
name: lxf${random.uuid} #随机一个uuid
age: ${random.int} #随机一个int值
happy: true
birth: 2002/02/02
maps: {k1:v1,k2:v2}
hello: small
list:
-code
-music
-girl
dog:
name:${person.hello:big}_旺财 #如果person.hello存在就显示它的值,不存在则显示big
age:3
4.4、松散绑定
application.yaml中写last-name: lxf对应实体类中lastName
4.4、JSR303校验
导入依赖:
<!--jsr303-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
@Component
@ConfigurationProperties(prefix = "person")
@Validated//开启JSR303验证
public class Person {
@NotNull(message="名字不能为空")//yaml或properties给lastName赋值时不能为null,否则报错:名字不能为空
private String lastName;
private Integer age;
private Boolean happy;
private Date birth;
private Map<String,Object> maps;
private List<Object> lists;
private Dog dog;
4.5、多环境切换
springboot可以通过激活不同的环境版本,实现环境的快速切换;
多配置文件
我们在主配置文件编写的时候,文件名可以是 application-{profifile}.properties/yml , 用来指定多个环境
版本;
例如:application-test.properties 代表测试环境配置 application-dev.properties 代表开发环境配置
但是Springboot并不会直接启动这些配置文件,它默认使用application.properties主配置文件;
我们需要通过一个配置来选择需要激活的环境:
#比如在配置文件中指定使用dev环境,我们可以通过设置不同的端口号进行测试;
#我们启动SpringBoot,就可以看到已经切换到dev下的配置了;
spring.profiles.active=dev
yml的多文档块
和properties配置文件中一样,但是使用yml去实现不需要创建多个配置文件,更加方便了 !
#默认
server:
port: 8081
#选择要激活那个环境块
spring:
profiles:
active: prod
---
server:
port: 8082
spring:
profiles: prod
---
server:
port: 8083
spring:
profiles:dev
6、注意:如果yml和properties同时都配置了端口,并且没有激活其他环境 , 默认会使用properties配
置文件的!
4.6、配置文件加载位置
springboot 启动会扫描以下位置的application.properties或者application.yml文件作为Spring boot的
默认配置文件
优先级1:项目路径下的config文件夹配置文件
优先级2:项目路径下配置文件
优先级3:资源路径下的config文件夹配置文件
优先级4:资源路径下配置文件
优先级由高到底,高优先级的配置会覆盖低优先级的配置;
SpringBoot会从这四个位置全部加载主配置文件;互补配置;
我们在最低级的配置文件中设置一个项目访问路径的配置来测试互补问题;
4.7、springboot精髓:
1、SpringBoot启动会加载大量的自动配置类
2、我们看我们需要的功能有没有在SpringBoot默认写好的自动配置类当中;
3、我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件存在在其中,我们就不需
要再手动配置了)
4、给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们只需要在配置文件中
指定这些属性的值即可;
xxxxAutoConfigurartion:自动配置类;给容器中添加组件
xxxxProperties:封装配置文件中相关属性;
五、SpringBoot Web开发
要解决的问题:
- 导入静态资源…
- 模板引擎Thymeleaf
- 装配扩展SpringMVC
- 拦截器
- 国际化!
- 增删改查
5.1、导入静态资源
- 在springboot,我们可以使用以下方式处理静态资源
- webjars
localhost:8080/webjars/**
- public,static,/**,resources文件夹
localhost:8080/
- 优先级:resource>static>public
- 新建一个index.html页面,在public、resource、static文件夹下都可以正常访问,放在templates文件夹下,必须通过controller才能访问到
package com.lxf.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
//在tempaltes目录下的所有页面,只能通过controller来跳转!
//这个需要模板引擎的支持!thymeleaf
@Controller
public class IndexController {
@GetMapping("/index")
public String index(){
return "index";
}
}
5.2、模板引擎Thymeleaf
pom依赖:
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-java8time</artifactId>
</dependency>
index.html:
<!DOCTYPE html>
<html lang="en" xmlns:th="http//wwww.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<!--所有html元素都可以被thymeleaf接管: th:元素名-->
<div th:text="${msg}"></div>
<!--处理字符串中的html代码-->
<div th:utext="${msg}"></div>
<!--遍历集合:-->
<!--第一种方法-->
<div th:each="user:${users}" th:text="${user}"></div>
<hr/>
<!--第二种方法-->
<div th:each="user:${users}">[[${user}]]</div>
</body>
</html>
IndexController
package com.lxf.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import java.util.Arrays;
//在tempaltes目录下的所有页面,只能通过controller来跳转!
//这个需要模板引擎的支持!thymeleaf
@Controller
public class IndexController {
@GetMapping("/index")
public String index(Model model){
model.addAttribute("msg","<h1>hello thymeleaf!</h1>");
model.addAttribute("users", Arrays.asList("lxf","lys"));
return "index";
}
}
5.3、装配扩展SpringMVC
package com.lxf.controller;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.core.Ordered;
import org.springframework.web.accept.ContentNegotiationManager;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.view.ContentNegotiatingViewResolver;
public class MySpringBootMvc {//自定义视图解析器
@Bean
@ConditionalOnBean(ViewResolver.class)
@ConditionalOnMissingBean(name = "viewResolver", value = ContentNegotiatingViewResolver.class)
public ContentNegotiatingViewResolver viewResolver(BeanFactory beanFactory) {
ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
resolver.setContentNegotiationManager(beanFactory.getBean(ContentNegotiationManager.class));
// ContentNegotiatingViewResolver使用所有其他视图解析器来定位视图,因此它应该具 有较高的优先级
resolver.setOrder(Ordered.HIGHEST_PRECEDENCE); return resolver; }
}
修改SpringBoot的默认配置:
//应为类型要求为WebMvcConfigurer,所以我们实现其接口
//可以使用自定义类扩展MVC的功能
//不能标注@EnableWebMvc注解
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
// 浏览器发送/test , 就会跳转到test页面
registry.addViewController("/test").setViewName("test");
}
}
确实也跳转过来了!所以说,我们要扩展SpringMVC,官方就推荐我们这么去使用,既保SpringBoot
留所有的自动配置,也能用我们扩展的配置!
5.4、显示页面、config层、dao层、pojo层编写
pojo层:
Department部门类:
package com.lxf.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
//部门表
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Department {
private Integer id;
private String departmentName;
}
Employee员工类:
package com.lxf.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
//员工表
@NoArgsConstructor
@Data
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();
}
}
dao层:
departmentdao类
package com.lxf.dao;
import com.lxf.pojo.Department;
import org.springframework.stereotype.Repository;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
//部门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,"教研部"));
departments.put(104,new Department(104,"运营部"));
departments.put(105,new Department(105,"后勤部"));
}
//获得所有部门信息
public Collection<Department> getDepartments(){
return departments.values();
}
//通过id得到部门
public Department getdeDepartmentByID(Integer id){
return departments.get(id);
}
}
EmplyoeeDao类
package com.lxf.dao;
import com.lxf.pojo.Department;
import com.lxf.pojo.Employee;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
//员工Dao
@Repository
public class EmployeeDao {
//模拟数据库中的数据
private static Map<Integer, Employee> employees=null;
//员工所属的部门
@Autowired
private DepartmentDao departmentDao;
static {
employees=new HashMap<Integer,Employee>();//创建一个部门表
//给部门表增加数据
employees.put(101,new Employee(1001,"AA","A12345@qq.com",1,new Department(101,"教学部")));
employees.put(101,new Employee(1002,"BB","B12345@qq.com",0,new Department(102,"市场部")));
employees.put(101,new Employee(1003,"CC","C12345@qq.com",1,new Department(103,"教研部")));
employees.put(101,new Employee(1004,"DD","D12345@qq.com",0,new Department(104,"运营部")));
employees.put(101,new Employee(1005,"EE","E12345@qq.com",1,new Department(105,"后勤部")));
}
//主键自增
private static Integer initId=1006;
//增加一个员工
public void addEmployee(Employee employee){
if(employee.getId()==null){
employee.setId(initId++);
}
employee.setDepartment(departmentDao.getdeDepartmentByID(employee.getDepartment().getId()));
employees.put(employee.getId(),employee);
}
//查询全部员工信息
public Collection<Employee> getAllEmployees(){
return employees.values();
}
//通过id查询员工
public Employee getEmployeeByID(Integer id){
return employees.get(id);
}
//通过id删除员工
}
config层:
MyMvcConfig类
package com.lxf.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/index.html").setViewName("index");
}
}
首页index.html:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title>Signin 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/signin.css}" rel="stylesheet">
</head>
<body class="text-center">
<form class="form-signin" action="dashboard.html">
<img class="mb-4" th:src="@{/img/bootstrap-solid.svg}" alt="" width="72" height="72">
<h1 class="h3 mb-3 font-weight-normal">Please sign in</h1>
<label class="sr-only">Username</label>
<input type="text" class="form-control" placeholder="Username" required="" autofocus="">
<label class="sr-only">Password</label>
<input type="password" class="form-control" placeholder="Password" required="">
<div class="checkbox mb-3">
<label>
<input type="checkbox" value="remember-me"> Remember me
</label>
</div>
<button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
<p class="mt-5 mb-3 text-muted">© 2017-2018</p>
<a class="btn btn-sm">中文</a>
<a class="btn btn-sm">English</a>
</form>
</body>
</html>
application.properties文件:
#关闭thymeleaf缓存
spring.thymeleaf.cache=false
显示页面:
5.5、国际化
在resource目录下新建一个i18n文件夹,然后在此文件夹下新建login.properties文件和login_zh_CN.properties和login_en_US.properties文件
在login_zh_CN.properties或login_en_US.properties文件将页面的默认和中英文写上
index.html页面:
<!DOCTYPE html>
<html lang="en-US" xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title th:text="#{login.home}">Home page</title>
<!-- 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">
</head>
<body class="text-center">
<form class="form-signin" action="dashboard.html">
<img class="mb-4" th:src="@{/img/bootstrap-solid.svg}" alt="" width="72" height="72">
<h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign in</h1>
<label class="sr-only" th:text="#{login.username}">Username</label>
<input type="text" class="form-control" th:placeholder="#{login.username}" required="" autofocus="">
<label class="sr-only" th:text="#{login.password}">Password</label>
<input type="password" class="form-control" th:placeholder="#{login.password}" required="">
<div class="checkbox mb-3">
<label th:text="#{login.remember}" >
<input type="checkbox" value="remember-me" > Remember me
</label>
</div>
<button class="btn btn-lg btn-primary btn-block" type="submit" th:text="#{login.bth}">Sign in</button>
<p class="mt-5 mb-3 text-muted">© 2017-2018</p>
<a class="btn btn-sm" th:href="@{/index.html(l='zh_CN')}">中文</a>
<a class="btn btn-sm" th:href="@{/index.html(l='en_US')}">English</a>
</form>
</body>
</html>
config文件夹下新建一个MyLocalResolver类:
package com.lxf.config;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.LocaleResolver;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Locale;
public class MyLocalResolver implements LocaleResolver {
//解析请求
@Override
public Locale resolveLocale(HttpServletRequest httpServletRequest) {
//获取请求中的语言参数
String l = httpServletRequest.getParameter("l");
Locale locale = Locale.getDefault();//如果没有就获取默认的
//如果请求的链接携带了国际化参数
if(!StringUtils.isEmpty(l)){
//分割字符串
String[] split = l.split("_");
//通过国家、地区的local获取对象
locale=new Locale(split[0],split[1]);
}
return locale;
}
@Override
public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) {
}
}
现在就可以实现中英文切换了
5.6、拦截器
config文件夹下新建一个LoginHandlerInterceptor类:
package com.lxf.config;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class LoginHandlerInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//登录成功之后,应该有用户的session
if (request.getSession().getAttribute("loginUser") == null) {
request.setAttribute("msg","请先登录!!!");
request.getRequestDispatcher("/index.html").forward(request,response);
return false;
}else{
return true;
}
}
}
config文件夹下的WebMvcConfigurer类:
package com.lxf.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/index.html").setViewName("index");
registry.addViewController("/main.html").setViewName("dashboard");
}
@Bean
public LocaleResolver localeResolver(){
return new MyLocalResolver();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginHandlerInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/index.html","/","/user/login","/static/**");
}
}
config文件夹下的MyMvcConfig类:
package com.lxf.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/index.html").setViewName("index");
registry.addViewController("/main.html").setViewName("dashboard");
}
@Bean
public LocaleResolver localeResolver(){
return new MyLocalResolver();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginHandlerInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/index.html","/","/user/login","/static/**");
}
}
index.html页面form加上action
<form class="form-signin" th:action="@{/user/login}">
dashboard.html页面
<!DOCTYPE html>
<!-- saved from url=(0052)http://getbootstrap.com/docs/4.0/examples/dashboard/ -->
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<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 */
@-webkit-keyframes chartjs-render-animation {
from {
opacity: 0.99
}
to {
opacity: 1
}
}
@keyframes chartjs-render-animation {
from {
opacity: 0.99
}
to {
opacity: 1
}
}
.chartjs-render-monitor {
-webkit-animation: chartjs-render-animation 0.001s;
animation: chartjs-render-animation 0.001s;
}
</style>
</head>
<body>
<nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0">
<a class="navbar-brand col-sm-3 col-md-2 mr-0" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#" th:text="${session.loginUser}"></a>
<input class="form-control form-control-dark w-100" type="text" placeholder="Search" aria-label="Search">
<ul class="navbar-nav px-3">
<li class="nav-item text-nowrap">
<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">Sign out</a>
</li>
</ul>
</nav>
<div class="container-fluid">
<div class="row">
<nav class="col-md-2 d-none d-md-block bg-light sidebar">
<div class="sidebar-sticky">
<ul class="nav flex-column">
<li class="nav-item">
<a class="nav-link active" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-home">
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path>
<polyline points="9 22 9 12 15 12 15 22"></polyline>
</svg>
Dashboard <span class="sr-only">(current)</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file">
<path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"></path>
<polyline points="13 2 13 9 20 9"></polyline>
</svg>
Orders
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-shopping-cart">
<circle cx="9" cy="21" r="1"></circle>
<circle cx="20" cy="21" r="1"></circle>
<path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"></path>
</svg>
Products
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-users">
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
<circle cx="9" cy="7" r="4"></circle>
<path d="M23 21v-2a4 4 0 0 0-3-3.87"></path>
<path d="M16 3.13a4 4 0 0 1 0 7.75"></path>
</svg>
Customers
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-bar-chart-2">
<line x1="18" y1="20" x2="18" y2="10"></line>
<line x1="12" y1="20" x2="12" y2="4"></line>
<line x1="6" y1="20" x2="6" y2="14"></line>
</svg>
Reports
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-layers">
<polygon points="12 2 2 7 12 12 22 7 12 2"></polygon>
<polyline points="2 17 12 22 22 17"></polyline>
<polyline points="2 12 12 17 22 12"></polyline>
</svg>
Integrations
</a>
</li>
</ul>
<h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted">
<span>Saved reports</span>
<a class="d-flex align-items-center text-muted" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-plus-circle"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="16"></line><line x1="8" y1="12" x2="16" y2="12"></line></svg>
</a>
</h6>
<ul class="nav flex-column mb-2">
<li class="nav-item">
<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
<polyline points="14 2 14 8 20 8"></polyline>
<line x1="16" y1="13" x2="8" y2="13"></line>
<line x1="16" y1="17" x2="8" y2="17"></line>
<polyline points="10 9 9 9 8 9"></polyline>
</svg>
Current month
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
<polyline points="14 2 14 8 20 8"></polyline>
<line x1="16" y1="13" x2="8" y2="13"></line>
<line x1="16" y1="17" x2="8" y2="17"></line>
<polyline points="10 9 9 9 8 9"></polyline>
</svg>
Last quarter
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
<polyline points="14 2 14 8 20 8"></polyline>
<line x1="16" y1="13" x2="8" y2="13"></line>
<line x1="16" y1="17" x2="8" y2="17"></line>
<polyline points="10 9 9 9 8 9"></polyline>
</svg>
Social engagement
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
<polyline points="14 2 14 8 20 8"></polyline>
<line x1="16" y1="13" x2="8" y2="13"></line>
<line x1="16" y1="17" x2="8" y2="17"></line>
<polyline points="10 9 9 9 8 9"></polyline>
</svg>
Year-end sale
</a>
</li>
</ul>
</div>
</nav>
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
<div class="chartjs-size-monitor" style="position: absolute; left: 0px; top: 0px; right: 0px; bottom: 0px; overflow: hidden; pointer-events: none; visibility: hidden; z-index: -1;">
<div class="chartjs-size-monitor-expand" style="position:absolute;left:0;top:0;right:0;bottom:0;overflow:hidden;pointer-events:none;visibility:hidden;z-index:-1;">
<div style="position:absolute;width:1000000px;height:1000000px;left:0;top:0"></div>
</div>
<div class="chartjs-size-monitor-shrink" style="position:absolute;left:0;top:0;right:0;bottom:0;overflow:hidden;pointer-events:none;visibility:hidden;z-index:-1;">
<div style="position:absolute;width:200%;height:200%;left:0; top:0"></div>
</div>
</div>
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
<h1 class="h2">Dashboard</h1>
<div class="btn-toolbar mb-2 mb-md-0">
<div class="btn-group mr-2">
<button class="btn btn-sm btn-outline-secondary">Share</button>
<button class="btn btn-sm btn-outline-secondary">Export</button>
</div>
<button class="btn btn-sm btn-outline-secondary dropdown-toggle">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-calendar"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect><line x1="16" y1="2" x2="16" y2="6"></line><line x1="8" y1="2" x2="8" y2="6"></line><line x1="3" y1="10" x2="21" y2="10"></line></svg>
This week
</button>
</div>
</div>
<canvas class="my-4 chartjs-render-monitor" id="myChart" width="1076" height="454" style="display: block; width: 1076px; height: 454px;"></canvas>
</main>
</div>
</div>
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script type="text/javascript" th:src="@{/js/jquery-3.2.1.slim.min.js}" ></script>
<script type="text/javascript" th:src="@{/js/popper.min.js}" ></script>
<script type="text/javascript" th:src="@{/js/bootstrap.min.js}" ></script>
<!-- Icons -->
<script type="text/javascript" th:src="@{/js/feather.min.js}" ></script>
<script>
feather.replace()
</script>
<!-- Graphs -->
<script type="text/javascript" th:src="@{/js/Chart.min.js}" ></script>
<script>
var ctx = document.getElementById("myChart");
var myChart = new Chart(ctx, {
type: 'line',
data: {
labels: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
datasets: [{
data: [15339, 21345, 18483, 24003, 23489, 24092, 12034],
lineTension: 0,
backgroundColor: 'transparent',
borderColor: '#007bff',
borderWidth: 4,
pointBackgroundColor: '#007bff'
}]
},
options: {
scales: {
yAxes: [{
ticks: {
beginAtZero: false
}
}]
},
legend: {
display: false,
}
}
});
</script>
</body>
</html>
5.7、增删改查
EmployeeController.class
package com.lxf.controller;
import com.lxf.dao.DepartmentDao;
import com.lxf.dao.EmployeeDao;
import com.lxf.pojo.Department;
import com.lxf.pojo.Employee;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.Collection;
@Controller
public class EmployeeController {
@Autowired
EmployeeDao employeeDao;
@Autowired
DepartmentDao departmentDao;
@RequestMapping("emps")
public String list(Model model){
Collection<Employee> allEmployees = employeeDao.getAllEmployees();
model.addAttribute("allEmployees",allEmployees);
return "emp/list";
}
@GetMapping("/emp")
public String toAddPage(Model model){
Collection<Department> departments = departmentDao.getDepartments();
model.addAttribute("departments",departments);
return "emp/add";
}
@PostMapping("/emp")
public String addAEmployee(Employee employee){
System.out.println("employee = " + employee);
employeeDao.addEmployee(employee);//保存员工信息
return "redirect:/emps";
}
@GetMapping("/deleteEmp/{id}")
public String deleteAEmployee(@PathVariable("id")Integer id){
employeeDao.deleteEmployeeByID(id);//根据id删除一个用户
return "redirect:/emps";
}
@GetMapping("/emp/toUpdateEmp")
public String toUpdateEmp(Integer id,Model model){
//查出原来的数据
Employee employee = employeeDao.getEmployeeByID(id);
Collection<Department> departments = departmentDao.getDepartments();
model.addAttribute("employee",employee);
model.addAttribute("departments",departments);
return "emp/update";
}
@PostMapping("/emp/updateEmp")
public String updateAEmployee(Employee employee){
System.out.println("employee = " + employee);
employeeDao.updateAEmployee(employee);//修改员工信息
return "redirect:/emps";
}
}
EmployeeDao.class类:
package com.lxf.dao;
import com.lxf.pojo.Department;
import com.lxf.pojo.Employee;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
//员工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,"AA","A12345@qq.com",1,new Department(101,"教学部")));
employees.put(1002,new Employee(1002,"BB","B12345@qq.com",0,new Department(102,"市场部")));
employees.put(1003,new Employee(1003,"CC","C12345@qq.com",1,new Department(103,"教研部")));
employees.put(1004,new Employee(1004,"DD","D12345@qq.com",0,new Department(104,"运营部")));
employees.put(1005,new Employee(1005,"EE","E12345@qq.com",1,new Department(105,"后勤部")));
}
//主键自增
private static Integer initId=1006;
//增加一个员工
public void addEmployee(Employee employee){
if(employee.getId()==null){
employee.setId(initId++);
}
employee.setDepartment(departmentDao.getdeDepartmentByID(employee.getDepartment().getId()));
employees.put(employee.getId(),employee);
}
//查询全部员工信息
public Collection<Employee> getAllEmployees(){
return employees.values();
}
//通过id查询员工
public Employee getEmployeeByID(Integer id){
return employees.get(id);
}
//通过id更新员工信息
public void updateAEmployee(Employee emp){
employees.replace(emp.getId(),emp);
}
//通过id删除员工
public void deleteEmployeeByID(Integer id){
employees.remove(id);
}
}
list.html界面:数据界面
add.html:增加页面
update.html:修改页面
六、整合Mybatis框架
pom依赖
<!--mybatis-spring-boot-starter:整合-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.1</version>
</dependency>
<!--springboot官方的-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
数据库:
新建一个pojo文件夹,在mapper文件夹下新建一个User类
package com.lxf.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int id;
private String name;
private String pwd;
}
新建一个mapper文件夹,在mapper文件夹下新建一个UserMapper接口
package com.lxf.mapper;
import com.lxf.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;
import java.util.List;
@Mapper
@Repository
public interface UserMapper {
List<User> queryUserList();
User queryUserById(int id);
int addUser(User user);
int updateUser(User user);
int deleteUser(int id);
}
新建一个mybatis.mapper文件夹,在此文件夹下新建一个UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lxf.mapper.UserMapper">
<select id="queryUserList" resultType="User">
select * from user;
</select>
<select id="queryUserById">
select * from user where id=#{id};
</select>
<insert id="addUser" parameterType="User">
insert into user (id,name,pwd) values (#{id}.#{name},#{pwd});
</insert>
<update id="updateUser" parameterType="User">
update user set name=#{name},pwd=#{pwd} where id=#{id};
</update>
<delete id="deleteUser" parameterType="int">
delete from user where id=#{id};
</delete>
</mapper>
application.properties配置文件
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#整合mybatis
mybatis.type-aliases-package=com.lxf.pojo;
mybatis.mapper-locations=classpath:mybatis/mapper/*.xml
新建一个controller文件夹,在mapper文件夹下新建一个UserController类
package com.lxf.controller;
import com.lxf.mapper.UserMapper;
import com.lxf.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class UserController {
@Autowired
private UserMapper userMapper;
@RequestMapping("/queryUserList")
public String queryUserList(){
List<User> users = userMapper.queryUserList();
return users.toString();
}
@RequestMapping("/queryUserById")
public String queryUserById(int id){
User user = userMapper.queryUserById(id);
return user.toString();
}
@RequestMapping("/addUser")
public String addUser(User user){
int i = userMapper.addUser(user);
if(i>0){
return "增加失败!";
}else{
return "增加成功!";
}
}
@RequestMapping("/updateUser")
public String updateUser(User user){
int i = userMapper.updateUser(user);
if(i>0){
return "修改失败!";
}else{
return "修改成功!";
}
}
@RequestMapping("/deleteUser")
public String deleteUser(int id){
int i = userMapper.deleteUser(id);
if(i>0){
return "删除失败!";
}else{
return "删除成功!";
}
}
}
七、SpringSecurity
Spring Security 是针对Spring项目的安全框架,也是Spring Boot底层安全模块默认的技术选型,他可
以实现强大的Web安全控制,对于安全控制,我们仅需要引入 spring-boot-starter-security 模
块,进行少量的配置,即可实现强大的安全管理!
记住几个类:
-
WebSecurityConfifigurerAdapter: 自定义Security策略
-
AuthenticationManagerBuilder:自定义认证策略
-
@EnableWebSecurity:开启WebSecurity模式
功能:认证、授权
controller层RouterController类:
package com.lxf.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class RouterController {
@RequestMapping({"/","/index"})
public String index(){
return "index";
}
@RequestMapping("toLogin")
public String toLogin(){
return "views/login";
}
@RequestMapping("/level1/{id}")
public String level1(@PathVariable("id")int id){
return "views/level1/"+id;
}
@RequestMapping("/level2/{id}")
public String level2(@PathVariable("id")int id){
return "views/level2/"+id;
}
@RequestMapping("/level3/{id}")
public String level3(@PathVariable("id")int id){
return "views/level3/"+id;
}
}
config.SecurityConfig.class类:
package com.lxf.config;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//授权
@Override
protected void configure(HttpSecurity http) throws Exception {
//首页所有人可以访问,功能页只有对应有权限的人才可以访问
//请求授权的规则
http.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/level1/**").hasRole("vip1")
.antMatchers("/level2/**").hasRole("vip2")
.antMatchers("/level3/**").hasRole("vip3");
//没有权限默认会到登录页面,需要开启登录的页面
//定制登录页
http.formLogin().loginPage("/toLogin").usernameParameter("user").passwordParameter("pwd").loginProcessingUrl("/login");
//注销,开启注销功能
//防止网站工具:get,post
http.logout().logoutSuccessUrl("/");
//开启记住我功能 cooike,默认保存两周
http.rememberMe().rememberMeParameter("remember");
}
//认证
//java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null":密码没被加密
//在spring security 5.0+新增了很多的加密方法
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("lxf").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2","vip3")
.and().withUser("lys").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3")
.and().withUser("yk").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1");
}
}
index.html页面:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>首页</title>
<!--semantic-ui-->
<link href="https://cdn.bootcss.com/semantic-ui/2.4.1/semantic.min.css" rel="stylesheet">
<link th:href="@{/qinjiang/css/qinstyle.css}" rel="stylesheet">
</head>
<body>
<!--主容器-->
<div class="ui container">
<div class="ui segment" id="index-header-nav" th:fragment="nav-menu">
<div class="ui secondary menu">
<a class="item" th:href="@{/index}">首页</a>
<div class="right menu">
<!--如果未登录-->
<div sec:authorize="!isAuthenticated()">
<a class="item" th:href="@{/toLogin}" sec>
<i class="address card icon"></i> 登录
</a>
</div>
<!--已登录-->
<div sec:authorize="isAuthenticated()">
<a class="item" >
用户名:<span sec:authentication="name"></span>
角色:<span sec:authentication="authorities"></span>
</a>
</div>
<div sec:authorize="isAuthenticated()">
<a class="item" th:href="@{/logout}">
<i class="sign-out icon"></i> 注销
</a>
</div>
</div>
</div>
</div>
<div class="ui segment" style="text-align: center">
<h3>Spring Security Study from kuang</h3>
</div>
<div>
<br>
<div class="ui three column stackable grid">
<div class="column" sec:authorize="hasRole('vip1')">
<div class="ui raised segment">
<div class="ui">
<div class="content">
<h5 class="content">Level 1</h5>
<hr>
<div><a th:href="@{/level1/1}"><i class="bullhorn icon"></i> Level-1-1</a></div>
<div><a th:href="@{/level1/2}"><i class="bullhorn icon"></i> Level-1-2</a></div>
<div><a th:href="@{/level1/3}"><i class="bullhorn icon"></i> Level-1-3</a></div>
</div>
</div>
</div>
</div>
<div class="column" sec:authorize="hasRole('vip2')">
<div class="ui raised segment">
<div class="ui">
<div class="content">
<h5 class="content">Level 2</h5>
<hr>
<div><a th:href="@{/level2/1}"><i class="bullhorn icon"></i> Level-2-1</a></div>
<div><a th:href="@{/level2/2}"><i class="bullhorn icon"></i> Level-2-2</a></div>
<div><a th:href="@{/level2/3}"><i class="bullhorn icon"></i> Level-2-3</a></div>
</div>
</div>
</div>
</div>
<div class="column" sec:authorize="hasRole('vip3')">
<div class="ui raised segment">
<div class="ui">
<div class="content">
<h5 class="content">Level 3</h5>
<hr>
<div><a th:href="@{/level3/1}"><i class="bullhorn icon"></i> Level-3-1</a></div>
<div><a th:href="@{/level3/2}"><i class="bullhorn icon"></i> Level-3-2</a></div>
<div><a th:href="@{/level3/3}"><i class="bullhorn icon"></i> Level-3-3</a></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script th:src="@{/qinjiang/js/jquery-3.1.1.min.js}"></script>
<script th:src="@{/qinjiang/js/semantic.min.js}"></script>
</body>
</html>
login界面:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>登录</title>
<!--semantic-ui-->
<link href="https://cdn.bootcss.com/semantic-ui/2.4.1/semantic.min.css" rel="stylesheet">
</head>
<body>
<!--主容器-->
<div class="ui container">
<div class="ui segment">
<div style="text-align: center">
<h1 class="header">登录</h1>
</div>
<div class="ui placeholder segment">
<div class="ui column very relaxed stackable grid">
<div class="column">
<div class="ui form">
<form th:action="@{/login}" method="post">
<div class="field">
<label>Username</label>
<div class="ui left icon input">
<input type="text" placeholder="Username" name="user">
<i class="user icon"></i>
</div>
</div>
<div class="field">
<label>Password</label>
<div class="ui left icon input">
<input type="password" name="pwd">
<i class="lock icon"></i>
</div>
</div>
<div class="field">
<input type="checkbox" name="remember">记住我
</div>
<input type="submit" class="ui blue submit button"/>
</form>
</div>
</div>
</div>
</div>
<div style="text-align: center">
<div class="ui label">
</i>注册
</div>
<br><br>
<small>blog.kuangstudy.com</small>
</div>
<div class="ui segment" style="text-align: center">
<h3>Spring Security Study from kuang</h3>
</div>
</div>
</div>
<script th:src="@{/qinjiang/js/jquery-3.1.1.min.js}"></script>
<script th:src="@{/qinjiang/js/semantic.min.js}"></script>
</body>
</html>
其他页面:
八、Shiro
Shiro架构(主要):
subject: 应用代码直接交互的对象是Subject,也就是说Shiro的对外API核心就是Subject,
Subject代表了当前的用户,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是
Subject,如网络爬虫,机器人等,与Subject的所有交互都会委托给SecurityManager;Subject其
实是一个门面,SecurityManageer 才是实际的执行者
SecurityManager:安全管理器,即所有与安全有关的操作都会与SercurityManager交互,并且它
管理着所有的Subject,可以看出它是Shiro的核心,它负责与Shiro的其他组件进行交互,它相当于
SpringMVC的DispatcherServlet的角色
Realm:Shiro从Realm获取安全数据(如用户,角色,权限),就是说SecurityManager 要验证
用户身份,那么它需要从Realm 获取相应的用户进行比较,来确定用户的身份是否合法;也需要从
Realm得到用户相应的角色、权限,进行验证用户的操作是否能够进行,可以把Realm看成
DataSource;
参考文档:官方文档
快速开始:官方的quickstart
简单练习:
1.新建config文件夹
- ShiroConfig类
package com.lxf.config;
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.LinkedHashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {
//shiroFilterFactoryBean
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean=new ShiroFilterFactoryBean();
//设置安全管理
shiroFilterFactoryBean.setSecurityManager(securityManager);
//添加shiro的内置过滤器
/**
* anon: 无需认证就可以访问
* authc:必须认证了才让访问
* user:必须拥有 记住我 功能才能用
* perms:拥有对某个资源的权限才能访问
* role:拥有某个角色权限才能访问
*/
//拦截
Map<String, String> filterMap=new LinkedHashMap<>();
//授权,正常的情况下,没有授权就会跳到未授权的页面
filterMap.put("/user/update","perms[user:update]");
filterMap.put("/user/add","perms[user:add]");
// filterMap.put("/user/add","anon");
// filterMap.put("/user/update","authc");
//filterMap.put("/user/*","authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
//设置登录的请求
shiroFilterFactoryBean.setLoginUrl("/toLogin");
//设置未授权的页面
shiroFilterFactoryBean.setUnauthorizedUrl("/noauth");
return shiroFilterFactoryBean;
}
//DefaultWebSecurityManager:2
@Bean(name="securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//关联UserRealm
securityManager.setRealm(userRealm);
return securityManager;
}
//创建realm对象,需要自定义:1
@Bean
public UserRealm userRealm(){
return new UserRealm();
}
//整合shiroDialet:用来整合shiro thymeleaf
@Bean
public ShiroDialect getShiroDialect(){
return new ShiroDialect();
}
}
- AuthorizingRealm
package com.lxf.config;
import com.lxf.pojo.User;
import com.lxf.service.UserServiceImpl;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
public class UserRealm extends AuthorizingRealm {
@Autowired
private UserServiceImpl userService;
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了授权");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//拿到当前登录的这个对象
User user = (User) SecurityUtils.getSubject().getPrincipal();
info.addStringPermission(user.getPerms());
return info;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("执行了认证");
UsernamePasswordToken userToken = (UsernamePasswordToken) token;
//用户名:密码~ 数据库中取
User user=userService.queryUserByName(userToken.getUsername());
if(user==null){//没有这个人
return null;
}
Subject subject = SecurityUtils.getSubject();
subject.getSession().setAttribute("loginUser",user);
//可以加密:12345MD5加密:E10ADC3949BA59ABBE56E057F20F883E,MD5盐值加密:E10ADC3949BA59ABBE56E057F20F883Eusername
//密码认证,shiro做,加密了
return new SimpleAuthenticationInfo(user,user.getPwd(),"");
}
}
2.新建controller文件夹
- 新建Mycontroller类
package com.lxf.controller;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class MyController {
@RequestMapping({"/","/index"})
public String toIndex(Model model){
model.addAttribute("msg","Hello,Shiro!");
return "index";
}
@RequestMapping("/user/add")
public String toAdd(Model model){
return "user/add";
}
@RequestMapping("/user/update")
public String toUpdate(Model model){
return "user/update";
}
@RequestMapping("/toLogin")
public String toLogin(Model model){
return "login";
}
@RequestMapping("/login")
public String login(String username,String password,Model model){
//获取当前的用户
Subject subject = SecurityUtils.getSubject();
//封装用户的登录数据
UsernamePasswordToken token = new UsernamePasswordToken(username,password);
try {
subject.login(token);//执行登录方法,如果没有异常说明ok了
return "index";
} catch (UnknownAccountException e) {//用户名不存在
model.addAttribute("msg","用户名错误");
return "login";
}catch (IncorrectCredentialsException e) {//密码不存在
model.addAttribute("msg","密码错误");
return "login";
}
}
@RequestMapping("/noauth")
@ResponseBody
public String unauthorized(){
return "未经授权无法访问此页面";
}
}
3.新建mapper文件夹
- 新建UserMapper类
package com.lxf.mapper;
import com.lxf.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;
@Mapper
@Repository
public interface UserMapper {
User queryUserByName(String name);
}
4.新建pojo文件夹
- 新建User类
package com.lxf.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@AllArgsConstructor
@NoArgsConstructor
@Data
public class User {
private Integer id;
private String name;
private String pwd;
private String perms;
}
5.新建mybatis数据库(mysql)
- 新建表:
create table if not exists user(
id number,
name varchar(255),
pwd varchar(255),
perms varchar(255)
)ENGINE=INNODB DEFAULT CHARSET=uft8
6.新建service文件夹
- 新建UserService接口
package com.lxf.service;
import com.lxf.pojo.User;
public interface UserService {
User queryUserByName(String name);
}
- 新建UserServiceImpl类
package com.lxf.service;
import com.lxf.mapper.UserMapper;
import com.lxf.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService{
@Autowired
private UserMapper userMapper;
@Override
public User queryUserByName(String name) {
return userMapper.queryUserByName(name);
}
}
7.resource目录下新建mybatis.mapper文件夹
- 新建UserMapper.xml文件
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lxf.mapper.UserMapper">
<select id="queryUserByName" parameterType="String" resultType="User">
select * from user where name = #{name}
</select>
</mapper>
8.templates目录下新建user文件夹
- 新建add.html页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>增加页面</title>
</head>
<body>
<h1>add</h1>
</body>
</html>
- 新建update.html页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>更新页面</title>
</head>
<body>
<h1>update</h1>
</body>
</html>
9.templates下新建index.html和login.html页面
-
index.html
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro"> <head> <meta charset="UTF-8"> <title>首页</title> </head> <body> <h1>首页</h1> <p th:if="${session.loginUser==null}"> <a th:href="@{/toLogin}">登录</a> </p> </body> <p th:text="${msg}"></p> <div shiro:hasPermission="user:add"> <a th:href="@{/user/add}">add</a> </div> <div shiro:hasPermission="user:update"> <a th:href="@{/user/update}">update</a> </div> </html>
-
login.html:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>登陆</title>
</head>
<body>
<p th:text="${msg}" style="color: red"></p>
<form th:action="@{/login}">
<p>用户名:<input type="text" name="username"></p>
<p>密码:<input type="text" name="password"></p>
<p><input type="submit" value="登录"></p>
</form>
</body>
</html>
10.配置文件
- application.properties文件:
mybatis.configuration.map-underscore-to-camel-case=true
mybatis.mapper-locations=classpath:mybatis/mapper/*.xml
mybatis.type-aliases-package=com.lxf.pojo
- application.yml文件:
spring:
datasource:
username: root
password: root
#?serverTimezone=UTC解决时区的报错
url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
#Spring Boot 默认是不注入这些属性值的,需要自己绑定
#druid 数据源专有配置
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
#配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入
#如果允许时报错 java.lang.ClassNotFoundException: org.apache.log4j.Priority
#则导入 log4j 依赖即可,Maven 地址: https://mvnrepository.com/artifact/log4j/log4j
filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500