Spring是Java企业版(Java Enterprise Edition,JEE,也称J2EE)的轻量级代替品。无需开发重量级的Enterprise JavaBean(EJB),Spring为企业级Java开发提供了一种相对简单的方法,通过依赖注入和面向切面编程,用简单的Java对象(Plain Old Java Object,POJO)实现了EJB的功能。
虽然Spring的组件代码是轻量级的,但它的配置却是重量级的。一开始,Spring用XML配置,而且是很多XML配置。Spring 2.5引入了基于注解的组件扫描,这消除了大量针对应用程序自身组件的显式XML配置。Spring 3.0引入了基于Java的配置,这是一种类型安全的可重构配置方式,可以代替XML。
所有这些配置都代表了开发时的损耗。因为在思考Spring特性配置和解决业务问题之间需要进行思维切换,所以编写配置挤占了编写应用程序逻辑的时间。和所有框架一样,Spring实用,但与此同时它要求的回报也不少。
除此之外,项目的依赖管理也是一件耗时耗力的事情。在环境搭建时,需要分析要导入哪些库的坐标,而且还需要分析导入与之有依赖关系的其他库的坐标,一旦选错了依赖的版本,随之而来的不兼容问题就会严重阻碍项目的开发进度。
SpringBoot对上述Spring的缺点进行的改善和优化,基于约定优于配置的思想,可以让开发人员不必在配置与逻辑业务之间进行思维的切换,全身心的投入到逻辑业务的代码编写中,从而大大提高了开发的效率,一定程度上缩短了项目周期。
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.0.1.RELEASEversion>
parent>
SpringBoot要集成SpringMVC进行Controller的开发,所以项目要导入web的启动依赖:
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
dependencies>
@SpringBootApplication
public class MySpringBootApplication {
public static void main(String[] args) {
SpringApplication.run(MySpringBootApplication.class);
}
}
@Controller
public class QuickStartController {
@RequestMapping("/quick")
@ResponseBody
public String quick(){
return "springboot 访问成功!";
}
}
执行SpringBoot起步类的主方法,控制台打印日志如下:
通过日志发现,Tomcat started on port(s): 8080 (http) with context path ‘’
tomcat已经起步,端口监听8080,web应用的虚拟工程名称为空
打开浏览器访问url地址为:http://localhost:8080/quick
可以在修改代码后不重启就能生效,在 pom.xml 中添加如下配置就可以实现这样的功能,我们称之为热部署:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
dependency>
注意:IDEA进行SpringBoot热部署失败原因
出现这种情况,并不是热部署配置问题,其根本原因是因为Intellij IEDA默认情况下不会自动编译,需要对IDEA进行自动编译的设置,如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.itheima</groupId>
<artifactId>springboot_quick2</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>springboot_quick2</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>9</java.version>
</properties>
<dependencies>
<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>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
可以使用快速入门的方式创建Controller进行访问,此处不再赘述
按住Ctrl点击pom.xml中的spring-boot-starter-parent,跳转到了spring-boot-starter-parent的pom.xml,xml配置如下(只摘抄了部分重点配置):
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-dependenciesartifactId>
<version>2.0.1.RELEASEversion>
<relativePath>../../spring-boot-dependenciesrelativePath>
parent>
按住Ctrl点击pom.xml中的spring-boot-starter-dependencies,跳转到了spring-boot-starter-dependencies的pom.xml,xml配置如下(只摘抄了部分重点配置):
<properties>
<activemq.version>5.15.3activemq.version>
<antlr2.version>2.7.7antlr2.version>
<appengine-sdk.version>1.9.63appengine-sdk.version>
<artemis.version>2.4.0artemis.version>
<aspectj.version>1.8.13aspectj.version>
<assertj.version>3.9.1assertj.version>
<atomikos.version>4.0.6atomikos.version>
<bitronix.version>2.1.4bitronix.version>
<build-helper-maven-plugin.version>3.0.0build-helper-maven-plugin.version>
<byte-buddy.version>1.7.11byte-buddy.version>
... ... ...
properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-bootartifactId>
<version>2.0.1.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-testartifactId>
<version>2.0.1.RELEASEversion>
dependency>
... ... ...
dependencies>
dependencyManagement>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.jetbrains.kotlingroupId>
<artifactId>kotlin-maven-pluginartifactId>
<version>${kotlin.version}version>
plugin>
<plugin>
<groupId>org.jooqgroupId>
<artifactId>jooq-codegen-mavenartifactId>
<version>${jooq.version}version>
plugin>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
<version>2.0.1.RELEASEversion>
plugin>
... ... ...
plugins>
pluginManagement>
build>
从上面的spring-boot-starter-dependencies的pom.xml中我们可以发现,一部分坐标的版本、依赖管理、插件管理已经定义好,所以我们的SpringBoot工程继承spring-boot-starter-parent后已经具备版本锁定等配置了。所以起步依赖的作用就是进行依赖的传递。
按住Ctrl点击pom.xml中的spring-boot-starter-web,跳转到了spring-boot-starter-web的pom.xml,xml配置如下(只摘抄了部分重点配置):
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-startersartifactId>
<version>2.0.1.RELEASEversion>
parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
<version>2.0.1.RELEASEversion>
<name>Spring Boot Web Startername>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
<version>2.0.1.RELEASEversion>
<scope>compilescope>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-jsonartifactId>
<version>2.0.1.RELEASEversion>
<scope>compilescope>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-tomcatartifactId>
<version>2.0.1.RELEASEversion>
<scope>compilescope>
dependency>
<dependency>
<groupId>org.hibernate.validatorgroupId>
<artifactId>hibernate-validatorartifactId>
<version>6.0.9.Finalversion>
<scope>compilescope>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webartifactId>
<version>5.0.5.RELEASEversion>
<scope>compilescope>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webmvcartifactId>
<version>5.0.5.RELEASEversion>
<scope>compilescope>
dependency>
dependencies>
project>
从上面的spring-boot-starter-web的pom.xml中我们可以发现,spring-boot-starter-web就是将web开发要使用的spring-web、spring-webmvc等坐标进行了“打包”,这样我们的工程只要引入spring-boot-starter-web起步依赖的坐标就可以进行web开发了,同样体现了依赖传递的作用
@SpringBootApplication
public class MySpringBootApplication {
public static void main(String[] args) {
SpringApplication.run(MySpringBootApplication.class);
}
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
/**
* Exclude specific auto-configuration classes such that they will never be applied.
* @return the classes to exclude
*/
@AliasFor(annotation = EnableAutoConfiguration.class)
Class<?>[] exclude() default {};
... ... ...
}
其中有三个核心注解:
按住Ctrl点击查看注解@EnableAutoConfiguration:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
... ... ...
}
其中,@Import(AutoConfigurationImportSelector.class) 导入了AutoConfigurationImportSelector类
按住Ctrl点击查看AutoConfigurationImportSelector源码
public String[] selectImports(AnnotationMetadata annotationMetadata) {
... ... ...
List<String> configurations = getCandidateConfigurations(annotationMetadata,
attributes);
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
return StringUtils.toStringArray(configurations);
}
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
return configurations;
}
其中,SpringFactoriesLoader.loadFactoryNames 方法的作用就是从META-INF/spring.factories文件中读取指定类对应的类名称列表:
该spring.factories文件格式为键值对,键为自动配置类全类名,值(多个),为该自动配置类所对应的配置类的全类名
如果想使用自己的配置替换默认配置的话,就可以使用application.properties或者application.yml(application.yaml)进行配置。
SpringBoot默认会从Resources目录下加载==application.properties或application.yml(application.yaml)==文件
其中,application.properties文件是键值对类型的文件,之前一直在使用,所以此处不在对properties文件的格式进行阐述。除了properties文件外,SpringBoot还可以使用yml文件进行配置,下面对yml文件进行讲解。
# 数据库连接配置
spring.datasource.url=jdbc:mysql://localhost:3306/mydatabase
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
# 数据库连接配置
spring:
datasource:
url: jdbc:mysql://localhost:3306/mydatabase
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
person:
name: zhangsan
age: 18
@Value("${person.name}")
private String name;
@Value("${person.age}")
private Integer age;
优先级1:项目路径下的config文件夹配置文件
优先级2:项目的根目录下面配置文件
优先级3:资源路径下的config文件夹配置文件
优先级4:资源路径下配置文件
当properties、yaml和yml三种文件路径相同时,三个文件中的配置信息都会生效,但是当三个文件中有配置信息冲突时,加载顺序是
优先级低的配置会被先加载,所以优先级高的配置会覆盖优先级低的配置。
properties(最高)> yml > yaml(最低)
当我们把项目打包后,如何在配置SpringBoot项目呢?
项目打包好以后,我们可以使用命令行参数的形式,来改变想改变的几个参数,直接在启动命令后添加启动参数,如果有多个配置项,可以用空格分开。
java -jar springboot-configuration.jar --server.port=8088 --server.servlet.context-path=/spring
在第一种情况下,如果参数数量过多,我们就要考虑配置文件了,我们在启动项目的时候可以用spring.config.location
来指定配置文件的新位置。指定的配置文件和jar包中默认加载的配置文件共同起作用形成互补配置。
指定配置文件从F盘下读取
java -jar springboot-configuration.jar --spring.config.location=F:/application.properties
dev:开发环境
test:测试环境
prod:生产环境(线上)
application-dev.yml
application-test.yml
application-prod.yml
spring:
profiles:
active:dev
不需要创建多个文件来区分了,直接以 三个横杠 来当做一个配置文件环境。
以下案例就是分为了两个环境,然后最上方active来指定对应的profiles环境
spring:
profiles:
active: prod
---
server:
port: 8083
spring:
profiles: dev
---
server:
port: 8084
spring:
profiles: prod
java -jar spring-boot-02-config-0.0.1-SNAPSHOT.jar --spring.profiles.active=dev;
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
@RunWith(SpringRunner.class)
@SpringBootTest(classes = MySpringBootApplication.class)
public class MapperTest {
@Autowired
private UserMapper userMapper;
@Test
public void test() {
List<User> users = userMapper.queryUserList();
System.out.println(users);
}
}
其中,SpringRunner继承自SpringJUnit4ClassRunner,使用哪一个Spring提供的测试测试引擎都可以
public final class SpringRunner extends SpringJUnit4ClassRunner
@SpringBootTest的属性指定的是引导类的字节码对象
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>1.1.1version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
spring:
datasource:
url: jdbc:mysql://localhost:3306/mydatabase
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
在test数据库中创建user表
-- ----------------------------
-- Table structure for `user`
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(50) DEFAULT NULL,
`password` varchar(50) DEFAULT NULL,
`name` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES ('1', 'zhangsan', '123', '张三');
INSERT INTO `user` VALUES ('2', 'lisi', '123', '李四');
public class User {
// 主键
private Long id;
// 用户名
private String username;
// 密码
private String password;
// 姓名
private String name;
//此处省略getter和setter方法 .. ..
}
@Mapper
public interface UserMapper {
public List<User> queryUserList();
}
DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.itheima.mapper.UserMapper">
<select id="queryUserList" resultType="user">
select * from user
select>
mapper>
#spring集成Mybatis环境
#pojo别名扫描包
mybatis.type-aliases-package=com.itheima.domain
#加载Mybatis映射文件
mybatis.mapper-locations=classpath:mapper/*Mapper.xml
@Controller
public class MapperController {
@Autowired
private UserMapper userMapper;
@RequestMapping("/queryUser")
@ResponseBody
public List<User> queryUser(){
List<User> users = userMapper.queryUserList();
return users;
}
}
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.1.0version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
server:
port: 10100 # 配置启动端口号
mybatis:
config-location: classpath:mybatis.cfg.xml # mybatis主配置文件所在路径
type-aliases-package: com.demo.drools.entity # 定义所有操作类的别名所在包
mapper-locations: # 所有的mapper映射文件
- classpath:mapper/*.xml
spring: #springboot的配置
datasource: #定义数据源
#127.0.0.1为本机测试的ip,3306是mysql的端口号。serverTimezone是定义时区,照抄就好,mysql高版本需要定义这些东西
#useSSL也是某些高版本mysql需要问有没有用SSL连接
url: jdbc:mysql://127.0.0.1:3306/test?serverTimezone=GMT%2B8&useSSL=FALSE
username: root #数据库用户名,root为管理员
password: 123456 #该数据库用户的密码
# 使用druid数据源
type: com.alibaba.druid.pool.DruidDataSource
# mybatis-plus相关配置
mybatis-plus:
# xml扫描,多个目录用逗号或者分号分隔(告诉 Mapper 所对应的 XML 文件位置)
mapper-locations: classpath:mapper/*.xml
# 以下配置均有默认值,可以不设置
global-config:
db-config:
#主键类型 AUTO:"数据库ID自增" INPUT:"用户输入ID",ID_WORKER:"全局唯一ID (数字类型唯一ID)", UUID:"全局唯一ID UUID";
id-type: auto
#字段策略 IGNORED:"忽略判断" NOT_NULL:"非 NULL 判断") NOT_EMPTY:"非空判断"
field-strategy: NOT_EMPTY
#数据库类型
db-type: MYSQL
configuration:
# 是否开启自动驼峰命名规则映射:从数据库列名到Java属性驼峰命名的类似映射
map-underscore-to-camel-case: true
# 如果查询结果中包含空值的列,则 MyBatis 在映射的时候,不会映射这个字段
call-setters-on-nulls: true
# 这个配置会将执行的sql打印出来,在开发或测试的时候可以用
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
@Data
@TableName("user_info")//@TableName中的值对应着表名
public class UserInfoEntity {
/**
* 主键
* @TableId中可以决定主键的类型,不写会采取默认值,默认值可以在yml中配置
* AUTO: 数据库ID自增
* INPUT: 用户输入ID
* ID_WORKER: 全局唯一ID,Long类型的主键
* ID_WORKER_STR: 字符串全局唯一ID
* UUID: 全局唯一ID,UUID类型的主键
* NONE: 该类型为未设置主键类型
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 姓名
*/
private String name;
/**
* 年龄
*/
private Integer age;
/**
* 技能
*/
private String skill;
/**
* 评价
*/
private String evaluate;
/**
* 分数
*/
private Long fraction;
}
public class MybatisPlusConfig {
/**
* mybatis-plus SQL执行效率插件【生产环境可以关闭】
*/
@Bean
public PerformanceInterceptor performanceInterceptor() {
return new PerformanceInterceptor();
}
/**
* 分页插件
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
}
@SpringBootApplication
//@MapperScan和dao层添加@Mapper注解意思一样
@MapperScan(basePackages = "com.demo.drools.dao")
public class DroolsApplication {
public static void main(String[] args) {
SpringApplication.run(DroolsApplication.class, args);
}
}
@Mapper
public interface UserInfoDao extends BaseMapper<UserInfoEntity> {
}
public interface UserInfoService extends IService<UserInfoEntity> {
}
@Service
public class UserInfoSerivceImpl extends ServiceImpl<UserInfoDao, UserInfoEntity> implements UserInfoService {
}
Mybatis Plus的核心为QueryWrapper、UpdateWrapper
拦截器(Interceptor)同 Filter 过滤器一样,它俩都是面向切面编程——AOP 的具体实现(AOP切面编程只是一种编程思想而已)。
你可以使用 Interceptor 来执行某些任务,例如在 Controller 处理请求之前编写日志,添加或更新配置…
在 Spring中,当请求发送到 Controller 时,在被Controller处理之前,它必须经过 Interceptors(0或多个)。
如果你需要自定义 Interceptor 的话必须实现 org.springframework.web.servlet.HandlerInterceptor接口或继承 org.springframework.web.servlet.handler.HandlerInterceptorAdapter类,并且需要重写下面下面 3 个方法:
public class LogInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
long startTime = System.currentTimeMillis();
System.out.println("\n-------- LogInterception.preHandle --- ");
System.out.println("Request URL: " + request.getRequestURL());
System.out.println("Start Time: " + System.currentTimeMillis());
request.setAttribute("startTime", startTime);
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("\n-------- LogInterception.postHandle --- ");
System.out.println("Request URL: " + request.getRequestURL());
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("\n-------- LogInterception.afterCompletion --- ");
long startTime = (Long) request.getAttribute("startTime");
long endTime = System.currentTimeMillis();
System.out.println("Request URL: " + request.getRequestURL());
System.out.println("End Time: " + endTime);
System.out.println("Time Taken: " + (endTime - startTime));
}
}
public class OldLoginInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("\n-------- OldLoginInterceptor.preHandle --- ");
System.out.println("Request URL: " + request.getRequestURL());
System.out.println("Sorry! This URL is no longer used, Redirect to /admin/login");
response.sendRedirect(request.getContextPath()+ "/admin/login");
return false;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("\n-------- OldLoginInterceptor.postHandle --- ");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("\n-------- OldLoginInterceptor.afterCompletion --- ");
}
}
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LogInterceptor());
registry.addInterceptor(new OldLoginInterceptor()).addPathPatterns("/admin/oldLogin");
registry.addInterceptor(new AdminInterceptor()).addPathPatterns("/admin/*").excludePathPatterns("/admin/oldLogin");
}
}
如记录一下请求的处理时间,得到一些慢请求(如处理时间超过500毫秒),从而进行性能改进,一般的反向代理服务器如 apache 都具有这个功能,但此处我们演示一下使用拦截器怎么实现。
实现分析
问题:
我们的拦截器是单例,因此不管用户请求多少次都只有一个拦截器实现,即 线程不安全,那我们应该怎么记录时间呢?
解决方案是使用 ThreadLocal,它是线程绑定的变量,提供线程局部变量(一个线程一个 ThreadLocal,A线程的ThreadLocal 只能看到A线程的 ThreadLocal,不能看到B线程的 ThreadLocal)。
代码实现:
public class StopWatchHandlerInterceptor extends HandlerInterceptorAdapter {
private NamedThreadLocal<Long> startTimeThreadLocal = new NamedThreadLocal<>("StopWatch-StartTime");
private Logger logger = LoggerFactory.getLogger(StopWatchHandlerInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
long beginTime = System.currentTimeMillis();//1、开始时间
startTimeThreadLocal.set(beginTime);//线程绑定变量(该数据只有当前请求的线程可见)
return true;//继续流程
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
long endTime = System.currentTimeMillis();//2、结束时间
long beginTime = startTimeThreadLocal.get();//得到线程绑定的局部变量(开始时间)
long consumeTime = endTime - beginTime;//3、消耗的时间
if(consumeTime > 500) {//此处认为处理时间超过500毫秒的请求为慢请求
//TODO 记录到日志文件
logger.info(String.format("%s consume %d millis", request.getRequestURI(), consumeTime));
}
//测试的时候由于请求时间未超过500,所以启用该代码
// logger.info(String.format("%s consume %d millis", request.getRequestURI(), consumeTime));
}
}
NamedThreadLocal:Spring提供的一个命名的ThreadLocal实现。
在测试时需要把 stopWatchHandlerInterceptor 放在拦截器链的第一个,这样得到的时间才是比较准确的。
拦截器配置类
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new StopWatchHandlerInterceptor());
registry.addInterceptor(new OldLoginInterceptor()).addPathPatterns("/admin/oldLogin");
}
}
在访问某些资源时(如订单页面),需要用户登录后才能查看,因此需要进行登录检测。
流程
代码实现
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
boolean flag = true;
String ip = request.getRemoteAddr();
long startTime = System.currentTimeMillis();
request.setAttribute("requestStartTime", startTime);
if (handler instanceof ResourceHttpRequestHandler) {
System.out.println("preHandle这是一个静态资源方法!");
} else if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
System.out.println("用户:" + ip + ",访问目标:" + method.getDeclaringClass().getName() + "." + method.getName());
}
//如果用户未登录
User user = (User) request.getSession().getAttribute("user");
if (null == user) {
//重定向到登录页面
response.sendRedirect("toLogin");
flag = false;
}
return flag;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
if (handler instanceof ResourceHttpRequestHandler) {
System.out.println("postHandle这是一个静态资源方法!");
} else if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
long startTime = (long) request.getAttribute("requestStartTime");
long endTime = System.currentTimeMillis();
long executeTime = endTime - startTime;
int time = 1000;
//打印方法执行时间
if (executeTime > time) {
System.out.println("[" + method.getDeclaringClass().getName() + "." + method.getName() + "] 执行耗时 : "
+ executeTime + "ms");
} else {
System.out.println("[" + method.getDeclaringClass().getSimpleName() + "." + method.getName() + "] 执行耗时 : "
+ executeTime + "ms");
}
}
}
}
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>commons-iogroupId>
<artifactId>commons-ioartifactId>
<version>2.4version>
dependency>
server:
port: 18080
spring:
servlet:
multipart:
max-file-size: 10MB
max-request-size: 10MB
@PostMapping
注解指定处理POST请求的方法,并使用@RequestParam("file") MultipartFile file
参数接收上传的文件。// 创建文件上传控制器
@RestController
@RequestMapping("/api/upload")
public class FileUploadController {
// 处理文件上传请求的POST方法
@PostMapping("/")
public ResponseEntity<String> handleFileUpload(@RequestParam("file") MultipartFile file) {
try {
// 获取上传文件的文件名
String fileName = file.getOriginalFilename();
// 将文件保存到磁盘或执行其他操作,这里只是简单地将文件保存到静态资源目录下
file.transferTo(new File("D:/" + fileName));
return new ResponseEntity<>("文件上传成功!", HttpStatus.OK);
} catch (Exception e) {
return new ResponseEntity<>("文件上传失败:" + e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}
src/main/resources
目录下创建一个名为static
的文件夹,并在其中创建一个名为files
的文件夹,用于存放要下载的文件。@RestController
@RequestMapping("/api/download")
public class FileDownloadController {
private static final Logger log = LoggerFactory.getLogger(FileDownloadController.class);
@Autowired
private ResourceLoader resourceLoader;
// 处理文件下载请求的GET方法,通过文件名获取文件并返回给客户端下载
@GetMapping("/{filename:.+}")
public ResponseEntity<Resource> handleFileDownload(@PathVariable String filename) throws IOException {
// 获取要下载的文件的Resource对象,这里假设文件保存在静态资源目录下的files文件夹中
Resource resource = resourceLoader.getResource("classpath:static/files/" + filename);
if (resource == null) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
// 将文件内容包装为响应体,并设置响应头信息,提示浏览器下载文件而不是打开文件
InputStreamResource inputStreamResource = new InputStreamResource(resource.getInputStream());
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + filename);
return ResponseEntity.ok()
.headers(headers)
.contentType(MediaTypeFactory.getMediaType(resource).get())
.body(inputStreamResource);
}
}
静态资源,一般是网页端的:HTML文件、JavaScript文件和图片。尤其是设置图片的静态资源,尤其重要:
application.yml
/application.properties
内配置。Configuration配置类
。以上两种方法,均可实现用户访问网址,不走Controller层的拦截,直接进行静态文件访问。
spring.mvc.static-path-pattern
:根据官网的描述和实际效果,可以理解为**静态文件URL匹配头**,也就是静态文件的URL地址开头。Springboot默认为:/**
。
spring.web.resources.static-locations
:根据官网的描述和实际效果,可以理解为==实际静态文件地址==,也就是静态文件URL后,匹配的实际静态文件。
Springboot默认为:
classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/
注意:
最终效果:
浏览器输入:http://localhost:8088/SystemData/UserData/Avatar/Mintimate.jpeg
可以直接访问项目文件下的:/SystemData/UserData/Avatar/Mintimate.jpeg
配置文件:
spring:
mvc:
# URL响应地址(Springboot默认为/**)
static-path-pattern: /SystemData/**
web:
resources:
# 静态文件地址,保留官方内容后,进行追加
static-locations: classpath:/static,classpath:/public,classpath:/resources,classpath:/META-INF/resources,file:SystemData
其中,file:SystemData
就是映射本地文件了。
spring.mvc.static-path-pattern
配置只能写一项/SystemData/**
为URL匹配,就不能设置第二个/resources/**
这样的配置为第二静态目录设置配置类方法
方法。写一个配置类,实现静态资源的文件夹方法很多。比如:
继承于WebMvcConfigurationSupport
父类,并实现addResourceHandlers
方法。
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
}
这里的
registry
使用链式编程,方法为:
addResourceHandler
:添加URL响应地址目录。addResourceLocations
:添加实际资源目录。
引用WebMvcConfigurer
接口,并实现addInterceptors
方法(常用)
一些文章可能会让你继承于
WebMvcConfigurerAdapter
方法,但是实际上WebMvcConfigurerAdapter
方法在Spring5.0和Springboot2.0之后,已经弃用。
最终效果1:
http://localhost:8088/SystemData/UserData/Avatar/Mintimate.jpeg
/SystemData/UserData/Avatar/Mintimate.jpeg
,最终效果2:
http://localhost:8088/SystemDataTest/UserData/Avatar/Mintimate.jpeg
/Test/UserData/Avatar/Demo.jpeg
,添加一个配置类,并继承WebMvcConfigurationSupport
,实现addResourceHandlers
方法,并打上@Configuration
注解,使其成为配置类:
@Configuration
public class WebConfig extends WebMvcConfigurationSupport{
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
//定位到项目文件夹下的SystemData文件夹
static final String IMG_PATH=System.getProperty("user.dir")+"/SystemData/";
static final String IMG_PATH_TWO=System.getProperty("user.dir")+"/Test/";
registry.addResourceHandler("/SystemData/**)")
.addResourceLocations("file:"IMG_PATH);
registry.addResourceHandler("/SystemDataTest/**)")
.addResourceLocations("file:"IMG_PATH_TWO);
super.addResourceHandlers(registry);
}
}