一、热部署
为了提高我们的开发效率,我们可以放开IDEA中的SpringBoot项目的热部署操作
1.放开配置
在IDEA中默认是没有放开热部署操作的,我们需要手动的放开设置
2.注册
Ctrl+Shift+Alt+/ 快捷键 ,会出现一个弹出界面,选择Registry
3.添加devtools依赖
<!-- 添加热部署的加载 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
二、异常处理
1.自定义错误页面
SpringBoot默认的处理异常的机制,一旦程序出现了异常SpringBoot会向URL发送请求,在SpringBoot中提供了一个BasicExceptionController来处理/error请求,然后跳转到默认显示异常的页面来展示异常信息
如果我们需要将所有的异常统一跳转到我们自定义的错误页面,需要在src/main/resources/template目录下创建一个error.html页面,注意名称必须是error.html
要加上依赖才有效果
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
2.@ExceptionHandle注解
2.1 控制器
package com.biao.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
@Controller
public class UseController {
@RequestMapping("/show1")
public String showInfo() {
String name = null;
// 模拟空指针
name.length();
return "index";
}
@RequestMapping("/show2")
public String showInfo2() {
// 模拟空指针
int a = 1/0;
return "index";
}
@ExceptionHandler(value = {NullPointerException.class})
public ModelAndView nullPointerExceptionHandler(Exception e){
ModelAndView mm = new ModelAndView();
mm.addObject("error",e.toString());
mm.setViewName("error1");
return mm;
}
@ExceptionHandler(value = {ArithmeticException.class})
public ModelAndView arithmeticExceptionHandler(Exception e){
ModelAndView mm = new ModelAndView();
mm.addObject("error",e.toString());
mm.setViewName("error2");
return mm;
}
}
2.2 错误页面
3.@ControllerAdvice注解
上面的实现将控制器和异常处理的方法卸载了一块,显然不太合理,这时我们可以通过@ControllerAdvice注解来实现解耦
3.1 GlobalException控制器
package com.biao.exception;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.ModelAndView;
@ControllerAdvice
public class GlobalException {
@ExceptionHandler(value = {NullPointerException.class})
public ModelAndView nullPointerExceptionHandler(Exception e){
ModelAndView mm = new ModelAndView();
mm.addObject("error",e.toString());
mm.setViewName("error1");
return mm;
}
@ExceptionHandler(value = {ArithmeticException.class})
public ModelAndView arithmeticExceptionHandler(Exception e){
ModelAndView mm = new ModelAndView();
mm.addObject("error",e.toString());
mm.setViewName("error2");
return mm;
}
}
3.2 控制类
package com.biao.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
@Controller
public class UseController {
@RequestMapping("/show1")
public String showInfo() {
String name = null;
// 模拟空指针
name.length();
return "index";
}
@RequestMapping("/show2")
public String showInfo2() {
// 模拟空指针
int a = 1/0;
return "index";
}
}
4.SimpleMappingExceptionResolver
我们还可以通过SimpleMappingExceptionResolver来简化我们的异常处理
package com.biao;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;
import java.util.Properties;
@SpringBootApplication
public class SpringbootDemo10Application {
public static void main(String[] args) {
SpringApplication.run(SpringbootDemo10Application.class, args);
}
/**
* 通过SimpleMappingExceptionResolver 设置特定异常和处理器的映射关系
* @return
*/
@Bean
public SimpleMappingExceptionResolver getSimpleMappingExceptionResolver(){
SimpleMappingExceptionResolver resolver = new SimpleMappingExceptionResolver();
Properties properties = new Properties();
properties.setProperty("java.lang.NullPointerException","error1");
properties.setProperty("java.lang.ArithmeticException","error2");
resolver.setExceptionMappings(properties);
return resolver;
}
}
5.HandleExceptionResolver处理
我们上面讲的SimpleMappingExceptionResolver本质上就是实现HandleExceptionResolver的。
所以我们也可以自己实现HandleExceptionResolver接口
package com.biao.exception;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
public class MyHandleExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
ModelAndView mm = new ModelAndView();
if (e instanceof NullPointerException){
mm.setViewName("error1");
}else if (e instanceof ArithmeticException){
mm.setViewName("error2");
}else {
mm.setViewName("error");
}
return mm;
}
}
三、单元测试
为了提高在开发过程中的效率,我们可以通过SpringBoot中提供的单元测试来快速测试Service和dao的业务逻辑
1.依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
2.业务逻辑
package com.biao.service.impl;
import com.biao.service.IUserService;
import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.util.List;
@Service
public class UserServiceImpl implements IUserService {
@Override
public List<String> query() {
return Arrays.asList("张三","李四","王五");
}
}
3.测试
package com.biao;
import com.biao.service.IUserService;
import org.junit.jupiter.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class SpringbootDemo12ApplicationTests {
@Autowired
private IUserService service;
@Test
void contextLoads() {
System.out.println("---->" + service.query());
}
@BeforeEach
void before(){
System.out.println("before ...");
}
@AfterEach
void after(){
System.out.println("after ..");
}
}
四、整合Shiro
1.项目准备
创建一个SpringBoot项目整合MyBatis,Thymeleaf,SpringMVC等并创建相关的配置文件和Service逻辑。
1.1 项目依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.9</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies>
1.2 属性配置文件
server.port=8082
# 配置JDBC的相关信息
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/logistics?characterEncoding=utf-8&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=123456
# 配置连接池
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
# 配置MyBatis的package的别名
mybatis.type-aliases-package=com.biao.pojo
# 指定映射文件的位置
mybatis.mapper-locations=classpath:mapper/*.xml
1.3 持久层相关代码
通过MyBatis Generator自动生成持久层的相关代码(t_user表)或者从之前的货运系统中拷贝对应的代码
1.4 Service的逻辑实现
1.4.1 接口
package com.biao.service;
import com.biao.pojo.User;
import java.util.List;
public interface IUserService {
public User login(String userName);
public List<User> query(User user);
}
1.4.2 实现
package com.biao.service.impl;
import com.biao.mapper.UserMapper;
import com.biao.pojo.User;
import com.biao.pojo.UserExample;
import com.biao.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class UserServiceImpl implements IUserService {
@Autowired
public UserMapper mapper;
@Override
public User login(String userName) {
User user = new User();
user.setUserName(userName);
List<User> list = this.query(user);
if (list != null && list.size() == 1){
return list.get(0);
}
return null;
}
@Override
public List<User> query(User user) {
UserExample example = new UserExample();
UserExample.Criteria criteria = example.createCriteria();
if (user != null){
if (!"".equals(user.getUserName()) && user.getUserName() != null){
criteria.andUserNameEqualTo(user.getUserName());
}
}
return mapper.selectByExample(example);
}
}
2. Shiro整合
2.1 添加shiro依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
2.2 自定义Realm
package com.biao.realm;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
public class MyRealm extends AuthorizingRealm {
/**
* 认证
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
return null;
}
/**
* 授权
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
}
2.3 Shiro的配置(利用java配置类)
package com.biao.config;
import com.biao.realm.MyRealm;
import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.apache.shiro.mgt.SecurityManager;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {
/**
* 配置凭证匹配器
* @return
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher(){
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
matcher.setHashAlgorithmName("md5");
matcher.setHashIterations(1024);
return matcher;
}
/**
* 注册自定义的MyRealm
* @param hashedCredentialsMatcher
* @return
*/
@Bean
public MyRealm myRealm(CredentialsMatcher hashedCredentialsMatcher){
MyRealm realm = new MyRealm();
realm.setCredentialsMatcher(hashedCredentialsMatcher);
return realm;
}
/**
* 注册SecurityManager对象
* @param myRealm
* @return
*/
@Bean
public SecurityManager securityManager(Realm myRealm){
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
manager.setRealm(myRealm);
return manager;
}
/**
* 注册 ShiroFilterFactoryBean
* @return
*/
@Bean(name = "shiroFilter")
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager manager){
ShiroFilterFactoryBean filter = new ShiroFilterFactoryBean();
filter.setSecurityManager(manager);
filter.setLoginUrl("/login.do");
filter.setSuccessUrl("/success.html");
filter.setUnauthorizedUrl("/refuse.html");
//设置过滤器
Map<String,String> map = new HashMap<>();
map.put("/css","anon");
map.put("/img/**","anon");
map.put("/js/**","anon");
map.put("/login","anon");
map.put("/login.do","authc");
map.put("/**","authc");
filter.setFilterChainDefinitionMap(map);
return filter;
}
}
3.测试
添加对应的测试文件
3.1 Controller代码
3.1.1 loginController
package com.biao.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class LoginController {
@RequestMapping("/login")
public String goLoginPage(){
return "login";
}
}
3.2 UserController
package com.biao.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/query")
public String query(){
System.out.println("----user query----");
return "user";
}
}
4.认证过程
4.1自定义Realm
在自定义Realm中完成认证逻辑
package com.biao.realm;
import com.biao.pojo.User;
import com.biao.service.IUserService;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.SimpleByteSource;
import org.springframework.beans.factory.annotation.Autowired;
public class MyRealm extends AuthorizingRealm {
@Autowired
private IUserService service;
/**
* 认证
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
UsernamePasswordToken token =(UsernamePasswordToken) authenticationToken;
String userName = token.getUsername();
User user = new User();
user.setUserName(userName);
// 账号验证
user = service.login(userName);
if (user == null){
return null;
}
return new SimpleAuthenticationInfo(user,user.getPassword(),new SimpleByteSource(user.getU1()),"myRealm");
}
/**
* 授权
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
}
4.2 控制器
在控制器中完成认证失败的处理
package com.biao.controller;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;
@Controller
public class LoginController {
@RequestMapping("/login")
public String goLoginPage(){
return "login";
}
@RequestMapping("/login.do")
public String login(HttpServletRequest request){
Object obj = request.getAttribute(FormAuthenticationFilter.DEFAULT_ERROR_KEY_ATTRIBUTE_NAME);
System.out.println("认证错误的信息" + obj);
return "/login";
}
@RequestMapping("/logout")
public String logout(){
SecurityUtils.getSubject().logout();
return "/login";
}
}
4.3 登录页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>登录页面</h1>
<form th:action="@{/login.do}" method="post">
账号:<input type="text" name="username"><br>
密码:<input type="password" name="password"><br>
<input type="submit" value="提交">
</form>
</body>
</html>
5.授权操作
5.1 注解的使用
我们需要开启SpringMVC对Shiro授权注解的支持
5.1.1 在shiro的配置类中
/**
* 开启对shiro授权注解的支持
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
5.1.2 在自定义Realm中添加权限
/**
* 授权
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
User user = (User) principalCollection.getPrimaryPrincipal();
System.out.println("获取授权的账号:" + user.getUserName());
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addRole("role1");
return info;
}
5.1.3 添加依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</dependency>
5.1.4 启动类加上注解放开的支持
5.1.5 测试没有权限的错误
5.1.6 异常页面处理
/**
* 全局异常处理器
* @return
*/
@Bean
public SimpleMappingExceptionResolver simpleMappingExceptionResolver(){
SimpleMappingExceptionResolver resolver = new SimpleMappingExceptionResolver();
Properties properties = new Properties();
properties.put("org.apache.shiro.authz.UnauthorizedException","/403");
resolver.setExceptionMappings(properties);
return resolver;
}
5.2 标签的使用
5.2.1 添加标签shiro的依赖
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
5.2.2 在shiro配置类中注入
@Bean
public ShiroDialect shiroDialect(){
return new ShiroDialect();
}
5.2.3 在页面中实现处理
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro"
>
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>用户管理</h1>
<shiro:authenticated>
已登录:<shiro:principal property="userName"></shiro:principal>
</shiro:authenticated>
<a href="#" shiro:hasRole="role1">用户查询</a>
<a href="#" shiro:hasRole="role1">用户添加</a>
<a href="#" shiro:hasRole="role2">用户修改</a>
<a href="#" shiro:hasRole="role2">用户删除</a>
</body>
</html>