配套视频:【编程不良人】2021年SpringBoot最新最全教程_哔哩哔哩_bilibili
【编程不良人】2021年SpringBoot最新最全教程_哔哩哔哩_bilibili
springboot 微框架 = spring 工厂 + springmvc 控制器 微:快速开发框架 通过遵守默认约定 简化项目中样板化配置 数据库访问框架: hibernate jpa mybatis(框架) 回顾: spring框架如何整合mybatis? 1.引入依赖 spring 相关 mysql 相关 驱动 数据源 mybatis 相关 mybatis核心jar mybatis和spring框架整合 2.spring.xml a.开启注解扫描 b.创建数据源对象 注入 指定使用那种数据源类型 注入dirverClassName 注入url 注入username password c.创建sqlSessionFactory 注入 数据源 注入mapper配置文件位置 注入实体别名包 d.创建Dao对象 注入SqlSessionFactory 以及Dao接口所在包 e.创建事务管理器 DataSourceTranacationManager 注入 数据源对象 f.在业务层组件上加入事务注解 @Transacational <tx:annotation-driven transaction-manager="transactionManager"/> 3.测试 1).建表 2).开发实体类 3).开发DAO接口 4).开发Mapper配置文件 5).开发Service接口 6).开发ServiceImpl实现类 7).测试ServiceImpl =========================================================================== springboot框架中如何整合mybatis框架? 1.引入依赖 spring-boot-stater-web mysql相关 mysql驱动 druid数据源 mybatis相关的 (mybatis-spring-boot-stater) 依赖 (mybatis mybatis-spring) 2.书写配置 a.开启注解扫描 @SpringBootApplication @ComponentScan 省略 b.创建数据源 1.指定数据源类型 2.指定数据库驱动 3.指定url 4.指定username 5.指定password c.创建SqlSessionFactory 1.指定mapper配置文件位置 2.指定实体所在包位置 起别名 d.创建DAO 1.指定DAO接口所在包 e.创建事务管理器 开启注解式事务生效 省略 3.测试 1).建表 2).开发实体类 3).开发DAO接口 4).开发Mapper配置文件 5).开发Service接口 6).开发ServiceImpl实现类 7).测试ServiceImpl
<!--druid--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.4</version> </dependency> <!--mysql--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.38</version> </dependency> <!--mybatis-spring-boot-starter--> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.4</version> </dependency>
# 公共配置 server: port: 8888 # 修改内置tomcat端口号 servlet: context-path: /spring-boot-day03 # 数据源相关配置 spring: datasource: type: com.alibaba.druid.pool.DruidDataSource # 指定连接池类型 driver-class-name: com.mysql.jdbc.Driver # mysql 5.x版本驱动 # driver-class-name: com.mysql.cj.jdbc.Driver # mysql 8.x版本驱动 url: jdbc:mysql://localhost:3306/bootssm?characterEncoding=UTF-8 # 指定url username: root # 指定用户名和密码 password: 123456 # 指定密码 # mybatis相关配置 mybatis: mapper-locations: classpath:com/study/mapper/*.xml # 指定mapper配置文件位置 type-aliases-package: com.study.entity # 指定实体类的包名,默认别名:类名或类名首字母小写两种
package com.study; import org.apache.ibatis.annotations.Mapper; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; //入口类加入如下配置: @SpringBootApplication /** * @Mapper 注解 * 修饰范围:只能放在DAO接口上,且每个DAO接口都需要放置此注解 * 作用:用来在工厂中创建DAO对象 */ //@Mapper /** * @MapperScan 注解 * 修饰范围:用在类上 * 作用:用来扫描DAO接口所在包,同时将所有DAO接口在工厂中创建对象 */ @MapperScan("com.study.dao") public class SpringBootDay03Application { public static void main(String[] args) { SpringApplication.run(SpringBootDay03Application.class, args); } }
CREATE DATABASE IF NOT EXISTS bootssm; USER bootssm; CREATE TABLE IF NOT EXISTS t_user( id INT(11) NOT NULL AUTO_INCREMENT COMMENT '主键id', name VARCHAR(40) COMMENT '姓名', birthday TIMESTAMP COMMENT '生日', salary DOUBLE(10,2) COMMENT '工资', PRIMARY KEY(id) )ENGINE=INNODB DEFAULT CHARSET=utf8mb4;
User
package com.study.entity; import java.util.Date; /** * @ClassName User * @Description TODO * @Author Jiangnan Cui * @Date 2022/5/3 10:02 * @Version 1.0 */ public class User { private Integer id; private String name; private Date birthday; private Double salary; public User() { } public User(Integer id, String name, Date birthday, Double salary) { this.id = id; this.name = name; this.birthday = birthday; this.salary = salary; } @Override public String toString() { return "User{" + "id=" + id + ", name='" + name + '\'' + ", birthday=" + birthday + ", salary=" + salary + '}'; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } public Double getSalary() { return salary; } public void setSalary(Double salary) { this.salary = salary; } }
UserDAO
package com.study.dao; import com.study.entity.User; import java.util.List; /** * @ClassName UserDAO * @Description TODO * @Author Jiangnan Cui * @Date 2022/5/3 10:04 * @Version 1.0 */ public interface UserDAO { /** * @MethodName findAll * @Description 查询所有用户信息 * @return: java.util.List<com.study.entity.User> * @Author Jiangnan Cui * @Date 2022/5/3 10:04 */ List<User> findAll(); /** * @MethodName save * @Description 保存用户信息 * @param: user * @Author Jiangnan Cui * @Date 2022/5/3 10:05 */ void save(User user); }
UserDAOMapper(resources:com/study/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="com.study.dao.UserDAO"> <!--findAll--> <select id="findAll" resultType="User"> select id,name,birthday,salary from t_user </select> <!--save--> <insert id="save" parameterType="User" useGeneratedKeys="true" keyProperty="id"> insert into t_user values(#{id},#{name},#{birthday},#{salary}) </insert> </mapper>
package com.study.service; import com.study.entity.User; import java.util.List; /** * @ClassName UserService * @Description TODO * @Author Jiangnan Cui * @Date 2022/5/3 10:06 * @Version 1.0 */ public interface UserService { /** * @MethodName findAll * @Description 查询所有用户信息 * @return: java.util.List<com.study.entity.User> * @Author Jiangnan Cui * @Date 2022/5/3 10:04 */ List<User> findAll(); /** * @MethodName save * @Description 保存用户信息 * @param: user * @Author Jiangnan Cui * @Date 2022/5/3 10:05 */ void save(User user); }
UserServiceImpl
package com.study.service; import com.study.dao.UserDAO; import com.study.entity.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import java.util.List; /** * @ClassName UserServiceImpl * @Description TODO * @Author Jiangnan Cui * @Date 2022/5/3 10:10 * @Version 1.0 */ @Service("userService") @Transactional public class UserServiceImpl implements UserService{ private UserDAO userDAO; @Autowired public UserServiceImpl(UserDAO userDAO) { this.userDAO = userDAO; } @Override @Transactional(propagation = Propagation.SUPPORTS) public List<User> findAll() { return userDAO.findAll(); } @Override public void save(User user) { userDAO.save(user); } }
UserController
package com.study.controller; import com.study.entity.User; import com.study.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List; /** * @ClassName UserController * @Description TODO * @Author Jiangnan Cui * @Date 2022/5/3 10:12 * @Version 1.0 */ @RestController @RequestMapping("user") public class UserController { private UserService userService; @Autowired public UserController(UserService userService) { this.userService = userService; } /** * @MethodName findAll * @Description 查询所有用户信息 * @return: java.util.List<com.study.entity.User> * @Author Jiangnan Cui * @Date 2022/5/3 10:14 */ @RequestMapping("findAll") public List<User> findAll(){ return userService.findAll(); } /** * @MethodName save * @Description 添加用户信息 * @param: user * @Author Jiangnan Cui * @Date 2022/5/3 10:15 */ @RequestMapping("save") public void save(User user){ userService.save(user); } }
查询所有用户访问地址:http://localhost:8888/spring-boot-day03/user/findAll
添加用户信息访问地址:http://localhost:8888/spring-boot-day03/user/save?name=赵六&birthday=2013/08/04&salary=2000
配套视频:【编程不良人】2021年SpringBoot最新最全教程_哔哩哔哩_bilibili
/** * @Mapper 注解 * 修饰范围:只能放在DAO接口上,每个接口都需要放置 * 作用:用来在工厂中创建DAO对象 */ /** * @MapperScan 注解 推荐使用 * 修饰范围:用在类上,可以一次性扫描所有DAO接口所在包中的接口 * 作用:用来扫描DAO接口所在包,同时将所有DAO接口在工厂中创建对象 */
配套视频:【编程不良人】2021年SpringBoot最新最全教程_哔哩哔哩_bilibili
往往在开发过程中业务代码课程非常复杂频繁启动服务器测试,非常麻烦!这个时候使用本地测试就是一个很好的解决方案,springboot也提供了本地测试解决方案!
spring中本地测试 1.启动工厂 ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml"); 2.从工厂中获取指定对象 UserDao userDao = context.getBean("userDao"); 3.调用方法 userDao.xxx(参数); springboot框架中完成本地测试? springboot = spring + springmvc 1.引入结合junit和springboot依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <!--只能在测试时可用--> <scope>test</scope> </dependency> 2.启动springboot应用才能spring工厂启动,注入测试对象 @SpringBootTest 修饰范围: 用在类上 作用: 在这个类实例化过程中启动springboot应用
<!--spring-boot-starter-test集成了Junit单元测试--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>
@SpringBootTest
- 修饰范围:用在类上
- 作用:用来启动本地Spring环境
项目自带的测试类XxxApplication(通过SpringInitializr创建的项目)
package com.study; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest class SpringBootDay03ApplicationTests { @Test void contextLoads() { } }
实际应用时一般都是通过子测试类继承基本测试类(父测试类)来进行测试,具体如下:
父测试类:BasicTests
package com.study; import org.springframework.boot.test.context.SpringBootTest; /** * @ClassName BasicTests * @Description SpringBoot基础测试类 * @Author Jiangnan Cui * @Date 2022/5/5 10:27 * @Version 1.0 */ @SpringBootTest public class BasicTests { }
子测试类:UserDAOTests
package com.study; import com.study.dao.UserDAO; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; /** * @ClassName UserDAOTests * @Description 测试UserDAO * @Author Jiangnan Cui * @Date 2022/5/5 10:29 * @Version 1.0 */ public class UserDAOTests extends BasicTests{ @Autowired private UserDAO userDAO; /** * @MethodName testFindAll * @Description 测试查询所有 * @Author Jiangnan Cui * @Date 2022/5/5 10:31 */ @Test public void testFindAll(){ userDAO.findAll().forEach(user -> System.out.println("user = " + user)); } }
子测试类:UserServiceTests
package com.study; import com.study.entity.User; import com.study.service.UserService; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; /** * @ClassName UserServiceTests * @Description 测试UserService * @Author Jiangnan Cui * @Date 2022/5/5 10:32 * @Version 1.0 */ public class UserServiceTests extends BasicTests{ @Autowired private UserService userService; //测试查询所有 @Test public void testFindAll(){ for (User user : userService.findAll()) { System.out.println("user = " + user); } } }
3. 热部署工具
配套视频:【编程不良人】2021年SpringBoot最新最全教程_哔哩哔哩_bilibili
为了进一步提高开发效率,springboot为我们提供了全局项目热部署,日后在开发过程中如果修改了部分代码以及相关配置文件后,不需要每次重启项目使修改生效,在项目中如果开启了springboot全局热部署之后,只需要在修改之后等待几秒即可使修改生效【底层通过强制刷新生成新的.class文件,jvm通过对比旧class与新class进行替换,运行新的class文件】。
3.1 pom.xml中引入依赖 (每次搭建项目后都需要引入)
<!--热部署依赖 devtools--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <!--optional:用于表示该依赖是否可以进行传递 false:能传递(默认) true:不能传递,只在当前项目可用--> <optional>true</optional> </dependency>
添加依赖后记得刷新Maven,否则热部署不能生效!!!
3.2 设置idea中支持自动编译(只需要设置一次)
1.开启自动编译(只要不换电脑或者重新安装idea,此操作只进行一次即可)
实现:
(1)视频中操作:Preferences | Build, Execution, Deployment | Compiler -> 勾选上 Build project automatically 这个选项
(2)实际操作:File---Setting---Build,Execution,Deployment---Compiler---勾选上Build project automatically选项---Apply---OK
2.开启允许在运行过程中修改文件
实现:
任意空白位置---Ctrl+Alt+Shift+/---选择1.Registry...---勾选compiler.automake.allow.when.app.running选项
3.3 启动项目检测热部署是否生效
修改内容后进行保存,等待项目自动进行编译,日志中由main变为restartedMain表示热部署生效
2022-05-16 16:15:49.676 INFO 6128 --- [ restartedMain] com.study.SpringBootDay03Application : Starting SpringBootDay03Application using Java 1.8.0_131 on cjn-PC with PID 6128 (D:\Software_Development\IDEA_code\SpringBoot\spring-boot-day03\target\classes started by cjn in D:\Software_Development\IDEA_code\SpringBoot\spring-boot-day03) 2022-05-16 16:15:49.676 INFO 6128 --- [ restartedMain] com.study.SpringBootDay03Application : No active profile set, falling back to default profiles: default 2022-05-16 16:15:49.825 INFO 6128 --- [ restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8888 (http) 2022-05-16 16:15:49.826 INFO 6128 --- [ restartedMain] o.apache.catalina.core.StandardService : Starting service [Tomcat] 2022-05-16 16:15:49.826 INFO 6128 --- [ restartedMain] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.46] 2022-05-16 16:15:49.843 INFO 6128 --- [ restartedMain] o.a.c.c.C.[.[.[/spring-boot-day03] : Initializing Spring embedded WebApplicationContext 2022-05-16 16:15:49.843 INFO 6128 --- [ restartedMain] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 165 ms 2022-05-16 16:15:49.949 INFO 6128 --- [ restartedMain] o.s.b.d.a.OptionalLiveReloadServer : LiveReload server is running on port 35729 2022-05-16 16:15:49.959 INFO 6128 --- [ restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8888 (http) with context path '/spring-boot-day03' 2022-05-16 16:15:49.962 INFO 6128 --- [ restartedMain] com.study.SpringBootDay03Application : Started SpringBootDay03Application in 0.306 seconds (JVM running for 259.773) 2022-05-16 16:15:49.963 INFO 6128 --- [ restartedMain] o.s.b.a.ApplicationAvailabilityBean : Application availability state LivenessState changed to CORRECT 2022-05-16 16:15:49.964 INFO 6128 --- [ restartedMain] .ConditionEvaluationDeltaLoggingListener : Condition evaluation unchanged 2022-05-16 16:15:49.964 INFO 6128 --- [ restartedMain] o.s.b.a.ApplicationAvailabilityBean : Application availability state ReadinessState changed to ACCEPTING_TRAFFIC
注意:日志出现restartedMain代表已经生效,在使用热部署时如果遇到修改之后不能生效,请重试重启项目再试
4. 日志处理
配套视频:【编程不良人】2021年SpringBoot最新最全教程_哔哩哔哩_bilibili
4.1 引言
springboot框架集成了logback 日志, Logback是由log4j创始人设计的又一个开源日志组件。目前,logback分为三个模块:logback-core,logback-classic和logback-access,logback-core是其它两个模块的基础模块;logback-classic是log4j的一个改良版本。此外logback-classic完整实现SLF4J API使你可以很方便地更换成其它日志系统如log4j或JDK14 Logging;logback-access访问模块与Servlet容器集成提供通过Http来访问日志的功能。该日志是对log4j日志展示进一步改进!即: logback 也是一个开源日志组件,和 log4j作用一致,都是用来生成日志,两者相比logback更加轻量!
4.2 日志的级别
级别从下到上依次提高,级别越高,输出的信息越详细:
级别从高到低依次为: All < Trace < `DEBUG < INFO < WARN < ERROR` < Fatal < OFF - OFF | 关闭:最高级别,不打印日志。 - FATAL | 致命:指明非常严重的可能会导致应用终止执行错误事件。 - ERROR | 错误:指明错误事件,但应用可能还能继续运行。 - WARN | 警告:指明可能潜在的危险状况。 - INFO | 信息:指明描述信息,从粗粒度上描述了应用运行过程。 - DEBUG | 调试:指明细致的事件信息,对调试应用最有用。 - TRACE | 跟踪:指明程序运行轨迹,比DEBUG级别的粒度更细。 - ALL | 所有:所有日志级别,包括定制级别。 日志级别由低到高: 日志级别越高输出的日志信息越多
4.3 项目中日志分类
# 日志分类: - 一种是rootLogger(根全局日志) : 用来监听项目中所有的运行日志,包括引入依赖jar中的日志 - 一种是logger(指定包级别日志) : 用来监听项目中指定包中的日志信息
4.4 配置日志
注意:SpringBoot框架中默认根日志为INFO级别
logging: level: root: debug # 指定根日志级别(一般不推荐修改根日志,输出信息太多,推荐使用子日志) com.study.dao: debug #指定dao包中日输出级别 file: name: debug.log # 指定生成的日志另存为文件的名称 path: ./ # 指定日志文件在项目目录下保存
4.5 项目中使用日志
@Controller public class HelloController { //声明日志成员 private static final Logger log = LoggerFactory.getLogger(HelloController.class); @RequestMapping("/hello") @ResponseBody public String hello(){ System.out.println("======hello world======="); log.debug("DEBUG,{}","信息"); log.info("INFO,{}","信息"); log.warn("WARN,{}","信息"); log.error("ERROR,{}","信息"); return "hello"; } }
4.6 具体实现
新建项目spring-boot-day4,新建包、类
a.导入依赖
<!--druid--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.4</version> </dependency> <!--mysql--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.38</version> </dependency> <!--mybatis-spring-boot-stater --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.4</version> </dependency>
b.修改application.properties为application.yml,配置参数
# 公共配置 server: port: 8888 servlet: context-path: /spring-boot-day4 # 整合Mybatis # 数据库配置 spring: datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/bootssm?characterEncoding=UTF-8&useSSL=true username: root password: 123456 # 别名配置 mybatis: mapper-locations: classpath:com/study/mapper/*.xml type-aliases-package: com.study.entity # 配置日志 logging: level: root: info # 默认根日志级别为info com.study.dao: debug # 指定某个包的输出日志级别 com.study.service: debug file: name: run.log # 指定生成日志文件的名称 path: ./ # 将日志文件保存在当前项目目录下
c.新建实体类
package com.study.entity; import javafx.beans.binding.DoubleExpression; import java.util.Date; /** * @ClassName User * @Description TODO * @Author Jiangnan Cui * @Date 2022/5/28 11:24 * @Version 1.0 */ public class User { private Integer id; private String name; private Date birthday; private Double salary; public User() { } public User(Integer id, String name, Date birthday, Double salary) { this.id = id; this.name = name; this.birthday = birthday; this.salary = salary; } @Override public String toString() { return "User{" + "id=" + id + ", name='" + name + '\'' + ", birthday=" + birthday + ", salary=" + salary + '}'; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } public Double getSalary() { return salary; } public void setSalary(Double salary) { this.salary = salary; } }
d.新建DAO接口
package com.study.dao; import com.study.entity.User; import java.util.List; /** * @ClassName UserDAO * @Description TODO * @Author Jiangnan Cui * @Date 2022/5/28 11:27 * @Version 1.0 */ public interface UserDAO { /** * @MethodName findAll * @Description 查询所有 * @return: java.util.List<com.study.entity.User> * @Author Jiangnan Cui * @Date 2022/5/28 11:28 */ List<User> findAll(); /** * @MethodName save * @Description 新增用户 * @param: user * @Author Jiangnan Cui * @Date 2022/5/28 11:28 */ void save(User user); }
e.新建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="com.study.dao.UserDAO"> <!--查询所有--> <select id="findAll" resultType="User"> select id,name,birthday,salary from t_user </select> <!--新增用户--> <insert id="save" useGeneratedKeys="true" keyProperty="id"> insert into t_user values (#{id},#{name},#{birthday},#{salary}) </insert> </mapper>
f.新建Service
package com.study.service; import com.study.entity.User; import java.util.List; /** * @ClassName UserService * @Description TODO * @Author Jiangnan Cui * @Date 2022/5/28 11:34 * @Version 1.0 */ public interface UserService { /** * @MethodName findAll * @Description 查询所有 * @return: java.util.List<com.study.entity.User> * @Author Jiangnan Cui * @Date 2022/5/28 11:28 */ List<User> findAll(); /** * @MethodName save * @Description 新增用户 * @param: user * @Author Jiangnan Cui * @Date 2022/5/28 11:28 */ void save(User user); }
h.新建ServiceImpl
package com.study.service; import com.study.dao.UserDAO; import com.study.entity.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import java.util.List; /** * @ClassName UserServiceImpl * @Description TODO * @Author Jiangnan Cui * @Date 2022/5/28 11:34 * @Version 1.0 */ @Service("userService") @Transactional public class UserServiceImpl implements UserService { private UserDAO userDAO; @Autowired public UserServiceImpl(UserDAO userDAO) { this.userDAO = userDAO; } @Override @Transactional(propagation = Propagation.SUPPORTS) public List<User> findAll() { return userDAO.findAll(); } @Override public void save(User user) { userDAO.save(user); } }
i.新建Controller
package com.study.controller; import com.study.service.UserService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @ClassName UserController * @Description TODO * @Author Jiangnan Cui * @Date 2022/5/28 12:03 * @Version 1.0 */ @RestController @RequestMapping("user") public class UserController { //声明日志成员 private static final Logger log = LoggerFactory.getLogger(UserController.class); private UserService userService; @Autowired public UserController(UserService userService) { this.userService = userService; } @RequestMapping("test") public String test(){ log.debug("debug,{}","信息1");//根日志默认为info级别,不能输出debug信息 log.info("info,{}","信息2"); log.warn("warn,{}","信息3"); log.error("error,{}","信息4"); return "Ok"; } }
4.7 IDEA安装Log Support 2插件
方法1:在Settings的Plungins中搜索插件安装,安装后重启IDEA
方法2:搜索不到时,下载插件进行离线安装(idea 2020版本好像不兼容Log Support 2 2021版本)
idea官网下载离线安装:JetBrains Marketplace
下载压缩包后不用解压,通过Install Plugin from Disk选择下载好的压缩包即可,之后重启idea。
安装好插件后,重启idea,还需进行如下配置:
(1)选择slf4j
(2)取消AndroidLog
之后就可以使用了,比如键入logi就会出现log.info("");并且自动添加private static final Logger log = LoggerFactory.getLogger(UserController.class);其中,logd 代表log.debug、logw 代表log.warn...以此类推。
注意:以后切换工作空间或者打开一个新的项目,都需要在设置中的Log Support中制定一下需要使用的日志框架
参考链接:
- Log Support2日志插件(springboot)_不秃头的小黄人的博客-CSDN博客_springboot 日志插件
- idea日志插件的安装与使用 - 简书
- 百度安全验证
- SpringBoot日志配置_IT小红袜的博客-CSDN博客
5. 面向切面编程(AOP)
配套视频:【编程不良人】2021年SpringBoot最新最全教程_哔哩哔哩_bilibili
5.1 引言
springboot是对原有项目中spring框架和springmvc的进一步封装,因此在springboot中同样支持spring框架中AOP切面编程,不过在springboot中为了快速开发仅仅提供了注解方式的切面编程。
5.2 项目环境搭建
新建项目spring-boot-day5
1.修改application.properties为application.yml,添加配置信息
server: port: 8888 servlet: context-path: /spring-boot-day5
2.引入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
3.编写Service及其实现类Impl
UserService
package com.study.service; /** * @ClassName UserService * @Description TODO * @Author Jiangnan Cui * @Date 2022/5/31 17:51 * @Version 1.0 */ public interface UserService { void insert(String name); void delete(Integer id); void update(String name); String select(String name); }
UserServiceImpl
package com.study.service; import com.study.annotations.MyAdvice; import org.springframework.stereotype.Service; /** * @ClassName UserServiceImpl * @Description TODO * @Author Jiangnan Cui * @Date 2022/5/31 17:54 * @Version 1.0 */ @Service public class UserServiceImpl implements UserService{ @Override public void insert(String name) { System.out.println("处理insert核心业务逻辑,调用DAO~~"); } @Override public void delete(Integer id) { System.out.println("处理delete核心业务逻辑,调用DAO~~"); } @Override public void update(String name) { System.out.println("处理update核心业务逻辑,调用DAO~~"); } @Override @MyAdvice //在使用基于注解的切入点表达式时自定义的注解 public String select(String name) { System.out.println("处理select核心业务逻辑,调用DAO~~"); return name; } }
5.3 切面使用
# 切面注解 - @Aspect 用来类上,代表这个类是一个切面 Aspect 切面 = Advice 附加操作 + Pointcut 切入点 - @Before 用在方法上代表这个方法是一个前置通知方法 - @After 用在方法上代表这个方法是一个后置通知方法 - @Around 用在方法上代表这个方法是一个环绕的方法 # 切入点表达式: - 1.execution 方法级别切入点表达式 save update 运行效率越低 - 2.within 类级别切入点表达式: 控制越粗 运行效率越高 - 3.基于注解的切入点表达式 @annotation(com.baizhi.annotations.Xxx),需要自定义通知Xxx,且需要YyyServiceImpl业务逻辑方法上添加@Xxx注解,均配置后才可使用
切面配置类
package com.study.config; 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; /** * @ClassName MyAspectConfig * @Description TODO * @Author Jiangnan Cui * @Date 2022/5/31 18:40 * @Version 1.0 */ @Configuration //代表当前这个类是一个spring的配置类,相当于spring.xml @Aspect //代表这是一个切面配置类 public class MyAspectConfig { /** * @MethodName before * @Description 前置通知 * @param: joinPoint * @Author Jiangnan Cui * @Date 2022/6/2 9:22 */ // @Before("execution(* com.study.service.*.*(..))") // public void before(JoinPoint joinPoint){ // System.out.println("前置附加操作"); // System.out.println("当前执行目标类:" + joinPoint.getTarget()); // System.out.println("当前执行目标类中方法:" + joinPoint.getSignature().getName()); // System.out.println("当前执行目标类中方法参数:" + joinPoint.getArgs()); // } /** * @MethodName after * @Description 后置通知 * @param: joinPoint * @Author Jiangnan Cui * @Date 2022/6/2 9:22 */ // @After("within(com.study.service.*))") // public void after(JoinPoint joinPoint){ // System.out.println("后置附加操作"); // System.out.println("当前执行目标类:" + joinPoint.getTarget()); // System.out.println("当前执行目标类中方法:" + joinPoint.getSignature().getName()); // System.out.println("当前执行目标类中方法参数:" + joinPoint.getArgs()); // } /** * @MethodName around * @Description 环绕通知 * @param: proceedingJoinPoint * @return: java.lang.Object 用来将业务方法返回结果给调用者 * @Author Jiangnan Cui * @Date 2022/6/2 9:21 */ //@Around("execution(* com.study.service.*.*(..))") @Around("@annotation(com.study.annotations.MyAdvice)") //仅对业务逻辑方法上含有@MyAdvice注解的方法生效 public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("环绕前置附加操作"); System.out.println("当前执行目标类:" + proceedingJoinPoint.getTarget()); System.out.println("当前执行目标类中方法:" + proceedingJoinPoint.getSignature().getName()); System.out.println("当前执行目标类中方法参数:" + proceedingJoinPoint.getArgs()); //进行目标方法的放行操作,即:继续执行业务逻辑方法 Object proceed = proceedingJoinPoint.proceed(); System.out.println("proceed = " + proceed); System.out.println("环绕后置附加操作"); return proceed; } }
自定义通知注解
package com.study.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @ClassName MyAdvice * @Description TODO * @Author Jiangnan Cui * @Date 2022/6/2 9:33 * @Version 1.0 */ //指定运行时生效 @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface MyAdvice { }
注意:
1.前置通知和后置通知都没有返回值,方法参数都为JointPoint
2.环绕通知有返回值,方法参数为ProceedingJoinPoint,需要抛出异常。一旦执行放行必须将目标方法的返回值返回,否则调用者无法接收返回数据
5.4 测试
BasciTests
package com.study; import org.springframework.boot.test.context.SpringBootTest; /** * @ClassName BasicTests * @Description TODO * @Author Jiangnan Cui * @Date 2022/5/31 19:06 * @Version 1.0 */ @SpringBootTest public class BasicTests{ }
UserServiceImpl
package com.study; import com.study.service.UserService; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; /** * @ClassName UserServiceTest * @Description TODO * @Author Jiangnan Cui * @Date 2022/5/31 19:08 * @Version 1.0 */ public class UserServiceTest extends BasicTests{ @Autowired private UserService userService; @Test public void testInsert(){ userService.insert("小崔"); } @Test public void testDelete(){ userService.delete(1); } @Test public void testUpdate(){ userService.update("加油"); } @Test public void testSelect(){ String name = userService.select("越努力,越幸运"); System.out.println(name); } }
5.5 总结
===================保存用户业务逻辑================= 用户业务 UserService void save(User user) void delete(Integer id); User queryById(Integer id); .... UserServiceImpl implement UserService void save(User user){ sout("=============");//业务功能-1 sout("*************");//业务功能-2 ... userDao.save(user); } void delete(Integer id){ sout("=============");//业务功能-1 sout("*************");//业务功能-2 ...... userDao.delete(id); } User queryById(Integer id){ sout("=============");//业务功能-1 sout("*************");//业务功能-2 ..... return userDao.queryById(id) } =================保存用户业务逻辑================== 加入新的功能: 保存用户之前: 打印输出一句话 "===========" ================用户业务逻辑====================== 加入新的功能: 保存|删除|修改|查询用户之前: 打印输出一句话 "===========" 保存|删除|修改|查询用户之前: 打印输出一句话 "***********" ================================================ 问题: 1.现有业务层开发存在问题 a.-->额外功能代码存在大量冗余? b.-->每个方法都需要书写一遍额外功能代码不利于后续项目维护? Spring 框架 AOP: Aspect(切面) Oriented(面向) Programmaing 面向切面编程 Aspect (切面) = Advice(通知) + 切入点(Pointcut) Advice 通知: 业务逻辑中一些附加操作称之为通知 前置 后置 环绕 Pointcut 切入点: 配置通知应用于项目中那些业务操作 Aspect 切面 = 附加操作(Advice) + 切入点(Pointcut)(配置) 1.类 implement xxAdvice接口 2.XML进行配置 <aop:config> <aop:pointcut id="pc" expresssion="execution(* 包.类.方法名(方法参数))|within(类级别)|@annotation(注解类型)"> <aop:advisor advice-ref="通知类" pointcut-ref="pc"/> </aop:config> SpringBoot框架 现有spring框架 进一步封装 1.无xml配置 一切皆java配置 AOP: 面向切面编程 Aspect 切面 = Advice(通知) + 切入点 1.开发附加操作 Advice 2.配置切入点&组装切面 面向切面编程步骤: 1.引入aop切面编程依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> 2.在springboot项目中新建config(配置)包 @Configuration //修饰范围: 只能用在类上 作用: 代表这是一个spring的配置类 spring.xml @Aspect //修饰范围: 只能用在类上 作用:代表这个类是一个切面类 <aop:config> MyAspectConfig(自定义切面类) 配置类{ //@Before: 代表这个方法是一个前置附加操作 //@After : 代表这个方法是一个后置附加操作 注意: 使用@Before 和 @After注解声明方法上加入一个参数 定义一个参数 JointPoint 连接点 //@Around: 代表这个方法是一个环绕附加操作 value属性: 用来书写切入点表达式 注意: 使用@Around注解 在方法定义时声明一个参数: ProceedingJoinPoint 处理过程中连接点 @Before("execution(* com.baizhi.service.*.*(..))") public void before(){ sout("===========") } }
6. 文件上传
配套视频:【编程不良人】2021年SpringBoot最新最全教程_哔哩哔哩_bilibili
【编程不良人】2021年SpringBoot最新最全教程_哔哩哔哩_bilibili
文件上传:用户访问当前系统,将自己本地计算机中文件通过浏览器上传到当前系统所在的服务器或文件服务器(OSS阿里云对象存储、mino对象存储、七牛云文件存储)的过程。
6.1 思路
a.提供一张上传页面 (此处以jsp页面为例) 提交方式必须:post enctype属性必须为 multipart/form-data(文本类型、二进制类型文件均可做编码),默认的application/x-www-form-urlencoded只能对文本类型(字符串)进行编码 b.开发上传controller
6.2 准备上传页面
upload.jsp
<%@page pageEncoding="UTF-8" contentType="text/html; UTF-8" isELIgnored="false" %> <!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>文件上传</title> </head> <body> <h1>测试文件上传</h1> <form action="${pageContext.request.contextPath}/file/uploadByJarDeploy" method="post" enctype="multipart/form-data"> <input type="file" name="file"> <input type="submit" value="上传文件"> </form> </body> </html>
success.jsp
<%@page pageEncoding="UTF-8" contentType="text/html; UTF-8" isELIgnored="false" %> <!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>文件上传成功</title> </head> <body> <h1>恭喜您,文件上传成功!</h1> </body> </html>
6.3 导入依赖
<!--解析jsp模板依赖--> <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-jasper</artifactId> </dependency>
6.4 编写配置文件
application.yml
server: port: 8888 servlet: context-path: /spring-boot-day5 jsp: # 打开jsp开发模式 init-parameters: development: true # springmvc相关配置 spring: mvc: view: prefix: / # 配置视图前缀 suffix: .jsp # 配置视图后缀 servlet: multipart: # 修改文件上传的大小限制,默认10M(10485760B) max-request-size: 120MB # 运行请求传递文件大小最大为120MB,单位可以直接写MB max-file-size: 120MB # 运行服务器可以处理的最大文件大小为120MB profiles: active: local # 激活本地配置生效 # 调整日志 logging: level: root: info # 默认info级别日志 com.study: debug # 开启com.study包中所有debug日志
application-local.yml
# 指定文件上传位置 file: upload: dir: D:\Software_Development\IDEA_code\SpringBoot\spring-boot-day5\src\main\webapp\upload\local # 指定本地上传测试目录
application-prod.yml
# 指定文件上传位置 file: upload: dir: D:\Software_Development\IDEA_code\SpringBoot\spring-boot-day5\src\main\webapp\upload\prod # 指定生产上传测试目录
6.5 编写控制器
package com.study.controller; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.multipart.MultipartFile; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import java.io.File; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; /** * @ClassName FileController * @Description TODO * @Author Jiangnan Cui * @Date 2022/6/2 17:40 * @Version 1.0 */ @Controller @RequestMapping("file") public class FileController { //创建日志对象 private static final Logger log = LoggerFactory.getLogger(FileController.class); /** * @MethodName upload * @Description 用来测试文件上传,注意此种方式不能用于jar包部署 * 由于这种方式存在局限性,所以现在不推荐使用了 * @param: file 定义接收文件对象multipartFile,file变量名要与form中input type="file"标签name属性名一致 * @param: request * @return: java.lang.String * @Author Jiangnan Cui * @Date 2022/6/2 18:02 */ @RequestMapping("upload") public String upload(MultipartFile file, HttpServletRequest request) throws IOException { //获得原始文件名 String originalFilename = file.getOriginalFilename(); log.debug("文件名:{}", originalFilename); log.debug("文件大小:{}",file.getSize());//以字节为单位 log.debug("文件类型:{}",file.getContentType()); //1.根据相对上传路径"/upload"获取绝对路径(真实路径) 动态获取 // 即:/users/桌面... 或 /home/springboot_day5/... String realPath = request.getSession().getServletContext().getRealPath("/upload"); log.debug("获取绝对路径:{}",realPath); //2.上传文件 //获取文件名后缀 FileNameUtil工具类需要引入jar包 String ext = originalFilename.substring(originalFilename.lastIndexOf(".")); //拼接新文件名 时间戳 SSS毫秒 String newFileName = new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date()) + ext; System.out.println("新文件名:" + newFileName); //上传文件 参数1: 将文件写入到那个目录 参数2:文件名 file.transferTo(new File(realPath, newFileName)); return "redirect:/success.jsp"; } //早期war文件部署到服务器tomcat中,可以解压得到目录 //现在springboot内嵌tomcat,jar包不会解压,找不到目录 //获得文件路径 @Value("${file.upload.dir}") private String realPath; /** * @MethodName uploadByJarDeploy * @Description 用来测试文件上传,这种方式适用于任何一种部署方式,推荐使用这种方式 * @param: file * @return: java.lang.String * @Author Jiangnan Cui * @Date 2022/6/3 19:05 */ @RequestMapping("uploadByJarDeploy") //测试路径:http://localhost:8888/spring-boot-day5/upload.jsp public String uploadByJarDeploy(MultipartFile file) throws IOException { //获得文件原始名 String originalFilename = file.getOriginalFilename(); log.debug("文件名:{}",originalFilename); log.debug("文件大小:{}",file.getSize()); log.debug("文件类型:{}",file.getContentType()); //获取文件后缀 String ext = originalFilename.substring(originalFilename.lastIndexOf(".")); //改名 String newFileName = new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date()) + ext; //上传文件 file.transferTo(new File(realPath,newFileName)); return "redirect:/success.jsp"; } }
注意:
#上传时出现如下异常: 上传文件的大小超出默认配置 默认10M nested exception is java.lang.IllegalStateException: org.apache.tomcat.util.http.fileupload.FileUploadBase$SizeLimitExceededException: the request was rejected because its size (38443713) exceeds the configured maximum (10485760) #修改上传文件大小: spring: http: multipart: max-request-size: 209715200 #用来控制文件上传大小的限制 max-file-size: 209715200 #用来指定服务端最大文件大小,可以直接使用MB spring.servlet.multipart.max-file-size=500MB spring.servlet.multipart.max-request-size=500MB
6.6 项目目录结构
7. 文件下载
配套视频:https://www.bilibili.com/video/BV1Cv411p7cQ?p=31
文件下载:将服务器某个资源文件下载到用户本地计算机过程
7.1 思路
a.确定项目中哪些资源可以被下载 aa.txt 用户须知.doc ..... b.将可以被下载资源放入服务器指定位置、文件上传服务器fastdfs(dfs 分布式文件存储系统 1000个节点 冗余备份)、上传到OSS对象存储 七牛云。。。。 /home/download aa.txt 用户须知.doc ..... /Users/chenyn/Desktop/线上课/项目阶段/01springboot/codes/springboot_day6/download c.项目中开发一个下载页面download.jsp 提供下载文件链接 d.开发下载控制器controller
7.2 新建项目,导入依赖
<!--tomcat解析jsp--> <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-jasper</artifactId> <!--provided表示当前idea环境可用,打包时不参与打包--> <scope>provided</scope> </dependency>
7.3 添加配置信息
server: port: 8888 servlet: context-path: /spring-boot-day6 jsp: init-parameters: development: true spring: mvc: view: prefix: / suffix: .jsp file: download: dir: D:\Software_Development\IDEA_code\SpringBoot\spring-boot-day6\download # 指定下载目录测试环境 logging: level: root: info com.study: debug
7.4 开发jsp页面
<%@page pageEncoding="UTF-8" contentType="text/html; UTF-8" isELIgnored="false" %> <!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>测试文件下载</title> </head> <body> <h1>文件下载页面</h1> <a href="${pageContext.request.contextPath}/file/download?fileName=HELP.md">HELP.md</a> <a href="${pageContext.request.contextPath}/file/download?fileName=readme.txt">readme.txt</a> <a href="${pageContext.request.contextPath}/file/download?fileName=说明.doc">说明.doc</a> <a href="${pageContext.request.contextPath}/file/download?fileName=图片.jpg">图片.jpg</a> </body> </html>
7.5 开发Controller
package com.study.controller; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Controller; import org.springframework.util.FileCopyUtils; import org.springframework.web.bind.annotation.RequestMapping; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletResponse; import java.io.*; import java.net.URLEncoder; /** * @ClassName FileController * @Description TODO * @Author Jiangnan Cui * @Date 2022/6/3 22:59 * @Version 1.0 */ @Controller @RequestMapping("file") public class FileController { //声明日志对象 private static final Logger logger = LoggerFactory.getLogger(FileController.class); //绝对路径 @Value("${file.download.dir}") private String realPath; /** * @MethodName download * @Description 测试文件下载 * @param: fileName * @param: response * @Author Jiangnan Cui * @Date 2022/6/3 23:14 */ @RequestMapping("download") //测试路径:http://localhost:8888/spring-boot-day6/download.jsp public void download(String fileName, HttpServletResponse response) throws IOException { logger.debug("当前下载文件名为:{}",fileName); logger.debug("当前下载文件路径为为:{}",realPath); //1.指定在绝对路径中读取文件 File file = new File(realPath, fileName); //2.将文件读取为文件输入流 FileInputStream fis = new FileInputStream(file); //3.获取响应流之前一定要设置以附件形式下载,否则只能在浏览器进行页面展示;为防止文件名中文乱码,需要指定编码 response.setHeader("content-disposition","attachment;filename="+ URLEncoder.encode(fileName,"UTF-8")); //4.获取响应输出流 ServletOutputStream sos = response.getOutputStream(); //5.将输入流复制给输出流 // int len = 0; // byte[] data = new byte[1024]; // while (true){ // len = fis.read(data); // if(len == -1) // break; // sos.write(data,0,len); // } // fis.close(); //spring工具类 FileCopyUtils.copy(fis,sos); } }