第1节 SpringBoot是什么
1.SpringBoot是一个可以快速创建可运行的、独立的、生产级的基于Spring的应用程序
2.SpringBoot采用一种约定优于配置的设计理念,可以快速让用户创建出一个可运行的基于Spring的应用
第2节 SpringBoot的优势
1.快速构建项目
2.对主流的开发框架无需配置集成,会自动的集成到一起(约定优于配置)
3.项目可独立运行,不需要外部servlet容器(不需要额外配置tomcat或者jetty容器)
4.提供运行时的应用监控(健康检查机制)
5.极大的提高了开发部署效率
第3节 SpringBoot的系统需求
由于每隔一段时间官网就会提升一次版本,当前授课采用的SpringBoot2.1.18版本(2020年11月)
当前版本的系统需求:
1、JDK8到JDK12(包含)版本
2、Spring框架版本5.1.19.RELEASE或者更高
3、项目构建工具
- 3.1 Maven版本 3.3+
- 3.2 Gradle版本 4.x (4.4 and later) and 5.x
4、Servlet Containers
- 4.1 Tomcat 9.0
- 4.2 Jetty 9.4
第二章 快速入门
第1节 SpringBoot的脚手架
- 脚手架的概念
在我们软件开发中的脚手架的概念,类似于我们的maven工具一样,以一种预先定义好的方式生成特定环境,特定的项目目录结构,并且预先定义好了每一个目录文件的具体功能
- 官网脚手架地址
https://start.spring.io/
- 阿里云脚手架地址
第2节 创建SpringBoot项目的方式
-
使用官网提供的工具创建(Spring官网下载sts)
-
使用IDEA创建
IDEA的
![
配置热加载:
- 使用脚手架在线创建
第3节 SpringBoot项目结构以及依赖
- SpringBoot的目录结构
-
SpringBoot的依赖样式
<dependency> <groupId>org.springframework.boot</groupId> <!--带有starter样式--> <artifactId>spring-boot-starter-web</artifactId> </dependency>
第4节 快速入门代码编写
- 使用IDEA创建项目(2.1.14版本)
- 勾选web依赖(首先创建一个web项目[相当于我们的SpringMVC项目])
第三章 启动方式
第1节 热部署
-
添加maven依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional><!--设置子项是否依赖,如果不设置为true热加载也不会成功--> </dependency>
-
配置maven插件(yml配置了,不用加)
<plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <fork>true</fork><!-- 如果不配置fork热加载不会成功 --> </configuration> </plugin>
-
application.yml文件配置
spring: #配置热部署 devtools: restart: enabled: true #监听类目录 additional-paths: 类目录 #排除监听目录文件 exclude: 目录 #页面 禁止thymeleaf缓存(建议:开发环境设置为false,生成环境设置为true) thymeleaf: cache: false
-
IDEA设置
1、打开IDEA 在当前路径下: File->Settings->Build, Execution, Deployment->Compiler 勾选Build project automatically 然后确定 2、继续在使用快捷键(Ctrl+Shift+Alt+/)打开面板,选择Registry勾选compiler.automake.allow.when.app.running
启动方式
1. 使用自带的main方法启动 2. 在当前项目的根目录下(pom文件所在目录)执行 mvn spring-boot:run 命令 3. 执行 java -jar xxx.jar 命令 4. 修改端口号 java -jar xxx.jar --server.port=9999
第2节 Web
1、简介
使用SpringBoot;
1)、创建SpringBoot应用,选中我们需要的模块;
2)、SpringBoot已经默认将这些场景配置好了,只需要在配置文件中指定少量配置就可以运行起来
3)、自己编写业务代码;
自动配置原理?
这个场景SpringBoot帮我们配置了什么?能不能修改?能修改哪些配置?能不能扩展?xxx
xxxxAutoConfiguration:帮我们给容器中自动配置组件;
xxxxProperties:配置类来封装配置文件的内容;
2、SpringBoot对静态资源的映射规则;
@ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false)
public class ResourceProperties implements ResourceLoaderAware {
//可以设置和静态资源有关的参数,缓存时间等
WebMvcAuotConfiguration:
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
return;
}
Integer cachePeriod = this.resourceProperties.getCachePeriod();
if (!registry.hasMappingForPattern("/webjars/**")) {
customizeResourceHandlerRegistration(
registry.addResourceHandler("/webjars/**")
.addResourceLocations(
"classpath:/META-INF/resources/webjars/")
.setCachePeriod(cachePeriod));
}
String staticPathPattern = this.mvcProperties.getStaticPathPattern();
//静态资源文件夹映射
if (!registry.hasMappingForPattern(staticPathPattern)) {
customizeResourceHandlerRegistration(
registry.addResourceHandler(staticPathPattern)
.addResourceLocations(
this.resourceProperties.getStaticLocations())
.setCachePeriod(cachePeriod));
}
}
//配置欢迎页映射
@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(
ResourceProperties resourceProperties) {
return new WelcomePageHandlerMapping(resourceProperties.getWelcomePage(),
this.mvcProperties.getStaticPathPattern());
}
//配置喜欢的图标
@Configuration
@ConditionalOnProperty(value = "spring.mvc.favicon.enabled", matchIfMissing = true)
public static class FaviconConfiguration {
private final ResourceProperties resourceProperties;
public FaviconConfiguration(ResourceProperties resourceProperties) {
this.resourceProperties = resourceProperties;
}
@Bean
public SimpleUrlHandlerMapping faviconHandlerMapping() {
SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
mapping.setOrder(Ordered.HIGHEST_PRECEDENCE + 1);
//所有 **/favicon.ico
mapping.setUrlMap(Collections.singletonMap("**/favicon.ico",
faviconRequestHandler()));
return mapping;
}
@Bean
public ResourceHttpRequestHandler faviconRequestHandler() {
ResourceHttpRequestHandler requestHandler = new ResourceHttpRequestHandler();
requestHandler
.setLocations(this.resourceProperties.getFaviconLocations());
return requestHandler;
}
}
1)、所有 /webjars/** ,都去 classpath:/META-INF/resources/webjars/ 找资源;
webjars:以jar包的方式引入静态资源;
http://www.webjars.org/
localhost:8080/webjars/jquery/3.3.1/jquery.js
<!--引入jquery-webjar-->在访问的时候只需要写webjars下面资源的名称即可
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.3.1</version>
</dependency>
2)、"/**" 访问当前项目的任何资源,都去(静态资源的文件夹)找映射
"classpath:/META-INF/resources/",
"classpath:/resources/",
"classpath:/static/",
"classpath:/public/"
"/":当前项目的根路径
localhost:8080/abc === 去静态资源文件夹里面找abc
3)、欢迎页; 静态资源文件夹下的所有index.html页面;被"/**"映射;
localhost:8080/ 找index页面
4)、所有的 **/favicon.ico 都是在静态资源文件下找;
3、RestfulCRUD
1)、默认访问首页
package com.springboot.demo.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
@Configuration
public class MyMvcConfig extends WebMvcConfigurationSupport {
//设置默认首页
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("login");
registry.addViewController("/index.html").setViewName("login");
}
}
2)、国际化
1)、编写国际化配置文件;
2)、使用ResourceBundleMessageSource管理国际化资源文件
3)、在页面使用fmt:message取出国际化内容
步骤:
1)、编写国际化配置文件,抽取页面需要显示的国际化消息
2)、SpringBoot自动配置好了管理国际化资源文件的组件;
@ConfigurationProperties(prefix = "spring.messages")
public class MessageSourceAutoConfiguration {
/**
* Comma-separated list of basenames (essentially a fully-qualified classpath
* location), each following the ResourceBundle convention with relaxed support for
* slash based locations. If it doesn't contain a package qualifier (such as
* "org.mypackage"), it will be resolved from the classpath root.
*/
private String basename = "messages";
//我们的配置文件可以直接放在类路径下叫messages.properties;
@Bean
public MessageSource messageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
if (StringUtils.hasText(this.basename)) {
//设置国际化资源文件的基础名(去掉语言国家代码的)
messageSource.setBasenames(StringUtils.commaDelimitedListToStringArray(
StringUtils.trimAllWhitespace(this.basename)));
}
if (this.encoding != null) {
messageSource.setDefaultEncoding(this.encoding.name());
}
messageSource.setFallbackToSystemLocale(this.fallbackToSystemLocale);
messageSource.setCacheSeconds(this.cacheSeconds);
messageSource.setAlwaysUseMessageFormat(this.alwaysUseMessageFormat);
return messageSource;
}
3)、去页面获取国际化的值;
<!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 href="asserts/css/bootstrap.min.css" th:href="@{/webjars/bootstrap/4.0.0/css/bootstrap.css}" rel="stylesheet">
<!-- Custom styles for this template -->
<link href="asserts/css/signin.css" th:href="@{/asserts/css/signin.css}" rel="stylesheet">
</head>
<body class="text-center">
<form class="form-signin" action="dashboard.html">
<img class="mb-4" th:src="@{/asserts/img/bootstrap-solid.svg}" src="asserts/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" placeholder="Username" th:placeholder="#{login.username}" required="" autofocus="">
<label class="sr-only" th:text="#{login.password}">Password</label>
<input type="password" class="form-control" placeholder="Password" th:placeholder="#{login.password}" required="">
<div class="checkbox mb-3">
<label>
<input type="checkbox" value="remember-me"/> [[#{login.remember}]]
</label>
</div>
<button class="btn btn-lg btn-primary btn-block" type="submit" th:text="#{login.btn}">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>
效果:根据浏览器语言设置的信息切换了国际化;
原理:
国际化Locale(区域信息对象);LocaleResolver(获取区域信息对象);
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "spring.mvc", name = "locale")
public LocaleResolver localeResolver() {
if (this.mvcProperties
.getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) {
return new FixedLocaleResolver(this.mvcProperties.getLocale());
}
AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
localeResolver.setDefaultLocale(this.mvcProperties.getLocale());
return localeResolver;
}
默认的就是根据请求头带来的区域信息获取Locale进行国际化
4)、点击链接切换国际化
/**
* 可以在连接上携带区域信息
*/
public class MyLocaleResolver implements LocaleResolver {
@Override
public Locale resolveLocale(HttpServletRequest request) {
String l = request.getParameter("l");
Locale locale = Locale.getDefault();
if(!StringUtils.isEmpty(l)){
String[] split = l.split("_");
locale = new Locale(split[0],split[1]);
}
return locale;
}
@Override
public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
}
}
@Bean
public LocaleResolver localeResolver(){
return new MyLocaleResolver();
}
}
3)、登陆
开发期间模板引擎页面修改以后,要实时生效
1)、禁用模板引擎的缓存
# 禁用缓存
spring.thymeleaf.cache=false
2)、页面修改完成以后ctrl+f9:重新编译;
登陆错误消息的显示
<p style="color: red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p>
4)、拦截器进行登陆检查
拦截器
/**
* 登陆检查,
*/
public class LoginHandlerInterceptor implements HandlerInterceptor {
//目标方法执行之前
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Object user = request.getSession().getAttribute("loginUser");
if(user == null){
//未登陆,返回登陆页面
request.setAttribute("msg","没有权限请先登陆");
request.getRequestDispatcher("/index.html").forward(request,response);
return false;
}else{
//已登陆,放行请求
return true;
}
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
注册拦截器
//所有的WebMvcConfigurerAdapter组件都会一起起作用
@Bean //将组件注册在容器
public WebMvcConfigurerAdapter webMvcConfigurerAdapter(){
WebMvcConfigurerAdapter adapter = new WebMvcConfigurerAdapter() {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("login");
registry.addViewController("/index.html").setViewName("login");
registry.addViewController("/main.html").setViewName("dashboard");
}
//注册拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
//super.addInterceptors(registry);
//静态资源; *.css , *.js
//SpringBoot已经做好了静态资源映射
registry.addInterceptor(new LoginHandlerInterceptor()).addPathPatterns("/**")
.excludePathPatterns("/index.html","/","/user/login");
}
};
return adapter;
}
4、错误处理机制
1)、SpringBoot默认的错误处理机制
默认效果:
1)、浏览器,返回一个默认的错误页面
浏览器发送请求的请求头:
2)、如果是其他客户端,默认响应一个json数据
原理:
可以参照ErrorMvcAutoConfiguration;错误处理的自动配置;
给容器中添加了以下组件
1、DefaultErrorAttributes:
帮我们在页面共享信息;
@Override
public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes,
boolean includeStackTrace) {
Map<String, Object> errorAttributes = new LinkedHashMap<String, Object>();
errorAttributes.put("timestamp", new Date());
addStatus(errorAttributes, requestAttributes);
addErrorDetails(errorAttributes, requestAttributes, includeStackTrace);
addPath(errorAttributes, requestAttributes);
return errorAttributes;
}
2、BasicErrorController:处理默认/error请求
@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {
@RequestMapping(produces = "text/html")//产生html类型的数据;浏览器发送的请求来到这个方法处理
public ModelAndView errorHtml(HttpServletRequest request,
HttpServletResponse response) {
HttpStatus status = getStatus(request);
Map<String, Object> model = Collections.unmodifiableMap(getErrorAttributes(
request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
response.setStatus(status.value());
//去哪个页面作为错误页面;包含页面地址和页面内容
ModelAndView modelAndView = resolveErrorView(request, response, status, model);
return (modelAndView == null ? new ModelAndView("error", model) : modelAndView);
}
@RequestMapping
@ResponseBody //产生json数据,其他客户端来到这个方法处理;
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
Map<String, Object> body = getErrorAttributes(request,
isIncludeStackTrace(request, MediaType.ALL));
HttpStatus status = getStatus(request);
return new ResponseEntity<Map<String, Object>>(body, status);
}
3、ErrorPageCustomizer:
@Value("${error.path:/error}")
private String path = "/error"; 系统出现错误以后来到error请求进行处理;(web.xml注册的错误页面规则)
4、DefaultErrorViewResolver:
@Override
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status,
Map<String, Object> model) {
ModelAndView modelAndView = resolve(String.valueOf(status), model);
if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);
}
return modelAndView;
}
private ModelAndView resolve(String viewName, Map<String, Object> model) {
//默认SpringBoot可以去找到一个页面? error/404
String errorViewName = "error/" + viewName;
//模板引擎可以解析这个页面地址就用模板引擎解析
TemplateAvailabilityProvider provider = this.templateAvailabilityProviders
.getProvider(errorViewName, this.applicationContext);
if (provider != null) {
//模板引擎可用的情况下返回到errorViewName指定的视图地址
return new ModelAndView(errorViewName, model);
}
//模板引擎不可用,就在静态资源文件夹下找errorViewName对应的页面 error/404.html
return resolveResource(errorViewName, model);
}
步骤:
一但系统出现4xx或者5xx之类的错误;ErrorPageCustomizer就会生效(定制错误的响应规则);就会来到/error请求;就会被BasicErrorController处理;
1)响应页面;去哪个页面是由DefaultErrorViewResolver解析得到的;
protected ModelAndView resolveErrorView(HttpServletRequest request,
HttpServletResponse response, HttpStatus status, Map<String, Object> model) {
//所有的ErrorViewResolver得到ModelAndView
for (ErrorViewResolver resolver : this.errorViewResolvers) {
ModelAndView modelAndView = resolver.resolveErrorView(request, status, model);
if (modelAndView != null) {
return modelAndView;
}
}
return null;
}
2)、如果定制错误响应:
1)、如何定制错误的页面;
1)、有模板引擎的情况下;error/状态码; 【将错误页面命名为 错误状态码.html 放在模板引擎文件夹里面的 error文件夹下】,发生此状态码的错误就会来到 对应的页面;
我们可以使用4xx和5xx作为错误页面的文件名来匹配这种类型的所有错误,精确优先(优先寻找精确的状态码.html);
页面能获取的信息;
timestamp:时间戳
status:状态码
error:错误提示
exception:异常对象
message:异常消息
errors:JSR303数据校验的错误都在这里
2)、没有模板引擎(模板引擎找不到这个错误页面),静态资源文件夹下找;
3)、以上都没有错误页面,就是默认来到SpringBoot默认的错误提示页面;
2)、如何定制错误的json数据;
1)、自定义异常处理&返回定制json数据;
@ControllerAdvice
public class MyExceptionHandler {
@ResponseBody
@ExceptionHandler(UserNotExistException.class)
public Map<String,Object> handleException(Exception e){
Map<String,Object> map = new HashMap<>();
map.put("code","user.notexist");
map.put("message",e.getMessage());
return map;
}
}
//没有自适应效果...
2)、转发到/error进行自适应响应效果处理
@ExceptionHandler(UserNotExistException.class)
public String handleException(Exception e, HttpServletRequest request){
Map<String,Object> map = new HashMap<>();
//传入我们自己的错误状态码 4xx 5xx,否则就不会进入定制错误页面的解析流程
/**
* Integer statusCode = (Integer) request
.getAttribute("javax.servlet.error.status_code");
*/
request.setAttribute("javax.servlet.error.status_code",500);
map.put("code","user.notexist");
map.put("message",e.getMessage());
//转发到/error
return "forward:/error";
}
3)、将我们的定制数据携带出去;
出现错误以后,会来到/error请求,会被BasicErrorController处理,响应出去可以获取的数据是由getErrorAttributes得到的(是AbstractErrorController(ErrorController)规定的方法);
1、完全来编写一个ErrorController的实现类【或者是编写AbstractErrorController的子类】,放在容器中;
2、页面上能用的数据,或者是json返回能用的数据都是通过errorAttributes.getErrorAttributes得到;
容器中DefaultErrorAttributes.getErrorAttributes();默认进行数据处理的;
自定义ErrorAttributes
//给容器中加入我们自己定义的ErrorAttributes
@Component
public class MyErrorAttributes extends DefaultErrorAttributes {
@Override
public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {
Map<String, Object> map = super.getErrorAttributes(requestAttributes, includeStackTrace);
map.put("company","atguigu");
return map;
}
}
最终的效果:响应是自适应的,可以通过定制ErrorAttributes改变需要返回的内容,
第四章 配置文件
第1节 SpringBoot配置文件的两种格式
1、application.properties
2、application.yml
上面这是SpringBoot支持的两种配置文件的格式,在开发中使用哪种都可以,最常见的是yml方式.
第2节 配置文件中自定义配置属性的获取
-
@Value(“${名称}”) 获取单个属性
1. 在application.properties自定义配置文件 # 自定义配置 com.qianfeng.springboot=hello name=Tom age=18 2. 在我们的Java类中获取 @Value("${com.qianfeng.springboot}") private String qianfeng; @Value("${name}") private String name; @Value("${age}") private Integer age; /** * 测试配置文件获取 */ @GetMapping(value = "/getProperties") public String getProperties(){ System.out.println(qianfeng); System.out.println(name); System.out.println(age); return "ok"; }
-
@ConfigurationProperties 获取实体对象
-
-
自定义配置(application.yml)
user: userId: 1001 userName: Tom hireDate: 2020-12-12 22:22:22
-
-
-
使用实体对象压入配置信息
@Component //加入IOC容器进行实例化 @ConfigurationProperties(prefix = "user") //指定配置前缀 public class User { private Integer userId; private String userName; @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") //时间格式 private Date hireDate; } 注意:当使用ConfigurationProperties注解时会报一个警告,官网解释需添加一个依赖即可 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency>
-
-
多环境配置(方式一)
SpringBoot多环境管理必须要遵循SpringBoot官方配置文件命名规则 application-{profile}.properties或者application-{profile}.yml eg: application-dev.yml 开发环境 application-test.yml 测试环境 application-pro.yml 生产环境
按照这个格式编写好的配置默认不会被SpringBoot执行,在运行时springboot还会默认执行application.yml文件,可以在appliaction.yml配置文件中设置Spring.profiles.active=dev 这样在项目运行时就会走application-dev.yml配置,但是这样还是需要频繁的修改配置,所以SpringBoot还提供了一个方式在不修改配置的情况下动态的进行配置文件修改
-
多环境配置(方式二)
在同一个文件中进行多环境配置的时候使用 --- 三个横线方式进行分割,在yml文件中配置 spring: profiles: active: dev --- spring: profiles: dev server: port: 8080 --- spring: profiles: test server: port: 8081 --- spring: profiles: pro server: port: 8082
-
使用命令动态的选择环境
1、使用命令将项目打成jar包,然后使用java -jar xxx.jar 运行 java -jar thymeleaf01-0.0.1-SNAPSHOT.jar --spring.profiles.active=pro 2、使用maven命令运行(2.x版本) mvn spring-boot:run -Dspring-boot.run.profiles=test
-
maven命令地址
https://docs.spring.io/spring-boot/docs/current/maven-plugin/reference/html/#run-example-active-profiles
第五章 异常处理
第1节 单个异常处理
//设置指定捕获当前类中的哪些异常,当前设置为Exception异常以及其子类都会被捕获到
@ExceptionHandler(value= {Exception.class})
public Object testException(Exception e){
Map<String,Object> result = new HashMap();
result.put("code",200);
result.put("msg":e.getMessage());
return result;
}
第2节 统一异常处理
@RestController
@ControllerAdvice
public class CommonException {
@ExceptionHandler(value = {Exception.class})
public Object testException(Exception e){
Map<String,Object> result = new HashMap();
result.put("code",200);
result.put("msg":e.getMessage());
return result;
}
}
-
异常的优先级
先查找当前类里面的异常处理,如果不存在在去统一异常处理类中匹配
第六章 日志设置
第1节 SpringBoot的日志
SpringBoot默认使用的是Slf4J+logback的日志
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
//日志的依赖一般都不需要加,因为很多其他依赖都带着此依赖,例如 web依赖
1.1 日志的配置
# 修改日志级别
logging.level.root=INFO
# 日志输出路径(会在当前文件夹下生成一个名字为spring.log的日志文件)
logging.path=C:\\log
# 修改生成的文件名称(会在项目的当前路径下生成一个 xxx.log 日志文件)
# logging.path和logging.file不能同时使用,如果同时使用,那么只有logging.file生效
logging.file=abc.log
logback.xml配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<!--
scan(非必须)属性:默认为false,true表示扫描配置文件的变化并自动重新配置,默认每隔1分钟扫描一次
scanPeriod(非必须)属性:搭配scan属性使用,设置扫描的间隔时间
-->
<configuration scan="true" scanPeriod="1 seconds">
<!--用于区分不同应用程序的记录-->
<contextName>edu-cloud</contextName>
<!--
设置自定义参数,类似于pom.xml里面的properties标签,下面可以使用${名称}引用此变量
-->
<property name="FILE_PATH" value="logs"></property>
<!--
ConsoleAppender: 控制台输出
STDOUT: 给appender起一个名字
-->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<!--
pattern: 日志输出的格式化样式
-->
<pattern>%d{HH:mm:ss.SSS} --> [%thread] --> %-5level %logger{36} - %msg%n</pattern>
<!--解决乱码问题-->
<charset>UTF-8</charset>
</encoder>
</appender>
<!--时间滚动日志生成-->
<appender name="TIME_ROLLING_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- ThresholdFilter:临界值过滤器,过滤掉 TRACE 和 DEBUG 级别的日志 -->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
</filter>
<encoder>
<charset>UTF-8</charset>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{0} -%msg%n</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--
设置生成的文件地址和文件名
-->
<!--<fileNamePattern>${FILE_PATH}/log%d{yyyy-MM-dd_HH-mm}.log</fileNamePattern>-->
<fileNamePattern>D:/SpringBootLog/log%d{yyyy-MM-dd_HH-mm}.log</fileNamePattern>
<!--最多生成十个,查过十个开始删除-->
<maxHistory>10</maxHistory><!--保存最近30天的日志-->
</rollingPolicy>
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<!-- 文件大小触发重写新文件 -->
<MaxFileSize>10MB</MaxFileSize>
</triggeringPolicy>
</appender>
<!--
debug: 指定日志的输出级别,大于debug的级别都会被输出
-->
<root level="debug">
<!--
appender-ref: 指定哪一个appender使用此日志输出级别
-->
<appender-ref ref="STDOUT" />
<appender-ref ref="TIME_ROLLING_FILE" />
</root>
</configuration>
第2节 SpringBoot的统一日志处理
2.1 使用AOP进行日志处理
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2.2 使用
@Aspect
@Component
public class LogAspect {
private final static Logger LOGGER = LoggerFactory.getLogger(LogAspect.class);
@Pointcut(value = "execution(* cn.ukoko.springbootaop.controller..*.*(..))")
public void log(){}
@Around(value = "log()")
public Object methodAroud(ProceedingJoinPoint joinPoint) throws Throwable {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
//获取主机IP
LOGGER.info("IP :{}",request.getRemoteAddr());
//获取请求地址
LOGGER.info("URL:{}",request.getRequestURL().toString());
//获取请求方式
LOGGER.info("HTTP Method:{}",request.getMethod());
//获取类名和方法名
LOGGER.info("Class Method:{}.{}",joinPoint.getSignature().getDeclaringTypeName(),joinPoint.getSignature().getName());
Object proceed = joinPoint.proceed();
LOGGER.info("Result:{}",proceed);
return proceed;
}
}
第七章 Spring5.x与Spring4.x版本的差异化
Spring5.x版本相比较于4.x版本发生了很大的变化
第1节 注解的变化
1. @RestController : 组合注解(@ResponseBody + @Controller)
2. @SpringBootApplication : 组合注解(多用于SpringBoot的启动注解)
3. @ImportResource : 如果一定要使用xml配置文件可以使用此注解加载
4. @ConfigurationProperties : 获取以xxx作为前缀的数据
5. @value : 获取以什么命名的数据
第2节 配置文件的变化
传统的配置比如ssm框架采用的xml配置,而SpringBoot官方不建议使用xml配置,而是采用传统的API和注解的方式进行配置
举一个例子(整合一个不遵循SpringBoot约定优于配置的依赖[非start依赖])
-
添加依赖
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.49</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.20</version> </dependency>
-
创建配置文件
applicaton.yml username1: root password1: root jdbcurl1: jdbc:mysql:///ssm /** * 如果maven的依赖满足start这样的类型,多半是不需要配置的,这样的类型的依赖可以和SpringBoot直接整合到一起 * 但是有一些框架或者工具没有提供start这样类型的依赖,在加入依赖之后不会自动整合到SpringBoot上面,这时候就需要我们配置 */ //@ImportResource(locations = {"classpath:bean.xml"}) @Configuration public class DruidConfiguration { @Value("${username1}") //在进行配置时候不要取有歧义的名字 private String username; @Value("${password1}") private String password; @Value("${jdbcurl1}") private String jdbcurl; /** * 配置Druid */ //@Primary //自动装配时当出现多个Bean候选者时,被注解为@Primary的Bean将作为首选者,否则将抛出异常 @Bean //将方法的返回值加入到IOC容器中,和我们的xml配置中的bean标签功能相同,内部有name属性,设置为IOC容器中的对象取别名 public DataSource createDruid(){ DruidDataSource dataSource = new DruidDataSource(); dataSource.setUsername(username); dataSource.setPassword(password); dataSource.setUrl(jdbcurl); System.out.println("------------"+dataSource); return dataSource; } }
-
测试
@Autowired //从IOC容器中获取对象 private DataSource dataSource; System.out.println("从ioc容器中获取对象:"+dataSource); //获取数据库连接 Connection connection = dataSource.getConnection(); Statement statement = connection.createStatement(); String sql="SELECT * FROM user"; ResultSet rs = statement.executeQuery(sql); while(rs.next()){ int userId = rs.getInt("user_id"); System.out.println("userId="+userId); System.out.println(); }
第八章 CRUD练习(API版本)
-
添加依赖
<dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.17</version> </dependency> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper-spring-boot-starter</artifactId> <version>1.3.0</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency>
-
基础配置
@Mapper: SpringBoot的Mapper层需要添加此注解 # 数据库配置 spring.datasource.url=jdbc:mysql://localhost:3306/ssm?characterEncoding=utf-8&useUnicode=true&serverTimezone=GMT%2B8 spring.datasource.username=root spring.datasource.password=root # mybatis配置 mybatis.mapper-locations=classpath:mapper/*Mapper.xml mybatis.type-aliases-package=com.ssm.testdemo.entity mybatis.config-location=classpath:mybatis-config.xml # 中文编码(处理请求) spring.http.encoding.charset=UTF-8 spring.http.encoding.force=true spring.http.encoding.enabled=true server.tomcat.uri-encoding=UTF-8
-
单元测试(2.1.x版本)
@RunWith(SpringRunner.class) @SpringBootTest
第九章 Thymeleaf模板
Thymeleaf是一个web端的并且独立的Java模板引擎,他能够处理HTML、XML、JavaScript、CSS以及纯文本,Thymeleaf的理念是创建一种优雅和易维护的模板,为了实现这一点,它建立在自然模板之上,将逻辑注入到模板文件中,还不会影响到模板被用作设计原型。Thymeleaf一开始就设计了Web标准,SpringBoot官方推荐使用Thymeleaf 而不是JSP
第1节 快速入门
-
添加依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>
-
配置文件
spring: thymeleaf: mode: HTML cache: false encoding: utf-8
-
创建页面
在templates目录下创建index.html和result.html 1、index.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>首页</title> </head> <body> <!--第一个页面请求--> <a href="/helloworld">Hello World</a> </body> </html> ------------------------------------------------------------------- 2、result.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>结果页</title> </head> <body> <!--结果页面--> <h1>结果页面</h1> </body> </html>
-
创建控制器(@Controller)
@Controller public class UserController { /** * 快速入门 */ @GetMapping(value = "/helloworld") public String helloworl(){ System.out.println("第一个thymeleaf请求..."); return "result"; } }
-
刷新前端页面快捷键
ctrl+F9
第2节 常见语法
-
index.html
<a href="/HelloThymeleaf">HelloThymeleaf</a>
-
result.html
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>结果页</title> </head> <body> <!--结果页面--> <h1>结果页面</h1> <!--显示普通文本,从域对象中获取--> <p th:text="${username}"></p> <hr> <!--显示带有样式的普通文本--> <p th:utext="${desc}"></p> <hr> <!--显示对象,数据处理(thymeleaf提供了内置对象API可以操作数据,比如对前端显示时间的格式化)--> <div> <p th:text="${user.userId}"></p> <p th:text="${user.userName}"></p> <p th:text="${user.createTime}"></p> <p th:text="${#dates.format(user.createTime,'yyyy-MM-dd HH:mm:ss')}"></p> </div> <hr> <!--内置域对象--> <p th:text="${#httpServletRequest.getAttribute('password')}"></p> <hr> <!--数据遍历 list集合--> <table> <tr> <td>No.</td> <td>UID</td> <td>姓名</td> <td>创建时间</td> <td>偶数</td> <td>奇数</td> </tr> <tr th:each="x,y:${users}"> <td th:text="${y.index+1}"/> <td th:text="${x.userId}"/> <td th:text="${x.userName}"/> <td th:text="${x.createTime}"/> <td th:text="${y.even}"/><!--偶数--> <td th:text="${y.odd}"/><!--奇数--> </tr> </table> <hr> <!--数据遍历 map集合--> <table> <tr> <td>No.</td> <td>UID</td> <td>姓名</td> <td>创建时间</td> <td>偶数</td> <td>奇数</td> </tr> <tr th:each="x,y:${map}"> <td th:text="${y.index+1}"/> <td th:text="${x.value.userId}"/> <td th:text="${x.value.userName}"/> <td th:text="${x.value.createTime}"/> <td th:text="${y.even}"/><!--偶数--> <td th:text="${y.odd}"/><!--奇数--> </tr> </table> </body> </html>
-
控制器
@GetMapping(value = "/HelloThymeleaf") public String HelloThymeleaf(Model model, HttpServletRequest request){ //显示普通文本,从域对象中获取 model.addAttribute("username","Tom"); //显示带有样式的普通文本 model.addAttribute("desc","<span style='color:red'>你好,中国</span>"); //显示对象,数据处理(thymeleaf提供了内置对象API可以操作数据,比如对前端显示时间的格式化) User user = new User(1001,"user01",new Date()); model.addAttribute("user",user); //内置域对象 request.setAttribute("password","123456"); //数据遍历 list User user1 = new User(1002,"user02",new Date()); User user2 = new User(1003,"user03",new Date()); User user3 = new User(1004,"user04",new Date()); List<User> users = new ArrayList<>(); users.add(user1); users.add(user2); users.add(user3); model.addAttribute("users",users); //数据遍历 map Map<String,User> map = new HashMap<>(); map.put("user01",user1); map.put("user02",user2); map.put("user03",user3); model.addAttribute("map",map); return "result"; }
第3节 路径处理
<script type="text/javascript" th:src="@{/js/main.js}"></script> <a th:href="@{/show}">访问controller方法</a> <a th:href="@{/static_index.html}">访问静态页面</a>
第4节 条件语句
th:if="boolean" th:if的表达式需为boolean值。如果为true,则标签显示,如果为false,则标签不显示 th:unless="boolean" th:unless和th:if相反,表达式也需为boolean值。如果为true,则标签不显示,如果为false,则标签显示 <span th:if="${stat}">偶</span> <span th:unless="${stat}">奇</span>
第5节 页面引入
-
页面引入介绍
我们常常需要在一个页面当中引入另一个页面,例如,公用的导航栏以及页脚页面。thymeleaf中提供了两种方式进行页面引入
-
引入方式一[th:replace(替换全部)]
-
引入方式二[th:include(替换内容)]
1、新建需要被引入的页面文件,路径为"/templates/footer.html" <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/> <footer th:fragment="companyInfo"> <p>设为首页 ©2018 SpringBoot 使用<span th:text="${name}"/>前必读意见反馈京ICP证030173号 </p> </footer> 2、在目标页面引入footer.html有两种方式 - 2.1 <div th:include="footer :: companyInfo" th:with="name=${username}"/> 可以设置参数 - 2.2 <div th:replace="footer :: companyInfo"/> 设置参数无效,所以直接引入即可
第6节 使用thymeleaf实现CRUD
请求路径的参数设置 <a th:href="@{/delteUser(userId=${x.userId})}">删除</a> 删除路径会构建成一个类似于 deleteUser?userId=1000 <a th:href="@{/jumpUser/{userId}(userId=${x.userId})}">更新</a> 更新路径会构建成一个类似于 jumpUser/1000
第十章 SpringBoor + MyBatis
导入相关依赖:
<!--web依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--SpringBoot启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!--导入配置文件处理器,配置文件进行绑定就会有提示-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!--引用ThymeLeaf-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--热加载-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional><!--设置子项是否依赖,如果不设置为true热加载也不会成功-->
</dependency>
<!--Druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.17</version>
</dependency>
<!--jdbc-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!--Mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>5.1.47</scope>
</dependency>
<!--MyBatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.1</version>
</dependency>
<!--<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<fork>true</fork><-- 如果不配置fork热加载不会成功
</configuration>
</plugin>-->
config文件
application.yml
spring:
profiles:
active: dev
datasource:
username: root
password: 3306
url: jdbc:mysql://localhost:3306/yikao?serverTimezone=UTC
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
#Druid连接池配置
druid:
initial-size: 5
max-active: 100
min-idle: 5
max-wait: 60000
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 30000
validation-query: SELECT 1 FROM DUAL
test-while-idle: true
test-on-borrow: true
test-on-return: false
#配置热部署
devtools:
restart:
enabled: true
#监听类目录
additional-paths: 类目录
#排除监听目录文件
exclude: 目录
#页面 禁止thymeleaf缓存(建议:开发环境设置为false,生成环境设置为true)
thymeleaf:
cache: false
#配置mybatis
mybatis:
mapperLocations: classpath*:mapper/*.xml
typeAliasesPackage: com.zhibang.springbootdemo2.dao
主程序类
@EnableTransactionManagement //开启事务管理
@MapperScan("com.xr.demo1.dao") //与dao层的@Mapper二选一写上即可(主要作用是扫包)
package com.zhibang.springbootdemo2;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@MapperScan("com.zhibang.springbootdemo2.dao")//要加上这个注解
@SpringBootApplication
public class Springbootdemo2Application {
public static void main(String[] args) {
SpringApplication.run(Springbootdemo2Application.class, args);
}
}
第十一章 Shiro整合(注解与传统xml对比)
Spring5.x版本在SpringBoot中官方推荐使用Java配置的方式来进行与第三方框架的集成
我们这里以springBoot和Shiro的整合方式来讲解Java的配置.
-
版本介绍
SpringBoot版本 : 2.1.18.RELEASE shiro-spring版本 : 1.5.0 <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.5.0</version> </dependency> //如果使用Shiro的注解功能需要添加springboot的aop依赖 //同时在application.properties中配置spring.aop.proxy-target-class=true,有的版本不需要配置(当前的2.1.17.RELEASE不需要) <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <!-- thymeleaf模板引擎和shiro框架的整合 --> <dependency> <groupId>com.github.theborakompanioni</groupId> <artifactId>thymeleaf-extras-shiro</artifactId> <version>2.0.0</version> </dependency>
-
整合用到的核心注解
@Bean: 可以修饰方法和注解,功能和<bean>标签相同,将对象加入到IOC容器中 @Configuration : 修饰类,被次注解修饰的类,会被Spring扫描到,被认定为是一个配置类
-
集成Shiro配置代码
@Configuration public class ShiroConfig { /** * 将Realm注入到IOC中 * @return */ @Bean public UserRealm createUserRealm(HashedCredentialsMatcher hashedCredentialsMatcher){ //创建Realm实例 UserRealm userRealm = new UserRealm(); //设置加密规则,HashedCredentialsMatcher对象通过给方法入参注入到此方法中 userRealm.setCredentialsMatcher(hashedCredentialsMatcher); return userRealm; } @Bean public HashedCredentialsMatcher createHashedCredentialsMatcher(){ //创建加密对象 HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher(); //加密方式 credentialsMatcher.setHashAlgorithmName("md5"); //迭代次数 credentialsMatcher.setHashIterations(1024); return credentialsMatcher; } /** * 安全管理器 */ @Bean public DefaultWebSecurityManager createSecurityManager(UserRealm userRealm){ //创建web的安全管理器 DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); //配置realm securityManager.setRealm(userRealm); return securityManager; } /** * 将ShiroFilter加入到IOC */ @Bean public ShiroFilterFactoryBean createShiroFilter(DefaultWebSecurityManager securityManager){ ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); //设置安全管理器 shiroFilterFactoryBean.setSecurityManager(securityManager); //添加Shiro内置过滤器 Map<String,String> filterMap = new LinkedHashMap<>(); //释放静态资源 filterMap.put("/static/**", "anon"); filterMap.put("/common/**", "anon"); filterMap.put("/css/**", "anon"); filterMap.put("/extends/**", "anon"); filterMap.put("/fonts/**", "anon"); filterMap.put("/images/**", "anon"); filterMap.put("/jquery/**", "anon"); filterMap.put("/js/**", "anon"); filterMap.put("/layer/**", "anon"); filterMap.put("/lib/**", "anon"); //释放login.html页面 filterMap.put("/dologin", "anon"); filterMap.put("/tAccount/domain", "anon"); filterMap.put("/main", "anon"); // filterMap.put("/tAccount/add", "authc"); // filterMap.put("/tAccount/update", "authc"); // filterMap.put("/tAccount/delete", "authc"); // map.put("/home1","authc,roles[admin]"); // map.put("/home2","authc,roles[admin],perms[user:update]"); //授权过滤器 // filterMap.put("/tAccount/**", "perms[user:*]"); //认证通过才能访问 filterMap.put("/**","authc"); //设置未认证跳转到登录页面 shiroFilterFactoryBean.setLoginUrl("/404"); //设置未授权跳转到登录页面 shiroFilterFactoryBean.setUnauthorizedUrl("/401"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap); return shiroFilterFactoryBean; } /** * 注解生效 * 如果使用Shiro注解,需要配置AuthorizationAttributeSourceAdvisor,否则注解不生效 */ @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager){ AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } /** * 配置ShiroDialect,用于thymeleaf和Shiro标签配合使用 * @return */ @Bean public ShiroDialect getShiroDialect(){ return new ShiroDialect(); } }
-
控制器测试方法
//@RestController @Controller public class UserController { /** * 登陆请求 */ @PostMapping(value = "/login") @ResponseBody public String login(SysUsers sysUsers){ UsernamePasswordToken token = new UsernamePasswordToken(sysUsers.getUsername(),sysUsers.getPassword()); Subject subject = SecurityUtils.getSubject(); try { subject.login(token); return "3"; }catch (UnknownAccountException uae){ System.out.println("用户名不存在:"+uae.getMessage()); return "0"; }catch (IncorrectCredentialsException ice){ System.out.println("密码错误:"+ice.getMessage()); return "1"; }catch (LockedAccountException lae){ System.out.println("用户被锁定:"+lae.getMessage()); return "0"; }catch (AuthenticationException ae){ // 由自定义Realm抛出,在此捕获 System.out.println("其他异常:"+ae.getMessage()); return "0"; } //return "success"; } /** * 测试请求 */ @RequiresRoles({"admin"}) @GetMapping(value = "/home1") public String index(){ System.out.println("我是home1,需要认证用户的角色为admin才能访问"); return "我是home1..."; } /** * 测试请求 */ @RequiresRoles({"admin"}) @RequiresPermissions({"user:update"}) @GetMapping(value = "/home2") public String index1(){ System.out.println("我是home2,需要认证用户的角色是admin并且权限为user:update才可以访问"); return "我是home2..."; } /** * 处理未认证的请求 */ @GetMapping("/unauthentication") public void unAuthorization(){ System.out.println("当前请求未认证..."); throw new RuntimeException("当前请求未认证..."); } /** * 处理未授权的请求 */ @GetMapping("/unauthorized") public void unauthorized(){ System.out.println("当前请求未授权..."); throw new RuntimeException("当前请求未授权..."); } /** * 退出登录 */ @RequestMapping("/logout") public String logout(Model model) { Subject subject = SecurityUtils.getSubject(); subject.logout(); model.addAttribute("msg","安全退出!"); return "login"; } }
-
统一异常处理
@Component @ControllerAdvice public class ShiroExceptionHandler { @ResponseBody @ExceptionHandler(value = {AuthorizationException.class}) public Object processShiroAuthorizationException(){ return "授权失败..."; } @ResponseBody @ExceptionHandler(value = {AuthenticationException.class}) public Object processShiroAuthenticationException(){ return "认证失败..."; } }
-
自定义的Realm
public class UserRealm extends AuthorizingRealm { @Autowired private SysUsersService sysUsersService; @Autowired private SysUsersRolesService sysUsersRolesService; @Autowired private SysRolesService sysRolesService; @Autowired private SysRolesPermissionsService sysRolesPermissionsService; @Autowired private SysPermissionsService sysPermissionsService; /** * 获取授权数据 * @param principalCollection * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { //获取用户名 String username = principalCollection.getPrimaryPrincipal().toString(); System.out.println("username:"+username); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); //通过用户名查询当前用户的所有角色 SysUsers user = sysUsersService.getUserByUsername(username); //根据用户ID查询角色信息 List<SysUsersRoles> usersRoles = sysUsersRolesService.getSysUsersRolesByUserId(user.getUserId()); //根据角色ID查询角色名称 Set<String> roleNames = new HashSet<>(); for (SysUsersRoles usersRole : usersRoles) { SysRoles roles = sysRolesService.getSysRolesByRoleId(usersRole.getRoleId()); roleNames.add(roles.getRoleName()); //通过角色ID查询权限列表 List<SysRolesPermissions> sysRolesPermissionss = sysRolesPermissionsService.getSysRolesPermissionsByRoleId(roles.getRoleId()); //根据权限ID查询权限名称 Set<String > permissionsNames = new HashSet<>(); for (SysRolesPermissions rolesPermissionss : sysRolesPermissionss) { SysPermissions permissions = sysPermissionsService.getSysPermissionsByPermissionsId(rolesPermissionss.getPermissionId()); if (null!=permissions){ permissionsNames.add(permissions.getPermissionName()); } } //将权限信息封装进info info.addStringPermissions(permissionsNames); } info.setRoles(roleNames); // info.addRoles(hashSet); //Session session = SecurityUtils.getSubject().getSession(); //session.setAttribute("role", list); return info; } /** * 获取认证数据 * @param authenticationToken * @return * @throws AuthenticationException */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { //获取用户名 String username = authenticationToken.getPrincipal().toString(); System.out.println("realm: username:"+username); if(username==null || username.length()<=0){ throw new UnknownAccountException("当前用户不存在"); } //通过用户名查询数据库 SysUsers user = sysUsersService.getUserByUsername(username); if(user==null){ throw new UnknownAccountException("当前用户不存在"); } System.out.println("====="+user); //从数据库中获取数据,并且将查询出来的认证数据封装进alt ByteSource salt = ByteSource.Util.bytes(user.getSalt()); SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user.getUsername(),user.getPassword(),salt,getName()); return info; } }
-
thymeleaf和shiro标签使用
使用标签时先添加:
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
常用标签:
guest标签 <shiro:guest> </shiro:guest> 用户没有身份验证时显示相应信息,即游客访问信息。 user标签 <shiro:user> </shiro:user> 用户已经身份验证/记住我登录后显示相应的信息。 authenticated标签 <shiro:authenticated> </shiro:authenticated> 用户已经身份验证通过,即Subject.login登录成功,不是记住我登录的。 notAuthenticated标签 <shiro:notAuthenticated> </shiro:notAuthenticated> 用户已经身份验证通过,即没有调用Subject.login进行登录,包括记住我自动登录的也属于未进行身份验证。 principal标签 <shiro: principal/> <shiro:principal property="username"/> 相当于((User)Subject.getPrincipals()).getUsername()。 lacksPermission标签 <shiro:lacksPermission name="org:create"> </shiro:lacksPermission> 如果当前Subject没有权限将显示body体内容。 hasRole标签 <shiro:hasRole name="admin"> </shiro:hasRole> 如果当前Subject有角色将显示body体内容。 hasAnyRoles标签 <shiro:hasAnyRoles name="admin,user"> </shiro:hasAnyRoles> 如果当前Subject有任意一个角色(或的关系)将显示body体内容。 lacksRole标签 <shiro:lacksRole name="abc"> </shiro:lacksRole> 如果当前Subject没有角色将显示body体内容。 hasPermission标签 <shiro:hasPermission name="user:create"> </shiro:hasPermission> 如果当前Subject有权限将显示body体内容