创建一个springboot项目
操作流程
File->New->Project->Spring Initializr->Next(如果显示超时就选择Custom并填写我写的阿里云镜像)
填写相关信息->Next
选择Spring Boot版本并勾选相关依赖->Next->填写文件路径
springboot项目目录介绍
resources目录下必须有aplication.yml或application.properties核心配置文件
在Java文件夹目录下有且只有一个项目入口类,一般是项目名+Application
Spring Boot启动显示项目信息
修改启动入口类:
@SpringBootApplication
public class ErpApplication{
public static void main(String[] args) throws IOException {
ConfigurableApplicationContext context = SpringApplication.run(ErpApplication.class, args);
Environment environment = context.getBean(Environment.class);
System.out.println("启动成功,后端服务API地址:http://" + ComputerInfo.getIpAddr() + ":"
+ environment.getProperty("server.port") + "/jshERP-boot/doc.html");
System.out.println("您还需启动前端服务,启动命令:yarn run serve 或 npm run serve,测试用户:jsh,密码:123456");
}
}
运行Spring Boot项目及相关注解介绍
添加一些代码
进入到你的xxxApplicaiton.java文件,填充相关注解和home方法
@RestController//表示这是一个控制器类
@SpringBootApplication//是组合注解,整合了EnableAutoConfiguration和ComponentScan
public class Example {
@RequestMapping("/")//访问的接口
public String home() {//处理的函数
return "Hello World!";//返回值
}
public static void main(String[] args) {
SpringApplication.run(Example.class, args);
}
}
访问浏览器界面
右键运行程序,打开浏览器,url栏输入localhost:8080
然后就能看到Hello World!
相关注解介绍
@ComponentScan
扫描当前包以及子包@Controller
:修饰class,用来创建处理http请求的对象@RequestMapping()
表示@xxxMapping的接口路径前面都会默认加上RequestMapping的路径,配置url映射。现在更多的也会直接用以Http Method直接关联的映射注解来定义,比如:GetMapping
、PostMapping
、DeleteMapping
、PutMapping
等@RestController
:Spring4之后加入的注解,原来在@Controller
中返回json需要@ResponseBody
来配合,如果直接用@RestController
替代@Controller
就不需要再配置@ResponseBody
,默认返回json格式@SpringBootApplication
是组合注解,整合了@EnableAutoConfiguration和@ComponentScan
打包成jar包以及运行
需要在pom.xml文件中添加依赖(Spring Initializr会自动添加)
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
点击右侧的Maven->LifeCycle->compile(不要选package)
运行方式:
java -jar xxx.jar
一些常用的yml配置
server:
port: 8080 # 修改端口,别忘了前面的空格
servlet:
context-path: /xxx # 指定项目名,即访问的时候要在8080后面加上/xxx,注意以斜杠开头
staters介绍
staters可以方便地管理相关依赖,例如springboot-stater-web
即可得到web应用启动的代码
全部staters请参看官网
Spring Boot开发规范
标准开发方式目录:
com
+- example
+- myapplication
+- Application.java---------------->启动类
|
+- customer------------------------>管理客户
| +- Customer.java
| +- CustomerController.java
| +- CustomerService.java
| +- CustomerRepository.java
|
+- order---------------------------->管理订单
+- Order.java
+- OrderController.java
+- OrderService.java
+- OrderRepository.java
- config编写配置类
- controller是业务层
- entity是实体类层
- dao是编写数据库操作函数接口的
- service是实现数据库操作功能的,整合dao层,文件格式是
xxxService(interface)
+xxxImpl(class)
xxxImpl
是impl
的缩写,service层先有接口,再有impl实现接口,整合了DAO层- utils是编写工具类
- mapper是写mapper的,实现dao的接口
- 接口路径格式是
/xxxx
,即前面加上/
,其实加不加都可以,最好还是加,规范嘛
配置类与xml配置
配置是进行工厂设计模式,即对对象进行加工后再返回,看到的名字一样,但是已经执行了一步程序了
官方推荐使用配置类(Java config):
- 添加@Configuration注解,将配置类自动注入,类似于@Component
- @Import不推荐使用,因为这个必须在要用的位置写,即哪用在哪写,但是@Configuration是写一次到处用
如果是xml格式:
- 编写xml文件
- @ImportResource添加在对应的类上
自定义项目启动的彩蛋
- 打开网址http://patorjk.com/software/taag/将字符转为字符画
- 拷贝生成的字符画到名为banner.txt文件中
- 将banner.txt拷贝到项目的resources目录中再启动程序
${AnsiColor.BRIGHT_YELLOW}
// _ooOoo_ //
// o8888888o //
// 88" . "88 //
// (| ^_^ |) //
// O\ = /O //
// ____/`---'\____ //
// .' \\| |// `. //
// / \\||| : |||// \ //
// / _||||| -:- |||||- \ //
// | | \\\ - /// | | //
// | \_| ''\---/'' | | //
// \ .-\__ `-` ___/-. / //
// ___`. .' /--.--\ `. . ___ //
// ."" '< `.___\_<|>_/___.' >'"". //
// | | : `- \`.;`\ _ /`;.`/ - ` : | | //
// \ \ `-. \_ __\ /__ _/ .-` / / //
// ========`-.____`-.___\_____/___.-`____.-'======== //
// `=---=' //
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //
// 佛祖保佑 永不宕机 永无BUG //
=====================================================================
spring boot:${spring-boot.version}
- ${AnsiColor.BRIGHT_CYAN}来设定banner字体
- ${AnsiBackground.BRIGHT_CYAN}来设定banner背景颜色
- ${AnsiStyle.UNDERLINE}设定字体样式
- ${spring-boot.version}查看当前版本
配置文件的拆分
情景是这样的,在不同环境下的配置不同,例如本地和上线的配置就不一样,改来改去的很麻烦,我们可以写几个配置文件将配置进行拆分
- 写几个配置文件,格式为application-dev/prod.yml(测试环境/成产环境)
- 在application.yml文件中添加一行spring.profiles.active=dev进行激活环境配置
注:如果不是application-dev.yml格式的话,在激活环境的时候需要写全名
创建自定义简单对象
在类上添加如下注解,再使用@Autowired自动注入对象
- @Service注解标注xxximpl类(实现dao层)
- @Controller
- @Repository
- …
注:@Bean注解创建的对象,@Autowired自动注入两个,用==
返回true
,应该在方法或类上加上@Scope("prototype")
注解实现多例模式,默认是singleton
单例模式
从配置文件获取变量值
配置文件添加了:
name=zrl
bir=2021/2/16
如果想在对象中取到这个值,需要使用
@Value("${name}")
private String name
@Value("${bir}")
private Date bir
yml数据结构格式:
-
数组:直接用逗号分隔,例如
aa,bb,cc
,注入时标明数组格式 -
列表:直接用逗号分隔,例如
aa,bb,cc
,注入时标明列表格式 -
Map:@Value取值格式是
#{${mapname}}
properties格式是mapname={key:value,key:value...}
,yml格式如下mapname.aa=111
mapname.bb=222
mapname.cc=333
-
对象:以user类,properties格式为例,
user.name=xx
,user.age=xx
,然后在实体类添加两个注解@Component
@ConfigurationProperties(prefix = "user")
Spring Boot的跨域请求
什么是跨域请求
浏览器
(APP不会)出于安全的考虑,使用ⅩIMlhttpRequest对象发起HTTP请求时必须遵守同源策略,否则就是跨域的HTTP请求,这种请求默认情况下是被禁止的。同源策略要求源相同才能正常进行通信,即协议、域名、端口号都完全一致。
CORS是—个W3C标准,全称是“跨域资源共享”( Cross-origin resource sharing),允许浏览器向跨源服务器,发出 XmlHttpreQuesT请求,从而克服了Ajax只能同源使用的限制。
它通过服务器增加个特殊的 Header[Access-Control- Allow-Origin]来告诉客户端跨域的限制,如果浏览器支CORS、并且判断 Origin通过的话,就会允许 XmlHttpReques发起跨域请求。
解决跨域问题的两种方式
方法一:给类或方法添加注解
@CrossOrigin
public class Test {}
方法二:创建一个配置类
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
@Configuration
public class CorsConfig {
@Bean
public FilterRegistrationBean corsFilter() {
// 注册CORS过滤器
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true); // 是否支持安全证书
config.addAllowedOrigin("*"); // 允许任何域名使用
config.addAllowedHeader("*"); // 允许任何头
config.addAllowedMethod("*"); // 允许任何方法(post、get等)
// 预检请求的有效期,单位为秒。
//config.setMaxAge(3600L);
source.registerCorsConfiguration("/**", config);
FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
bean.setOrder(0);
return bean;
}
}
解决跨域的原理
在请求头添加了:
Origin: http://ip:port
在响应头添加了:
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: http://ip:port
Spring Boot集成thymeleaf模板引擎
简单使用
Spring Boot推荐使用thymeleaf模板,因为thymeleaf模板使用的是html,可以直接运行,同时因为不需要转换提高了效率。同时为了spring做了使用方法,很方便。注意控制器的@Controller
注解一定要注意不要写成@RestController
,除此之外,直接返回页面时不要加/
,例如index
不要写成/index
,否则打包为jar包后会无法找到页面
依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
配置(这个不写也没关系,因为下面的都是默认配置):
spring.thymeleaf.prefix=classpath:/templates/ # 模板目录
spring.thymeleaf.suffix=.html # 模板后缀
spring.thymeleaf.encoding=UTF-8 # 模板编码
spring.thymeleaf.enabled=true # 开始thymeleaf模板
spring.thymeleaf.servlet.content-type=text/html # 使用模板响应类型
springboot.resources.static-location=classpath:/templates/,classpath:/static/ # 修改静态文件目录,这个默认不包含templates
控制器:
@Controller//一定要注意不要写成@RestController
@RequestMapping("/user")
public class UserController {
@GetMapping("/findAll")
public String findAll(){
return "index";//只写页面逻辑名
}
}
后端传值:
方法一,返回ModelAndView
@Controller
@RequestMapping("/user")
public class TestController {
@GetMapping("findAll")
public ModelAndView findAll(){
HashMap<String, Object> map = new HashMap<>();
map.put("name","xiaoming");
return new ModelAndView("index", map);
}
}
方法二:model.addAttribute()或request.setAttribute()
@Controller
@RequestMapping("/user")
public class TestController {
@GetMapping("findAll")
public String findAll(Model model){
model.addAttribute("name","xiaoming");
return "index";
}
}
thymeleaf语法
页面加入命名空间
<html lang="en" xmlns:th="http://www.thymeleaf.org">
表达式:
- ${}用的最多也是最强大的表达式,获取变量值;底层是OGNL;
- Selection Variable Expressions: *{…}:选择表达式:和${}在功能上是一样;
- Message Expressions: #{…}:获取国际化内容
- Link URL Expressions: @{…}:定义URL
@{/order/process(execId=${execId},execType='FAST')}
- Fragment Expressions: ~{…}:片段引用表达式,引入另一个界面
<div th:insert="~{commons :: main}">...</div>
表达式支持的语法:
Literals(字面量)
Text literals: 'one text' , 'Another one!' ,…
Number literals: 0 , 34 , 3.0 , 12.3 ,…
Boolean literals: true , false
Null literal: null
Literal tokens: one , sometext , main ,…
Text operations:(文本操作)
String concatenation: +
Literal substitutions: |The name is ${name}|
Arithmetic operations:(数学运算)
Binary operators: + , - , * , / , %
Minus sign (unary operator): -
Boolean operations:(布尔运算)
Binary operators: and , or
Boolean negation (unary operator): ! , not
Comparisons and equality:(比较运算)
Comparators: > , < , >= , <= ( gt , lt , ge , le )
Equality operators: == , != ( eq , ne )
Conditional operators:(条件运算)(三元运算符)
If-then: (if) ? (then)
If-then-else: (if) ? (then) : (else)
Default: (value) ?: (defaultvalue)
Special tokens:
No-Operation: _
综合案例:
<!DOCTYPE html>
<!--导入名称空间-->
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>成功</h1>
<!--th:text 将div的文本内容设置为-->
<div id="div01" class="myDiv" th:id="${hello}" th:class="${hello}" th:text="${hello}">这里显示的是欢迎信息</div>
<br>
<!--th:utext 不会转义特殊字符,会直接做出来一个h1标签,但是text不会,会显示<h1>你好</h1>-->
<div th:text="${hello}">这里显示的是欢迎信息</div>
<div th:utext="${hello}">这里显示的是欢迎信息</div>
<br>
<!--遍历-->
<h4 th:text="${user}" th:each="user:${users}"></h4>
<br>
<!--行内写法-->
<h4>
<span th:each="user:${users}">[[${user}}]]</span>
</h4>
</body>
</html>
thymeleaf常见问题
引入js等静态资源文件
<script src="./../static/layui/layui.all.js" th:src="@{/layui/layui.all.js}"></script>
th:src
在springboot中默认就是static文件夹下,所以不用在./…/
js引用thymeleaf值
注:script标签中 th:inline 一定不能少,通常在取值的前后会加上不同的注释
<script th:inline="javascript">
var message = [[${message}]];
console.log(message);
</script>
给a标签的href传递参数
<a th:href="@{/delete(id=${emp.id})}"> delete</a>
Spring Boot从后端传值给前端
前端直接使用Thymeleaf语法即可
使用ModelAndView+Map
ModelAndView是用来返回页面的,防止添加了@RestController
注解
@GetMapping("/seller/logout")
public ModelAndView logout(Map<String,Object> map){
map.put("msg","登出!");
map.put("url","/sell");
return new ModelAndView("common/success",map);
}
使用HttpServletRequest
注意不要加@RestController
注解
@GetMapping("/index")
public Object index(HttpServletRequest request) {
//先获取principal,这个是通过MyRealm的认证方法rutuen的,进行了注入
Object principal = SecurityUtils.getSubject().getPrincipal();
AccountProfile user = (AccountProfile) principal;
//添加session
request.setAttribute("username",user.getUsername());
return "index";
}
使用Model
@RequestMapping(value = "/")
public String index(Model model){
String students ="刘洋";
model.addAttribute("s",students)
return "index";
}
Spring Boot接收从前端传来数据
前后端同名传参(GET)
直接把表单的参数写在Controller相应的方法的形参中,提交的参数需要和Controller方法中的入参名称一致。
url形式:http://localhost:8080/addUser1?username=lixiaoxi&password=111111
@RequestMapping("/addUser1")
public String addUser1(String username,String password) {
System.out.println("username is:"+username);
System.out.println("password is:"+password);
return "demo/index";
}
@RequestParam(POST)
用@RequestParam
注解绑定请求参数到方法入参,当请求参数username不存在时会有异常发生,可以通过设置属性required=false
解决,例如: @RequestParam(value="username", required=false)
这种POST只适合普通表单的POST,如果是axios则需要使用@RequestBody
注解
@RequestMapping(value="/addUser6",method=RequestMethod.GET)
public String addUser6(@RequestParam("username") String username,@RequestParam("password") String password) {
System.out.println("username is:"+username);
System.out.println("password is:"+password);
return "demo/index";
}
@RequestBody(axios的POST)
创建一个实体类,用于接收所有类型的POST请求,请求参数和UserModel属性相对应
@RequestMapping("/addUser3")
public String addUser3(@Validated @RequestBody UserModel user) {
System.out.println("username is:"+user.getUsername());
System.out.println("password is:"+user.getPassword());
return "demo/index";
}
RequestBody可以使用Map作为兼容,同时axios不用进行设置请求头
@PathVariable获取路径中的参数
例如,访问http://localhost/addUser4/lixiaoxi/111111 路径时,则自动将URL中模板变量{username}和{password}绑定到通过@PathVariable注解的同名参数上,即入参后username=lixiaoxi、password=111111。
@RequestMapping(value="/addUser4/{username}/{password}",method=RequestMethod.GET)
public String addUser4(@PathVariable String username,@PathVariable String password) {
System.out.println("username is:"+username);
System.out.println("password is:"+password);
return "demo/index";
}
HttpServletRequest接收(GET、POST)
通过HttpServletRequest接收,post方式和get方式都可以
@RequestMapping("/addUser2")
public String addUser2(HttpServletRequest request) {
String username=request.getParameter("username");
String password=request.getParameter("password");
System.out.println("username is:"+username);
System.out.println("password is:"+password);
return "demo/index";
}
axios 默认是 Payload 格式进行数据请求,需要为 axios 的 post 请求设置请求头
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
@ModelAttribute(POST)
使用@ModelAttribute注解获取POST请求的FORM表单数据
<form action ="<%=request.getContextPath()%>/demo/addUser5" method="post">
用户名: <input type="text" name="username"/><br/>
密 码: <input type="password" name="password"/><br/>
<input type="submit" value="提交"/>
<input type="reset" value="重置"/>
</form>
@RequestMapping(value="/addUser5",method=RequestMethod.POST)
public String addUser5(@ModelAttribute("user") UserModel user) {
System.out.println("username is:"+user.getUsername());
System.out.println("password is:"+user.getPassword());
return "demo/index";
}
Spring Boot整合Mybatis
添加依赖:
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--阿里巴巴-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.12</version>
</dependency>
添加配置:
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
username: root
password: 123456
url: jdbc:mysql://localhost:3306/sell
mybatis:
mapper-locations: classpath:/com/baizhi/mapper/*.xml # mapper位置
type-aliases-package: com.baizhi.entity # 实体类位置
configuration:
map-underscore-to-camel-case: true # 开启自动驼峰命名
入口类添加注解:
# 注意
必须在xxxApplication.java入口类上加上@mapperScan("com.xxx.xxx.dao")注解,扫描DAO接口所在包,或者是在dao层的每个接口都加上@Mapper注解
使用注解方式操作数据库,直接在dao层加上注解:
@Select("select xxxx where id=#{id}")
public Blog selectBlog(int id)
@Delete("delete xxxx where id=#{blog.id}")
public int selectBlog(Blog blog)
使用xml配置文件方式操作数据库,即写mapper:
<?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="org.mybatis.example.BlogMapper">
<select id="selectBlog" resultType="Blog">
select * from Blog where id = #{id}
</select>
</mapper>
Spring Boot的devtools热部署
热部署就是在不重启Spring Boot项目的情况下,让我们的更改生效。这样在启动项目的时候会创建两个类加载器,dev-tools是通过两个类加载器实现热部署的,更改后会由空闲的进行编译,然后会进行类加载器的切换,空闲的替换正在工作的。甚至不用点小锤子
依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
在idea中设置支持自动编译
# 开启自动编译
Preferences-->Build,Execution,Development-->Compiler-->勾选Build project automatically
# 开启允许在运行过程中修改文件
ctrl+alt+shift+/---->选择Registry---->勾选compiler.automake.allow.when.app.running
关闭thymeleaf缓存
springboot.thymeleaf.cache=false
Spring Boot日志的使用
logback、log4j啥的使用起来挺麻烦的,直接使用插件吧,需要安装lombok
插件并引入lombok
依赖
日志级别:debug < info < warn < error < off
控制台输出默认是info级别,可以在配置文件修改当前包的日志级别
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
@Slf4j
class TestControllerTest {
@Test
void test1() {
log.debug("嘿嘿嘿");
log.info("哈喽,你好啊");
log.error("出错啦,问题是{}","没事");
}
}
或者是不使用插件:
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
class TestControllerTest {
@Test
void test1() {
Logger logger = LoggerFactory.getLogger(User.class);
logger.debug("xxx");
}
}
Spring Boot实现文件上传
上传界面:
<form method="post" action="路径" enctype="multipart/form-data">
<input type="file" name="file"><br>
<input type="submit" value="提交">
</form>
<!--
提交方式必须为post
enctype属性必须为multipart/form-data
后台方法接收MultipartFile变量名字要与文件选择的name属性一致,可以都写为file
-->
控制器:
@PostMapping("/upload")
public String upload(MultipartFile file, HttpServletRequest request) throws IOException {
//获取绝对路径
//String realPath = request.getServletContext().getRealPath("/files");这个是服务器的临时目录,不推荐
String realPath = ResourceUtils.getURL("classpath:").getPath()+"/static/files";
System.out.println(realPath);
File dir = new File(realPath);
if(!dir.exists()) {//如果路径不存在就创建文件夹
dir.mkdir();
}
//文件相关信息
System.out.println(file.getName());//文件名
System.out.println(file.getSize());//文件大小
System.out.println(file.getContentType());//文件类型
file.transferTo(new File(dir,file.getOriginalFilename()));//文件上传
return "redirext:index";//不重定向的话表单会重复提交
}
修改配置文件的允许上传最大文件大小,不写默认为10MB:
# 不写单位的话默认是以字节为单位
# 服务器最大文件大小
spring.servlet.multipart.max-file-size=500MB
# 文件上传最大大小
spring.servlet.multipart.max-request-size=500MB
文件上传控制器优化:
@PostMapping("/upload")
public String upload(MultipartFile file, HttpServletRequest request) throws IOException {
//获取绝对路径
String realPath = ResourceUtils.getURL("classpath:").getPath()+"/static/files";
//按日期放置文件
String dateDir = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
File dir = new File(realPath,dateDir);
if(!dir.exists()) {//如果路径不存在就创建文件夹
dir.mkdir();
}
//修改文件名
//前缀
String newFileNamePrefix = new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date())+ UUID.randomUUID().toString();
//后缀
/*
需要引入依赖
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
*/
String extension = FilenameUtils.getExtension(file.getOriginalFilename());
String newFileName = newFileNamePrefix+"."+extension;
file.transferTo(new File(dir,newFileName));//文件上传
return "redirect:index";
}
Spring Boot实现文件下载
下载界面:
<a href="../file/download?fileName=text.txt">text.txt</a>
控制器1——不直接下载文件而是点一下展示文件:
@GetMapping("download")
public void download(String fileName, HttpServletResponse response) throws Exception {
String realPath = ResourceUtils.getURL("classpath:").getPath()+"/static/files";
//获取文件输入流
FileInputStream is = new FileInputStream((new File(realPath, fileName)));
//相应输出流
ServletOutputStream os = response.getOutputStream();
//文件拷贝
int len = 0;
byte[] b = new byte[1024];
while(true) {
len = is.read(b);
if(len==-1) {
break;
}
os.write(b,0,len);
}
is.colse();
os.close();
}
控制器2——直接下载文件:
@GetMapping("download")
public void download(String fileName, HttpServletResponse response) throws Exception {
String realPath = ResourceUtils.getURL("classpath:").getPath()+"/static/files";
//获取文件输入流
FileInputStream is = new FileInputStream((new File(realPath, fileName)));
//相应输出流
ServletOutputStream os = response.getOutputStream();
//attachment附件下载,inline是在线打开(图片,pdf等)
response.setHeader("content-disposition","attachment;fileName="+ URLEncoder.encode(fileName,"UTF-8"));
//拷贝文件
/*
需要引入依赖
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
*/
IOUtils.copy(is,os);
IOUtils.closeQuietly(is);
IOUtils.closeQuietly(os);
}
Spring Boot中的拦截器开发
如果在每个控制器(controller)方法中都使用session.grtAttribute("user")
会显得十分冗余,我们可以使用拦截器,请求先经过拦截器,通过了然后再进入控制器。
作用:通过拦截器执行通用代码逻辑,以减少控制器中代码冗余
特点:
- 只能拦截控制器相关请求,不能拦截静态资源和页面相关情况(css,js…)
- 请求发送经过拦截器,响应回来同样经过拦截器
- 拦截器可以中断用户请求
- 拦截器可以针对性拦截某些控制器请求
拦截器开发流程:
# 1、类(xxxInterceptor) implements HandlerInterceptor
preHandler----预先处理,返回值true放行到controller中,false中断请求同时在原页面
postHandler----请求过程中处理,即controller执行之后的操作
afterCompletion----响应之后执行
# 2、springboot中配置拦截器
注册到springboot拦截数组中,方法是写一个配置类
拦截器:
public class MyInterceptor implements HandlerInterceptor {
//实现你想要的接口即可,可以只实现一个,一般放在interceptor包下
}
配置类——1.x版本:
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
@Configuration
public class InterceptorConfig extends WebMvcConfigurerAdapter {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor())//添加拦截器
.addPathPatterns("/**")//定义拦截请求
.excludePathPatterns("/hello/**");//排除拦截请求
}
}
配置类——2.x版本:
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
//即继承变成了实现接口,JDK8有了接口的默认实现,因此只实现一个方法也是可以的
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor())//添加拦截器
.addPathPatterns("/**")//定义拦截路径
.excludePathPatterns("/hello/**");//排除拦截路径
}
}
拦截器执行顺序:
配置类中先添加的inter1,再添加的inter2
Spring Boot面向切面的编程
springboot支持spring中的aop编程,切面就是将现有功能进行抽取,将这个功能应用在各种各样的业务处理上,达到代码复用,例如,可以把蓝色圈看成业务层,里面套了性能处理,外面套了整体业务执行的处理
依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
三种切面:
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
@Aspect
@Configuration
@Order(1)//多个环绕通知可以加这个注解,数字越小越先执行,这个注解现在只能加在类上,加在方法上已经不生效了
public class MyAspect {
//环绕通知,用的比较多,目标方法执行时会先进入环绕通知,放行后执行目标方法,目标方法执行完后回到环绕通知
@Around("within(com.example.service.*ServiceImpl)")
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("进入环绕通知业务处理");
Object proceed = proceedingJoinPoint.proceed();//放行,去执行业务方法
System.out.println("方法执行后的业务处理逻辑如下:");
//todo
return proceed;
}
//前置通知,在目标方法执行前执行
@Before("within(com.example.service.*ServiceImpl)")
public void before(JoinPoint joinPoint) {
System.out.println("目标方法名称:"+joinPoint.getSignature().getName());
System.out.println("目标参数:"+joinPoint.getArgs());
System.out.println("目标对象:"+joinPoint.getTarget());
System.out.println("前置业务通知");
}
//后置通知,在目标方法执行后执行
@After("within(com.example.service.*ServiceImpl)")
public void after(JoinPoint joinPoint) {
System.out.println("目标方法名称:"+joinPoint.getSignature().getName());
System.out.println("目标参数:"+joinPoint.getArgs());
System.out.println("目标对象:"+joinPoint.getTarget());
System.out.println("后置业务通知");
}
}
Spring Boot手写starter
命名规范:第三方的starter应该是类似projectName-spring-boot-starter
的格式
starter中真正工作的是starter的自动配置类
引入依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
写属性类:
@Component
@Getter
@Setter
@ConfigurationProperties(prefix = "tx.redis")
public class RedisProperties {
private int database = 0;
private String url;
private String host = "localhost";
private String password;
private int port;
}
写自动配置类:
@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
public class RedisAutoConfiguration {
@Autowired
RedisProperties properties;
@Bean
public RedisTemplate<Object, Object> redisTemplate() {
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
JedisConnectionFactory factory = new JedisConnectionFactory(new RedisStandaloneConfiguration(properties.getHost()));
redisTemplate.setConnectionFactory(factory);
return redisTemplate;
}
}
在resources
文件夹中创建名为META-INF
的文件夹,创建spring.factories
文件,第二行写自动配置类的地址
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.demo.auto.RedisAutoConfiguration
测试自写的starter:
可以在yml文件中,通过tx.redis配置到配置信息,酷酷的。然后可以直接使用RedisTemplate
这个类,因为在spring.factories
文件中做了键值对配置,会被自动扫描。
在使用的时候,有时候会遇到加上@Qualifier("testClass1")
这个注解的时候,作用是对自动配置类中,不同方法产生了相同对象进行类区,详情可以参看https://blog.csdn.net/qq_30062181/article/details/107619529
Spring Boot开发通用手段
# 删除数据
发起请求,在请求中进行重定向界面
# 修改数据
和创建用户一个界面,只不过需要判断是否有id,有的话进行填充数据,提交就是保存
或者时重新写一个页面
# 页面重定向
在return页面的时候,写成`return "redirect:index"`,即重定向到index界面,或者请求,注意不要写RestController
# 支持事务
@Transactional(propagation = Propagation.SUPPORTS)
# 使用Map作为返回值
如果业务简单可以使用Map作为返回值,避免了实体类的复杂处理
Spring Boot开发全局异常处理
@ControllerAdvice+@ExceptionHandler的形式:
创建一个类,作为Controller的全局异常,类通过@ControllerAdvice修饰,方法通过@ExceptionHandler修饰
//vaule可以是多个异常,用数组分开
@ExceptionHandler(value={xxxx.class})
public ModelAndView xxxxHandler(Exception e) {
return new ModelAndView("error.html",map);
}
注:只是在Controller中添加@ExceptionHandler方法不能达到全局的效果,需要使用@ControllerAdvice
使用SimpleMappingExceptionResolver的形式:创建配置类但是无法传递给界面异常信息,因此也不常用了