1 Hello World
笔记是B站狂神说相关视频的学习笔记
1.1 Spring是怎么简化开发的?
1、基于POJO的轻量级和最小侵入性编程,所有东西都是bean;
2、通过IOC,依赖注入(DI)和面向接口实现松耦合;
3、基于切面(AOP)和惯例进行声明式编程;
4、通过切面和模版减少样式代码,RedisTemplate,xxxTemplate;
1 2 什么是SpringBoot?
- 简化配置:Springboot是对Spring的进一步封装,基于注解开发,舍弃了笨重的xml配置,使用yml或者properties配置
- 产品级独立运行:每一个工程都可以打包成一个jar包,内置了Tomcat和Servlet容器,可以独立运行
- 强大的场景启动器:每一个特定场景下的需求都封装成了一个starter,只要导入了这个starter就有了这个场景所有的一切
- 学习路线
1.3 Hello World
1 开发环境
- JDK-1.8
- Maven-3.6.1
- Springboot-2.2.6
2 IDEA创建
- 选择spring initalizr,初学勾选
Web
即可
3 编写一个HelloWordController
- handler方法:
- 注意:必须和主启动类在同一级目录下创建包和方法才能生效
- 如果默认扫描包不一致,需要在主启动类上使用@ComponentScan指定扫描包
- 注意:必须和主启动类在同一级目录下创建包和方法才能生效
@RestController public class HelloWordController { /* springBoot的contextPath默认是""空字符串,不用想SSM配置Tomcat时候配置工程名,简化了配置 访问地址:http://localhost:8080/hello */ @RequestMapping("/hello") public String hello() { return "hello world"; } }
- 主启动类
- 启动主启动类,访问http://localhost:8080/hello
// 将当前类标记成一个Springboot应用 @SpringBootApplication public class SpringBoot_01_Application { public static void main(String[] args) { SpringApplication.run(SpringBoot_01_Application.class, args); } }
4 打包jar发布
- 测试用例不让打包,删除测试用例打包
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> <!-- 在工作中,很多情况下我们打包是不想执行测试用例的 可能是测试用例不完事,或是测试用例会影响数据库数据 跳过测试用例执 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <configuration> <!--跳过项目运行测试用例--> <skipTests>true</skipTests> </configuration> </plugin> </plugins> </build>
- <stron>
- target中就会出现xxx.jar包,显示打包成功
2 pom.xml
2.1 父工程
- 父工程师官网默认设置好了的资源和依赖,已有的,我们配置时就不用写版本号
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.6.RELEASE</version> <relativePath/> </parent>
- 点spring-boot-starter-parent进去看,就会看到官方配置好的依赖和资源版本号
2.2 starter启动器
- SpringBoot将所有的功能场景都抽取出来,做成一个个的starter (启动器),只需要在项目中引入这些starter即可,所有相关的依赖都会导入进来 , 我们要用什么功能就导入什么样的场景启动器即可 ;我们未来也可以自己自定义 starter;
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
3 主启动类
@SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
- SpringBootApplication注解:
// 主启动类的注解,一个tab表示点进去一次 @SpringBootApplication @SpringBootConfiguration// 表明是一个SpringBoot配置文件 @Configuration// 再次说明这是一个Spring配置 @EnableAutoConfiguration // 自动配置 @AutoConfigurationPackage @Import(AutoConfigurationPackages.Registrar.class) @Import(AutoConfigurationImportSelector.class) @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
3.1 @SpringBootApplication
@SpringBootConfiguration
- 说明里面的 @Component 这就说明,启动类本身也是Spring中的一个组件而已,负责启动应用!
@SpringBootApplication @SpringBootConfiguration// 表明是一个SpringBoot配置文件 @Configuration// 再次说明这是一个Spring配置
@EnableAutoConfiguration
- @AutoConfigurationPackage
- @AutoConfigurationPackages.Registrar.class:
- Registrar.class作用就是将主启动所在的包及以下的所有子包都扫描进spring容器中
- @AutoConfigurationPackages.Registrar.class:
@AutoConfigurationPackage @Import(AutoConfigurationImportSelector.class)
- @Import(AutoConfigurationImportSelector.class) ,找到
getCandidateConfigurations
方法getCandidateConfigurations
方法:获得候选的配置
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()); Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you " + "are using a custom packaging, make sure that file is correct."); return configurations; }
- 找到
SpringFactoriesLoader.loadFactoryNames
,点进去,找到loadSpringFactories
方法SpringFactoriesLoader
类中的预定义的自动装配路径FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) { String factoryTypeName = factoryType.getName(); return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList()); } private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) { //获得classLoader MultiValueMap<String, String> result = cache.get(classLoader); if (result != null) { return result; } ... }
- 多次出现的spring.factories,就是预定好的加载配置文件
@ComponentScan
- 自动扫描并加载符合条件的组件bean,并将这个组件bean注入到IOC容器中
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
3.2 自动装配
自动配置真正实现是从classpath中搜寻所有的
META-INF/spring.factories
配置文件 ,并将其中对应的org.springframework.boot.autoconfigure.包下的配置项,通过反射实例化为对应标注了 @Configuration的JavaConfig形式的IOC容器配置类 , 然后将这些都汇总成为一个实例并加载到IOC容器中。用户如何书写yml配置,需要去查看
META-INF/spring.factories
下的某自动配置,如HttpEncodingAutoConfiguration
EnableConfigrutionProperties(xxx.class)
:表明这是一个自动配置类,加载某些配置XXXProperties.class
:封装配置文件中的属性,yam中需要填入= 它指定的前缀+方法
3.3 工作原理总结
- 读取
spring.properties
文件- SpringBoot在启动的时候从
spring-boot-autoConfigure.jar
包下的的META-INF/spring.factories中获取EnableAutoConfiguration
属性的值加载自动配置类 - 将这些值作为自动配置类导入容器 , 自动配置类就生效 , 帮我们进行自动配置工作;
- SpringBoot在启动的时候从
- 加载
XXXProperties
类- 根据自动配置类中指定的xxxxProperties类设置自动配置的属性值,开发者可以根据该类在yml配置文件中修改自动配置
- 根据
@ConditionalXXX
注解决定加载哪些组件- Springboot通过该注解指定组件加入IOC容器时锁需要具备的特定条件。这个组件会在满足条件时候加入到IOC容器内
3.4 SpringApplication.run(xxx.class)
- 推断应用的类型是普通的项目还是Web项目
- 查找并加载所有可用初始化器 , 设置到initializers属性中
- 找出所有的应用程序***,设置到listeners属性中
- 推断并设置main方法的定义类,找到运行的主类
public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) { // ...... this.webApplicationType = WebApplicationType.deduceFromClasspath(); this.setInitializers(this.getSpringFactoriesInstances(); this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class)); this.mainApplicationClass = this.deduceMainApplicationClass(); }
4 application.yml
4.1 传统的xml配置以标记语言为中心
<server> <port>8081<port> </server>
4.2 yaml配置以数据为中心
- yml: y are not markup langure . y 不是一个标记语言,而是以数据为中心
server: prot: 8080 servlet: # 原先的Tomcat工程路径在这里修改 context-path: /laosong
4.3 yml语法
- 空格不能省略,键值对中间必须要有一个空格
- 以缩进来控制层级关系,只要是左边对齐的一列数据都是同一个层级的。
- 属性和值的大小写都是十分敏感的。
4.4 yml基本类型写入
字面值:普通字符换、数值、布尔类型,直接写成k:v,字符串默认不用加‘’或“”
name: zhangsan
“ ” 双引号,不会转义字符串里面的特殊字符 , 特殊字符会作为本身想表示的意思;
比如 :name: "kuang \n shen" 输出 :kuang 换行 shen
'' 单引号,会转义特殊字符 , 特殊字符最终会变成和普通字符一样输出
比如 :name: ‘kuang \n shen’ 输出 :kuang \n shen
注意:设置数据库密码如果是0开头,SpringBoot会默认按照八进制来解析,yml配置时加上引号,如
'01111'
对象、Map:属性值必须和Bean中的对应一致
Student: name: zhangsan age: nan
数组:使用 - 表示一个元素
Countries: - Chine - USA
4.5 yml配置bean属性,导入依赖
创建一个application.yml
person: name: qinjiang age: 3 happy: false birth: 2000/01/01 maps: {k1: v1,k2: v2} lists: - code - girl - music dog: name: 旺财 age: 1
@ConfigurationProperties(prefix = "person")
默认springboot项目会报错,提示找不到注解配置,需要导包,然后Idea重启
@ConfigurationProperties(prefix = "person")默认是从全局配置获取,文件名只能是application.yml
<!-- 1 导入配置文件处理器,配置文件进行绑定就会有提示,需要重启 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency>
@Component @Data @AllArgsConstructor @NoArgsConstructor // 2 绑定配置文件:指定yml配置中的配置名称装配 @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; } @Data @AllArgsConstructor @NoArgsConstructor public class Dog { private String name; private Integer age; }
测试
// 3 编写测试类 @SpringBootTest class DemoApplicationTests { @Autowired Person person; @Test void personTest() { System.out.println(person); } }
控制台显示
4.6 加载指定配置文件
@ConfigurationProperties(prefix = "person"):默认是从全局配置获取,
- 文件名只能是application.yml
- 优点是只需要配置prefix=key会自定匹配相对应的属性
@PropertySource :加载指定的配置文件;
- 定义一个person.yml,yml中可以使用**{rondom.int}**来随机生成数
person: name: qinjiang ${random.uuid} id: ${random.int}
- 指定yml,缺点是需要手动注入属性${"XXX"}
@Data @AllArgsConstructor @NoArgsConstructor @Component // 指定配置yml文件 @PropertySource(value = "classpath:person.yml") public class Person1 { @Value("${name}") private String name; @Value("${id}") private String id; }
4.7 小结
1. yml的好处? - **实现松散绑定?** - yml中的first-name = bean中的firstName - @ConfigurationProperties只需要写一次即可 , @Value则需要每个字段都添加 - **JSR303数据校验** - 复杂类型封装
4.8 JSR303数据校验
就是一种数据校验格式,在类上绑定
@Validated
,在属性上使用指定的参数如@Email(message="邮箱格式错误")
常见的数据校验参数,包是
javax.validation.constraints
// JSR303常用校验 @NotNull(message="名字不能为空") private String userName; @Max(value=120,message="年龄最大不能查过120") private int age; @Email(message="邮箱格式错误") private String email; // 空检查 @Null 验证对象是否为null @NotNull 验证对象是否不为null, 无法查检长度为0的字符串 @NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格. @NotEmpty 检查约束元素是否为NULL或者是EMPTY. // Booelan检查 @AssertTrue 验证 Boolean 对象是否为 true @AssertFalse 验证 Boolean 对象是否为 false // 长度检查 @Si***=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内 @Length(min=, max=) string is between min and max included. // 日期检查 @Past 验证 Date 和 Calendar 对象是否在当前时间之前 @Future 验证 Date 和 Calendar 对象是否在当前时间之后 @Pattern 验证 String 对象是否符合正则表达式的规则
4.9 application.yml可以安装的位置
- file:./config和file:./config
- file:config/appplication.yml
- file:application.yml
- resources:/config/application.yml
- resources:application.yml
- 执行的优先级依次递减:
4.10 yml多配置
- 来分割多个yml配置,并且用profiles来命名
- 一个yml文件可以使用---来区分配置,比properties强大之一
- 激活dev的配置
server: port: 8080 # 选择使用哪些配置 spring: profiles: active: dev # 激活dev配置 --- server: port: 8081 spring: profiles: test --- server: port: 8082 spring: profiles: dev
5 Web开发
要解决的问题:
导入静态资源
指定首页
jsp。模版引擎Thymeleaf
装配扩展SpringMVC
增删改查
拦截器和国际化
1 静态资源源码
- idea按两下shift,搜索
webautoConfiguration - WebMvcAutoConfigurationAdapter - addResourceHandlers
@Override public void addResourceHandlers(ResourceHandlerRegistry registry) { if (!this.resourceProperties.isAddMappings()) { logger.debug("Default resource handling disabled"); return; } Duration cachePeriod = this.resourceProperties.getCache().getPeriod(); CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl(); // 第一种方式 webjars if (!registry.hasMappingForPattern("/webjars/**")) { customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**") .addResourceLocations("classpath:/META-INF/resources/webjars/") .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl)); } // 第二种方式 String staticPathPattern = this.mvcProperties.getStaticPathPattern(); if (!registry.hasMappingForPattern(staticPathPattern)) { customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern) .addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations())) .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl)); } }
2 三个静态资源的位置
第一个if:没有
第二个if:webjars不推荐使用,官网导包,url:META-INF/resources/webjars/
第三个if:优先级:resources > static(默认) > public
- classPath :/**
- classpath:/META-INF/resources/
- classpath:/resources/
- classpath:/static/(默认创建)
- classpath:/public/
private String staticPathPattern = "/**"; private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/META-INF/resources/","classpath:/resources/","classpath:/static/","classpath:/public/" }; //以上4个人位置加装静态资源
3 首页
WebMvcAutoConfiguration
下的 ,观察源码知:在静态资源目录下创建index.html即可
private Resource getIndexHtml(String location) { return this.resourceLoader.getResource(location + "index.html"); }
4 Thymeleaf
- 注意:template/**下的任何页面都需要Controller来跳转才能访问,不能直接访问
导入依赖
<!--thymeleaf模版引擎在,都是基于3.x版本开发--> <dependency> <groupId>org.thymeleaf</groupId> <artifactId>thymeleaf-spring5</artifactId> </dependency> <dependency> <groupId>org.thymeleaf.extras</groupId> <artifactId>thymeleaf-extras-java8time</artifactId> </dependency>
查看源码,在template下使用
@ConfigurationProperties(prefix = "spring.thymeleaf") public class ThymeleafProperties { private static final Charset DEFAULT_ENCODING = StandardCharsets.UTF_8; // 前缀 public static final String DEFAULT_PREFIX = "classpath:/templates/"; // 后缀 public static final String DEFAULT_SUFFIX = ".html"; ... }
第一次使用,传msg,接受:th:text="${msg}",(thymeleaf独有的语法)
@Controller public class ThymeleafController { @RequestMapping("/thymeleaf") public String thy(Model model){ model.addAttribute("msg","<p>hello Thymeleaf</p>"); model.addAttribute("users", Arrays.asList("张三","李四","王五")); // 后缀默认是 .html return "thymeleafTest"; } }
<!DOCTYPE html> <!--thymeleaf约束--> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>thymeleaf</title> </head> <body> <!--所有的html元素都可以被Th接管:th:xx元素名--> <!--test=默认是转义文本--> <h1 th:text="${msg}"></h1> <!--utest=默认是不转义文本--> <h1 th:utext="${msg}"></h1> <h1> <!--遍历往前写item--> 遍历一 推荐这么使用: <h2 th:each="user:${users}" th:text="com.wenyibi.futuremail.model.User@55e55bdb"></h2><br> 遍历二: <h2 th:each="user:${users}" >[[com.wenyibi.futuremail.model.User@55e55bdb]]</h2> </h1> </body> </html>
Thymeleaf常用语法
<h1> <h2 th:each="user:${users}" th:text="com.wenyibi.futuremail.model.User@55e55bdb"></h2> </h1>
5 SpringMVC扩展
implements WebMvcConfigurer
,重写一些方法
/** * 扩展SpringMVC:实现WebMvcConfigurer接口,重写某些方法让我们实现一些新的配置 * 注意:不能再使用@EnableWebMvc,这个方***让扩展方法失效 */ @Configuration public class MyMVCConfig implements WebMvcConfigurer { /** * 视图跳转 访问/ssl,跳转到config.html */ @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController( "/ssl").setViewName("config"); } }
6 员工管理系统
- 说明:这里未连接数据库数据,使用的是虚假的java
1 首页
- 建议使用扩展MVC配置的首页访问方式
@Configuration public class MyConfig implements WebMvcConfigurer { @Override public void addViewControllers(ViewControllerRegistry registry) { // 访问首页,建议使用扩展MVC registry.addViewController("/").setViewName("index"); registry.addViewController("/index").setViewName("index"); } }
template下的文件必须配置Controller跳转才能访问
thymeleaf语法:
- 导入依赖
- 导入约束:
xmlns:th="http://www.thymeleaf.org
- 前端url表达式语法: @{...} ,/表示默认默认从static下寻找
<!--thymeleaf约束 --> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <!-- / 从static开始 --> <link th:href="@{/css/bootstrap.min.css}" rel="stylesheet"> <link th:href="@{/css/signin.css}" rel="stylesheet">
2 国际化
- Idea中setting中“file-encoding”修改项目编码、配置文件编码均为“utf-8”,否则配置文件中文会显示乱码
- resources下配置i18n文件,index.html前端页面使用
th:text="#{login.btn}"
等接收配置文件里的参数
<!DOCTYPE html> <html lang="zh" 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> <!-- / 从static开始 --> <link th:href="@{/css/bootstrap.min.css}" rel="stylesheet"> <link th:href="@{/css/signin.css}" rel="stylesheet"> </head> <body class="text-center"> <form class="form-signin" action="/user/login"> <img class="mb-4" th:src="@{/img/bootstrap-solid.svg}" alt="" width="72" height="72"> <!--配置国际化 th:text="#{login.tip}--> <h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}"></h1> <!--配置登录失败信息--> <P style="color: red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></P> <input name="username" value="admin" type="text" class="form-control" th:placeholder="#{login.username}" required="" autofocus=""> <input name="password" value="123456" type="password" class="form-control" th:placeholder="#{login.password}" required=""> <div class="checkbox mb-3"> <label> <input type="checkbox" value="remember-me" th:text="#{login.remember}"> </label> </div> <button class="btn btn-lg btn-primary btn-block" type="submit" th:text="#{login.btn}"></button> <p class="mt-5 mb-3 text-muted">© 2020-2021</p> <!--国际化消息,配置MyLocalResolver,传递解析url thymeleaf用()表示?xx=xx --> <a class="btn btn-sm" th:href="@{/index(language='zh_CN')}">中文</a> <a class="btn btn-sm" th:href="@{/index(language='en_US')}">English</a> </form> </body> </html>
- 自定义一个MyLocalResolver继承LocalResolver
- 前端传递参数:
th:href="@{/index(language='zh_CN')}"
,thymeleaf中的?key=value用(key=value)简化
- 前端传递参数:
public class MyLocalResolver implements LocaleResolver { //解析国际化请求 @Override public Locale resolveLocale(HttpServletRequest request) { String language = request.getParameter("language"); System.out.println("语言:" + language); Locale locale = Locale.getDefault(); if (!Strings.isEmpty(language)) { String[] split = language.split("_"); locale = new Locale(split[0], split[1]); } return locale; } @Override public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) { } }
- 将组件注册进IOC容器
- 这里通过扩展的MVC来注册进组件,返回类型是
LocaleResolver
- 这里通过扩展的MVC来注册进组件,返回类型是
@Configuration public class MyConfig implements WebMvcConfigurer { // 国际化解析器注册进组件 @Bean public LocaleResolver localeResolver() { return new MyLocalResolver(); } // 首页视图解析器 @Override public void addViewControllers(ViewControllerRegistry registry) { // 访问首页,建议使用扩展MVC registry.addViewController("/").setViewName("index"); registry.addViewController("/index").setViewName("index"); } }
3 拦截器
- 继承处理拦截器
implements HandlerInterceptor
- 这里假设获取session中的username,如果存在就表示登录成功;不存在就表示登录失败,request中存失败msg
/** * 自定义拦截器 */ public class LoginHandlerInterceptor implements HandlerInterceptor { /* 登录拦截器 */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 获取用户名称,登录成功之后,应该有用户的session Object username = request.getSession().getAttribute("username"); // 登录失败返会登录页 if (username == null) { request.setAttribute("msg", "没有权限,请重新登录"); request.getRequestDispatcher("/index").forward(request, response); return false; } else { return true; } } }
- Myconfig中配置拦截的请求
- 放心首页和表单提交地址,以及静态资源
//登录拦截器 @Override public void addInterceptors(InterceptorRegistry registry) { // 配置自定义拦截器 registry.addInterceptor(new LoginHandlerInterceptor()) .addPathPatterns("/**") .excludePathPatterns("/index", "/", "/user/login", "/css/**", "/img/**", "/js/**"); }
4 CRUD
1 提取公共页面
th:replace
:是替换全部元素,原有模块已经不存在;th:insert
:是加入元素,原有模块还是存在
<!--公共页面提供fragment--> <nav th:fragment="navbar"> <!--需要公共页面的标签使用,commons包下的comms.html是提出的公共页面--> <div th:replace="~{commons/comms::navbar}"></div> <div th:insert="~{commons/comms::navbar}"></div>
- 字体高亮,普通页面传递参数
(active='main.html')
给公共页面- 公共页面使用
"${active=='main.html'}?'nav-link active':'nav-link'"
查看是否显示高亮
- 公共页面使用
<div th:replace="~{commons/comms::sidebar(active='main.html')}"></div>
<a th:class="${active=='main.html'}?'nav-link active':'nav-link'" th:href="@{/index}">
2 展示员工列表
- 不连接数据库,提供假数据,部门和员工
@Repository public class DepartmentDao { // 模拟数据库中的数据,无需创建数据库 private static Map<Integer, Department> departments = null; static { departments = new HashMap<>(); 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> getDepartmens() { return departments.values(); } // 通过id获取部门 public Department getDepartmentById(Integer id) { return departments.get(id); } }
@Repository public class EmpDao { @Autowired private DepartmentDao departmentDao; // 模拟数据库中的数据,无需创建数据库 private static Map<Integer, Employee> employees = null; static { employees = new HashMap<>(); employees.put(1001, new Employee(1001, "AA", "123456@qq.com", 0, new Department(101, "教学部"))); employees.put(1002, new Employee(1002, "BB", "123456@qq.com", 1, new Department(102, "市场部"))); employees.put(1003, new Employee(1003, "CC", "123456@qq.com", 0, new Department(103, "教研部"))); employees.put(1004, new Employee(1004, "DD", "123456@qq.com", 1, new Department(104, "运营部"))); employees.put(1005, new Employee(1005, "EE", "123456@qq.com", 1, new Department(105, "后勤部"))); } // 主键自增 private static Integer initId = 1006; // 增加一个员工 public void saveEmp(Employee employee) { if (employee.getId() == null) { employee.setId(initId++); } employee.setDepartment(departmentDao.getDepartmentById(employee.getDepartment().getId())); employees.put(employee.getId(), employee); } // 删除员工 public void deleteEmppyeeById(Integer id) { employees.remove(id); } // 查询全部员工信息 public Collection<Employee> getAllEmp() { return employees.values(); } // 通过id查询员工 public Employee getEmployeeById(Integer id) { return employees.get(id); } }
- Controller
@Controller public class EmployeeController { @Autowired private EmpDao employeeDao; @RequestMapping("/emps") public String showEmployeeList(Model model) { Collection<Employee> employees = employeeDao.getAllEmp(); model.addAttribute("emps", employees); return "emp/list"; } }
- list.html
- 遍历:
th:each="emp:${emps}"
- 日期格式化:
th:text="'女':'男'"
和th:text="${#dates.format(emp.getBirth(),'yyyy-MM-dd HH:mm:ss')}"
- 遍历:
<table class="table table-striped table-sm"> <thead> <tr> <th>id</th> <th>lastName</th> <th>email</th> <th>gender</th> <th>department</th> <th>birth</th> <th>操作</th> </tr> </thead> <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="'女':'男'"></td> <td th:text="${emp.getDepartment().getDepartName()}"></td> <!--日期需要改变格式--> <td th:text="${#dates.format(emp.getBirth(),'yyyy-MM-dd HH:mm:ss')}"></td> <td> <button class="btn btn-sm btn-primary">编辑</button> <button class="btn btn-sm btn-danger">删除</button> </td> </tr> </tbody> </table>
3 添加员工
- list.html,添加跳转
<!--添加员工--> <h2> <!--默认get请求,第一次回显员工中的部门信息使用默认的get请求--> <a class="btn btn-sm btn-success" th:href="@{/emp}">添加员工</a> </h2>
add.html
部门信息,需要先查找出所有部门信息,再回显表单,并且提交是department.id
前端日期格式thymeleaf默认是yyyy/mm/dd,修改application.yml
spring: mvc: # 修改日期格式,默认是yyyy/MM/dd date-format: yyyy-MM-dd
<form th:action="@{/emp}" method="post"> <div class="form-group"> <label>LastName</label> <input name="lastName" value="tom" type="text" class="form-control" id="exampleInputEmail1" placeholder="lastName"> </div> <div class="form-group"> <label>Email</label> <input name="email" value="123456@qq.com" type="email" class="form-control" id="exampleInputPassword1" placeholder="email"> </div> <div class="form-group"> <label>Gender</label> <div class="form-check form-check-inline"> <input class="form-check-input" type="radio" value="1" name="gender"/> <label class="form-check-label">男</label> </div> <div class="form-check form-check-inline"> <input class="form-check-input" type="radio" value="0" name="gender"/> <label class="form-check-label">女</label> </div> </div> <div class="form-group"> <label>Department</label> <!--提交的Department的是部门id--> <select name="department.id" class="form-control"> <option th:each="dept:${departments}" th:text="${dept.getDepartName()}" th:value="${dept.getId()}"></option> </select> </div> <div class="form-group"> <label>Birth</label> <input name="birth" value="2020-5-27" type="text" class="form-control" placeholder="yyyy-MM-dd"> </div> <button type="submit" class="btn btn-default">添加</button> </form>
- Controller
@Controller public class EmployeeController { @Autowired private EmpDao employeeDao; @Autowired private DepartmentDao departmentDao; @RequestMapping("/emps") public String showEmployeeList(Model model) { Collection<Employee> employees = employeeDao.getAllEmp(); model.addAttribute("emps", employees); return "emp/list"; } // 跳转到add.html @GetMapping("/emp") public String toAddEmpPage(Model model) { // 查出所有部门的信息 Collection<Department> departments = departmentDao.getDepartments(); model.addAttribute("departments", departments); return "emp/add"; } // 提交添加员工 @PostMapping("/emp") public String toAddEmp(Employee employee) { // 保存员工信息 employeeDao.saveEmp(employee); return "redirect:/emps"; } }
4 修改员工
- list.html点击“修改”
<a class="btn btn-sm btn-primary" th:href="@{/emp/}+${emp.getId()}">编辑</a>
- update.html
- 隐藏域存empId:
< input type="hidden" th:value="${emp.getId()}">
- option:
th:selected="${dept.getId()==emp.getDepartment().getId()}"
- 日期格式转换:
th:value="${#dates.format(emp.getBirth(),'yyyy-MM-dd HH:mm:ss')}"
- 隐藏域存empId:
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4"> <form th:action="@{/updateEmp}" method="post"> <!--隐藏查出来的empId--> <input type="hidden" th:value="${emp.getId()}"> <div class="form-group"> <label>LastName</label> <input name="lastName" th:value="${emp.getLastName()}" type="text" class="form-control" id="exampleInputEmail1" placeholder="lastName"> </div> <div class="form-group"> <label>Email</label> <input name="email" th:value="${emp.getEmail()}" type="email" class="form-control" id="exampleInputPassword1" placeholder="email"> </div> <div class="form-group"> <label>Gender</label> <div class="form-check form-check-inline"> <input th:checked="${emp.getGender()==1}" class="form-check-input" type="radio" value="1" name="gender"/> <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" value="0" name="gender"/> <label class="form-check-label">女</label> </div> </div> <div class="form-group"> <label>Department</label> <!--提交的value是id--> <select name="department.id" class="form-control"> <!--回显:部门id等于员工部门的id--> <option th:each="dept:${departments}" th:selected="${dept.getId()==emp.getDepartment().getId()}" th:text="${dept.getDepartName()}" th:value="${dept.getId()}"></option> </select> </div> <div class="form-group"> <label>Birth</label> <!--日期回显,需要更改默认格式--> <input th:value="${#dates.format(emp.getBirth(),'yyyy-MM-dd HH:mm:ss')}" name="birth" type="text" class="form-control" placeholder="yyyy-MM-dd"> </div> <button type="submit" class="btn btn-default">修改</button> </form> </main>
- Controller
// 修改员工,回显员工表 @GetMapping("/emp/{id}") public String toUpdateEmpPage(@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 "emp/update"; } // 修改员工 @PostMapping("/updateEmp") public String updateEmp(Employee employee) { employeeDao.saveEmp(employee); return "redirect:/emps"; }
5 删除员工
- list.html
<a class="btn btn-sm btn-danger" th:href="@{/deleteEmp/}+${emp.getId()}">删除</a>
- Controller
// 删除员工 @GetMapping("/deleteEmp/{id}") public String deleteEmp(@PathVariable("id") Integer id) { employeeDao.deleteEmpById(id); return "redirect:/emps"; }
6 404错误
- springBoot封装了,在template中创建error包,下面放404.html,会自动出错时跳转到该页面
7 注销功能
- 注销标签
<a class="nav-link" th:href="@{/user/logout}">注销</a>
- Controller:注销掉session
@RequestMapping("/user/logout") public String logout(HttpSession session) { // 注销session session.invalidate(); return "redirect:/index"; }
8 如何写一个网站
前端
- 模版:自己网站搜
- 框架:组件,需要自己手动拼接:BootStrap,Layui,semantic-ui
设计数据库(真正的难点)
前端让他能够自动运行,独立化工程
数据接口如何对接:json,对象
前后端联调
- 前端:自己能够通过“’框架”网站组合出一个网页
- 后端:必须要有自己熟悉的一个后台模版,99%公司会让你自己写:推荐X-admin网站模版
7 整合JDBC
对于数据访问层,无论是SQL还是NOSQL,SpringBoot都是采用Spring Data方式统一处理
依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.2</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency>
- application.yml
spring: datasource: username: root password: admin url: jdbc:mysql://localhost:3306/mybatis?userUnicode=true&chacacterEncoding=utf-8&serverTimezone=UTC # Springboot使用 com.mysql.cj.jdbc.Driver 针对Mysql8以上,5可能会有bug driver-class-name: com.mysql.cj.jdbc.Driver
- 测试查看默认数据源:hikari,据说是目前最快的数据源连接
@SpringBootTest class JdbcApplicationTests { /** * SpringBoot只要配置了数据源,就自动将数据源封装进IOC容器,用户无需配置数据源组件,直接取出 */ @Autowired DataSource dataSource; @Test void contextLoads() throws SQLException { // 查看使用的数据源:目前最快的数据源:hikari.HikariDataSource // System.out.println(dataSource.getClass()); // 获取数据库连接 Connection connection = dataSource.getConnection(); System.out.println(connection); // 关闭数据源 connection.close(); } }
- controller
@Controller public class JdbcController { @Autowired JdbcTemplate jdbcTemplate; // 查询出user表中的所有信息,没有实体类,可以使用万能的map来存 @GetMapping("/userList") @ResponseBody public List<Map<String, Object>> userList() { String sql = "select * from user"; List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql); return maps; } }
8 整合Druid
- Druid是阿里开源的数据源,自动整合了日志监控
- 依赖
<dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.22</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency>
- DruidConfig
- Druid内置了一个监控工页面,可以通过配置监控后台进行访问:http://localhost:8080/druid/index.html
@Configuration public class DruidConfig { //将自定义的Druid配置进IOC容器 @ConfigurationProperties(prefix = "spring.datasource") @Bean public DataSource getDataSource() { return new DruidDataSource(); } //配置 Druid 监控管理后台的Servlet; //内置 Servlet 容器时没有web.xml文件,所以使用 Spring Boot 的注册 Servlet 方式 @Bean public ServletRegistrationBean statViewServlet() { ServletRegistrationBean bean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*"); // 这些参数可以在 com.alibaba.druid.support.http.StatViewServlet // 的父类 com.alibaba.druid.support.http.ResourceServlet 中找到 Map<String, String> initParams = new HashMap<>(); initParams.put("loginUsername", "admin"); //后台管理界面的登录账号 initParams.put("loginPassword", "123456"); //后台管理界面的登录密码 //后台允许谁可以访问 //initParams.put("allow", "localhost"):表示只有本机可以访问 //initParams.put("allow", ""):为空或者为null时,表示允许所有访问 initParams.put("allow", ""); //deny:Druid 后台拒绝谁访问 //initParams.put("kuangshen", "192.168.1.20");表示禁止此ip访问 //设置初始化参数 bean.setInitParameters(initParams); return bean; } //配置 Druid 监控 之 web 监控的 filter //WebStatFilter:用于配置Web和Druid数据源之间的管理关联监控统计 @Bean public FilterRegistrationBean webStatFilter() { FilterRegistrationBean bean = new FilterRegistrationBean(); bean.setFilter(new WebStatFilter()); //exclusions:设置哪些请求进行过滤排除掉,从而不进行统计 Map<String, String> initParams = new HashMap<>(); initParams.put("exclusions", "*.js,*.css,/druid/*,/jdbc/*"); bean.setInitParameters(initParams); //"/*" 表示过滤所有请求 bean.setUrlPatterns(Arrays.asList("/*")); return bean; } }
9 整合Mybatis
- 依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.2</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency>
- application.yml
- 注意:非常重要,解决绑定异常:mapper-locations中mapper.xml最好和接口的包名路径一致,避免出现问题
# 整合数据源 spring: datasource: username: root password: admin url: jdbc:mysql://localhost:3306/mybatis?userUnicode=true&chacacterEncoding=utf-8&serverTimezone=UTC # Springboot使用 com.mysql.cj.jdbc.Driver 针对Mysql8以上,5可能会有bug driver-class-name: com.mysql.cj.jdbc.Driver # 整合mybatis mybatis: type-aliases-package: com.ssl.bean # 解决绑定异常:mapper.xml最好和接口的包名路径一致 mapper-locations: classpath:com.ssl.mapper/*.xml
- mapper接口和xml,bean
// 这个注解表示了这是一个mapper的注解类 @Mapper @Repository public interface UserMapper { List<User> getAllUser(); User getUserById(@Param("id") int id); void addUser(User user); void deleteUser(@Param("id")int id); User updateUser(User user); } @Data @AllArgsConstructor @NoArgsConstructor public class User { private int id; private String name; private String pwd; }
<?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.ssl.mapper.UserMapper"> <select id="getAllUser" resultType="User"> SELECT * FROM USER; </select> <select id="getUserById" parameterType="int" resultType="User"> 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>
- controller
@Controller public class UserController { @Autowired private UserMapper userMapper; @RequestMapping("/users") @ResponseBody public List<User> getUsers() { return userMapper.getAllUser(); } }
10 整合SpringSecurity
- 功能权限、访问权限、菜单权限...,我们使用过滤器,拦截器需要写大量的原生代码,这样很不方便
- 所以在网址设计之初,就应该考虑到权限验证的安全问题,其中Shiro、SpringSecurity使用很多
- 前端资源:后期百度云补充
1 简介
- SpringSecurity是Springboot底层安全模块默认的技术选型,它可以实现强大的Web安全机制,只需要少数的
spring-boot--spring-security
依赖,进行少量的配置,就可以实现 - SpringBoot中的SpringSecurity依赖:
<!--security--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
记住几个类 :
WebSecurityConfigurerAdapter
:自定义Security策略AuthenticationManagerBuilder
:自定义认证策略@EnableWebSecurity
:开启WebSecurity模式
两个单词:en是认证,or是权限
- 认证方式:
Authentication
- 权限:
Authorization
- 认证方式:
2 认证和授权
- controller
@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; } }
- 写一个类继承
WebSecurityConfigurerAdapter
,使用@EnableWebSecurity
开启web安全服务- 地址授权:使用
HttpSecurity security
- 账户认证和给予权限:使用
AuthenticationManagerBuilder builder
- SpringSecurity5 以后默认需要密码加密方式,推荐使用
passwordEncoder(new BCryptPasswordEncoder())
- SpringSecurity5 以后默认需要密码加密方式,推荐使用
- 地址授权:使用
@EnableWebSecurity public class MySecurityConfig extends WebSecurityConfigurerAdapter { // url授权: HttpSecurity @Override protected void configure(HttpSecurity security) throws Exception { // 首页所有人可以访问,但是功能也只有对有权限的人可以访问 security .authorizeRequests() // 认证请求 .antMatchers("/").permitAll() .antMatchers("/level1/**").hasRole("vip1") .antMatchers("/level2/**").hasRole("vip2") .antMatchers("/level3/**").hasRole("vip3") ; // 自带登录页面,http://localhost:8080/login // 定制登录页,loginPage("/toLogin") // 指定表单提交url:loginProcessingUrl("/user/login") security.formLogin().loginPage("/toLogin") .usernameParameter("username").passwordParameter("password") .loginProcessingUrl("/user/login"); // 开启注销功能,源码http://localhost:8080/logout,并且注销成功后跳转到/的Controller security.logout().logoutSuccessUrl("/"); // 版本不同问题,可能会出现注销失败,关闭csrf // security.csrf().disable(); // 开启记住我功能:本质就是记住一个cookies,默认保存2周 security.rememberMe().rememberMeParameter("remember"); } // 用户认证:AuthenticationManagerBuilder // SpringSecurity5 以后默认需要新郑密码密码加密方式 @Override public void configure(AuthenticationManagerBuilder builder) throws Exception { // 内存中测试数据 builder.inMemoryAuthentication() // SpringSecurity5 以后默认需要新郑密码密码加密方式 .passwordEncoder(new BCryptPasswordEncoder()) .withUser("admin").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1") .and() .withUser("admin1").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2", "vip3") ; } }
3 登录注销和记住我
- 定制登录
- 开启登录功能:
formLogin()
,Springboot默认自带一个登录页/login
定制看下面 - 定制登录页:
loginPage("/toLogin")
- 修改表单提交的name:
.usernameParameter("username").passwordParameter("password")
- 表单提交的action:
loginProcessingUrl("/user/login")
- 开启登录功能:
// 自带登录页面,http://localhost:8080/login // 定制登录页,loginPage("/toLogin") // 指定表单提交url:loginProcessingUrl("/user/login") security.formLogin().loginPage("/toLogin") .usernameParameter("username").passwordParameter("password") .loginProcessingUrl("/user/login");
- 注销
- springboot自带注销页:
/logout
- 注销成功后跳转到/控制器
- springboot自带注销页:
// 开启注销功能,源码http://localhost:8080/logout,并且注销成功后跳转到/的Controller security.logout().logoutSuccessUrl("/");
- 记住我
- 本质就是存一个cookies,默认保存2周
- 自定前端记住我的name:
rememberMeParameter("remember")
// 开启记住我功能:本质就是记住一个cookies,默认保存2周 security.rememberMe().rememberMeParameter("remember");
<div class="field"> <input type="checkbox" name="remember"> 记住我 </div>
4 补充:前端权限验证
- 前端根据用户权限选择性展示元素,可以使用SpringSecurity和thymeleaf的整合包,更方便授权
- 依赖:
thymeleaf-extras-springsecurity5
- Springboot2.1.X以上需要springSecurity5的版本
<!--security整合thymeleaf,便于前端整合 Springboot2.1.X以上需要springSecurity5的版本 但是xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4"后缀还是4依然可以使用 --> <dependency> <groupId>org.thymeleaf.extras</groupId> <artifactId>thymeleaf-extras-springsecurity5</artifactId> <version>3.0.4.RELEASE</version> </dependency>
- 前端
- 登录授权检查: 没有登录就消失登录按钮
sec:authorize="!isAuthenticated()"
,反之亦然 - 登录后,展示用户名:
<span sec:authentication="name"
- 登录授权检查: 没有登录就消失登录按钮
<!--登录注销--> <div class="right menu"> <!--如果未登录,就消失登录按钮--> <div sec:authorize="!isAuthenticated()"> <a class="item" th:href="@{/toLogin}"> <i class="address card icon"></i> 登录 </a> </div> <!--如果登录,就显示用户名和注销--> <div sec:authorize="isAuthenticated()"> <a class="item" th:href="@{/logout}"> <!--从授权那里获取name--> 用户名:<span sec:authentication="name"></span> <!-- 有bug不能使用,角色:<span sec:authentication="principal.getAuthorities()"></span>--> </a> <a class="item" th:href="@{/logout}"> <i class="sign-out card icon"></i> 注销 </a> </div> </div>
11 整合Shiro
- shiro官网:http://shiro.apache.org/,用Idea观看官方Quickstart源码,配置运行一遍
- 核心三大对象:用户Subject, 管理用户SecurityManager, 连接数据Realms
Subjec
t:即“当前操作用户”。但是,在Shiro中,Subject这一概念并不仅仅指人,也可以是第三方进程、后台帐户(Daemon Account)或其他类似事物。它仅仅意味着“当前跟软件交互的东西”。SecurityManager
:它是Shiro框架的核心,典型的Facade模式,Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务。Realm
: Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。从这个意义上讲,Realm实质上是一个安全相关的DAO:它封装了数据源的连接细节,并在需要时将相关数据提供给Shiro。当配置Shiro时,你必须至少指定一个Realm,用于认证和(或)授权。配置多个Realm是可以的,但是至少需要一个。- 前端资源:后期百度云整理
1 依赖
<!--前端交互整合--> <dependency> <groupId>com.github.theborakompanioni</groupId> <artifactId>thymeleaf-extras-shiro</artifactId> <version>2.0.0</version> </dependency> <!--整合Mybatis--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.22</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.2</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <!-- shiro-spring --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.5.3</version> </dependency> <!--thymeleaf--> <dependency> <groupId>org.thymeleaf</groupId> <artifactId>thymeleaf-spring5</artifactId> </dependency> <dependency> <groupId>org.thymeleaf.extras</groupId> <artifactId>thymeleaf-extras-java8time</artifactId> </dependency>
2 Controller
- token:登录表单提交的信息,封装成
UsernamePasswordToken
,这是shiro提供的,便于xxxRelam
使用
@Controller public class MyController { @RequestMapping({"/", "/index"}) public String toIndex(Model model) { model.addAttribute("msg", "首页"); return "index"; } @RequestMapping("/user/add") public String addUser() { return "user/add"; } @RequestMapping("/user/update") public String updateUser() { return "user/update"; } @RequestMapping("/toLogin") public String toLogin() { return "login"; } @RequestMapping("/login") public String login(String username, String password, Model model) { // 获取用户 Subject user = SecurityUtils.getSubject(); // 封装参数,获取token UsernamePasswordToken token = new UsernamePasswordToken(username, password); // 验证登录 try { // 执行登录操作,跨类调用 user.login(token); model.addAttribute("msg", "成功登录"); return "index"; } catch (UnknownAccountException uae) { model.addAttribute("msg", "用户名错误"); return "login"; } catch (IncorrectCredentialsException ice) { model.addAttribute("msg", "用户密码错误"); return "login"; } } @RequestMapping("/noAuth") @ResponseBody public String noAuth() { return "未授权,无法访问"; } }
2 ShiroConfig
- 1 创建
XXXrealm
对象,注册进组件 - 2 获取安全管理器
DefaultWebSecurityManager
,设置xxxReaml - 3 获取
ShiroFilterFactoryBean
- 创建
ShiroFilterFactoryBean
,设置安全管理器setSecurityManager
- LinkedMap存储url和权限对应,
setFilterChainDefinitionMap(map);
- 指定登录映射:
setLoginUrl
- 创建
@Configuration public class ShiroConfig { // 3 获取ShiroBean @Bean public ShiroFilterFactoryBean getShiroFilterFactoryBean( @Qualifier("getDefaultWebSecurityManager") WebSecurityManager securityManager ) { ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean(); // 设置安全管理器 bean.setSecurityManager(securityManager); // 添加shiro的内置过滤器 /* anon: 无需认证就可以登录 authc:必须认证才能登录 user: 必须拥有“记住我”这个功能 perms:拥有对某个资源的权限才能访问 role:拥有某个角色才能访问 */ LinkedHashMap<String, String> map = new LinkedHashMap<>(); // 权限授权,访问url需要权限,支持通配符 map.put("/user/add", "perms[user:add]"); map.put("/user/update", "perms[user:update]"); bean.setFilterChainDefinitionMap(map); // 设置登录url映射 bean.setLoginUrl("/toLogin"); // 设置未授权的请求 bean.setUnauthorizedUrl("/noAuth"); return bean; } // 2 获取安全管理器 @Bean(name = "getDefaultWebSecurityManager") public DefaultWebSecurityManager getDefaultWebSecurityManager( @Qualifier("userRealm") UserRealm userRealm) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); // 直接userRealm()传参也可以,这里演示Spring指定自动注入 securityManager.setRealm(userRealm); return securityManager; } // 1 创建realm对象,需要自定义另一个类 @Bean(name = "userRealm") public UserRealm userRealm() { return new UserRealm(); } // 结合Mybatis,整合ShiroDialect进组件 @Bean public ShiroDialect getShiroDialect(){ return new ShiroDialect(); } }
3 userRelam
- 类继承
extends AuthorizingRealm
- 先认证
AuthenticationInfo
- ShiroConfig中的配置的登录映射走这里,提交表单封装成一个
token
,通过token中的信息走数据库查询 - 数据库查出用户名是否存在,如果存在就进行密码验证
new SimpleAuthenticationInfo(currentUser, currentUser.getPwd(), "");
:第一个形参传递查询出用户作为subject,这个subject等待AuthorizationInfo
使用;第二个参数是用户密码;第三个参数realmName
- ShiroConfig中的配置的登录映射走这里,提交表单封装成一个
- 后授权
AuthorizationInfo
- ShiroConfig的Map配置的Url被拦截走这里,创建简单的授权信息
new SimpleAuthorizationInfo()
AuthenticationInfo
最后第一参数传递过来subject获取当前用户,再获取权限,封装进简单授权信息,完成简单权限设置
- ShiroConfig的Map配置的Url被拦截走这里,创建简单的授权信息
// 自定义realms,继承AuthorizingRealm public class UserRealm extends AuthorizingRealm { @Autowired private UserServiceImpl userService; // 授权 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { // 进入被拦截的url,就会进这个info SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); // 授权应该从数据库查出权限字段 // info.addStringPermission("user:add"); // 从 new SimpleAuthenticationInfo(queryUser, queryUser.getPwd(), "");传递过来第一个参数user最为subject Subject subject = SecurityUtils.getSubject(); User currentUser = (User) subject.getPrincipal(); // 从数据库中获取验证权限 info.addStringPermission(currentUser.getPerms()); return info; } // 用户认证 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { // 模拟数据库中查出用户名、密码 UsernamePasswordToken userToken = (UsernamePasswordToken) token; User currentUser = userService.queryUserByName(userToken.getUsername()); // 验证用户名 if (currentUser == null) { // 用户名不正确,就抛出UnknownAccountException return null; } // 密码验证,shiro完成,不需要用户判断.直接返回 return new SimpleAuthenticationInfo(currentUser, currentUser.getPwd(), ""); } }
12 整合Swagger
1 第一个Swagger
- 依赖:提供Swagger2、SwaggerUI
<!-- springfox-swagger2 --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.9.2</version> </dependency> <!-- springfox-swagger-ui --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.9.2</version> </dependency>
- helloworld
@RestController public class HelloController { @RequestMapping(value = "/hello") public String hello() { return "hello"; } }
- 配置Swagger,编写一个空白的MySwagger
@Configuration @EnableSwagger2 // 开启Swagger2 public class MySwagger { }
2 修改默认info信息
- 修改apiInfo,访问http://localhost:8080/swagger-ui.html
@Configuration @EnableSwagger2 // 开启Swagger2 public class MySwagger { // 配置SwaggerBean实例 @Bean public Docket getDocker() { return new Docket(DocumentationType.SWAGGER_2) .apiInfo(changeInfo()); } // 更改默认info private ApiInfo changeInfo() { Contact author_contact = new Contact("laoSong", "http://123", "123@qq.com"); return new ApiInfo( "swagger文档学习", "学习swagger", "v1.0", "http://123", author_contact, "Apache 2.0", "http://www.apache.org/licenses/LICENSE-2.0", new ArrayList()); } }
3 配置扫描接口和路径
@Configuration @EnableSwagger2 // 开启Swagger2 public class MySwagger { // 配置SwaggerBean实例 @Bean public Docket getDocker() { return new Docket(DocumentationType.SWAGGER_2) // 是否启用swagger,则浏览器不能访问 // .enable(false) .apiInfo(changeInfo()) .select() // 扫描接口:,点进去看源码有很多种方式指定扫描方式 .apis(RequestHandlerSelectors.basePackage("com.ssl.controller")) // 扫描路劲:过滤路径 .paths(PathSelectors.ant("/hello")) .build(); } // 更改默认info private ApiInfo changeInfo() { Contact author_contact = new Contact("laoSong", "http://123", "123@qq.com"); return new ApiInfo( "swagger文档学习", "学习swagger", "v1.0", "http://123", author_contact, "Apache 2.0", "http://www.apache.org/licenses/LICENSE-2.0", new ArrayList()); } }
4 如何指定swagger在生产环境中使用
方法:
- 判断是不是生产环境的yml
- 注入enable(flag)
- application.yml,指定环境配置时dev
server: port: 8080 # 选择使用哪些配置 spring: profiles: active: dev # 激活dev配置 --- server: port: 8081 spring: profiles: test --- server: port: 8082 spring: profiles: dev
SwaggerConfig
@Configuration @EnableSwagger2 // 开启Swagger2 public class MySwagger { // 配置SwaggerBean实例 @Bean public Docket getDocker(Environment environment) { // 判断是否是生产环境的配置文件 Profiles isDevPro = Profiles.of("dev"); boolean flag = environment.acceptsProfiles(isDevPro); return new Docket(DocumentationType.SWAGGER_2) // 是否启用swagger,则浏览器不能访问 // .enable(false) // 这里判断是dev生产环境才开启Swagger .enable(flag) .apiInfo(changeInfo()) .select() // 扫描接口:,点进去看源码有很多种方式指定扫描方式 .apis(RequestHandlerSelectors.basePackage("com.ssl.controller")) // 扫描路劲:过滤路径 .paths(PathSelectors.ant("/hello")) .build(); } // 更改默认info private ApiInfo changeInfo() { Contact author_contact = new Contact("laoSong", "http://123", "123@qq.com"); return new ApiInfo( "swagger文档学习", "学习swagger", "v1.0", "http://123", author_contact, "Apache 2.0", "http://www.apache.org/licenses/LICENSE-2.0", new ArrayList()); } }
5 API分组
- 设置docket分组,配置多个不同名注入bean,供多人开发使用
@Bean public Docket docket2() { return new Docket(DocumentationType.SWAGGER_2) .groupName("AA"); }
6 常用api注释
@ApiModel("User实体类") @Data @AllArgsConstructor @NoArgsConstructor public class User { @ApiModelProperty("用户名") private String username; @ApiModelProperty("用户密码") private String password; }
@RestController public class HelloController { @GetMapping(value = "/hello") public String hello() { return "hello"; } //返回实体类,就会扫描进SwaggerUI中的modal @GetMapping(value = "/user") @ApiOperation("返回一个user") public User user() { return new User("张三", "123456"); } }
13 异步任务
方法上使用: @Async
启动类上使用:@EnableAsync
@Service public class AsyncService { //告诉Spring这是一个异步的方法 @Async public void hello() { try{ Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("正在处理异步请求"); } }
@Controller public class AsyncController { @Autowired private AsyncService asyncService; @RequestMapping("/hello") @ResponseBody public String hello() { return "hello"; } // 开启异步后,先返回结果,在等待后台处理请求 @RequestMapping("/sleep") @ResponseBody public String sleep() { // hello开启异步后,会创建另一个线程进行该操作 asyncService.hello(); return "hello"; } }
// 启动类开启异步注解功能 @EnableAsync @SpringBootApplication public class TestApplication { public static void main(String[] args) { SpringApplication.run(TestApplication.class, args); } }
14 邮件任务
- 依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-mail</artifactId> </dependency>
- 待补充
15 定时任务
- 待补充
16 整合Redis
17 整合Dubbo+Zookeeper
- 学习分布式理论:待补充
- 什么是RPC
- Http:是无状态的,基于网络通信的协议
- RPC:远程过程调用,是允许程序调用另一个地址空间的接口,两个核心:通讯和序列化
Dubbo概念
官网“快速启动”中提供了Spring整合Dubbo的操作,很简单;但是没有提供SpringBoot整合Dubbo的操作