文章目录
SpringBoot2的核心功能
配置文件
文件类型
- properties
- yaml
非常适合用来做以数据为中心的配置文件- key:value 【kv之间有空格】
- 大小写敏感
- 使用缩进表示层级关系
- 缩进不允许使用tab,只允许空格
- 缩进的格数不重要,但是要左对齐
- ‘#’表示注释
- ‘’和“”表示字符串内容, 会被转义/不转义
区别一下两种不同文件类型,编写配置文件的差异
首先一个Person类
public class Person {
private String userName;
private Boolean boss;
private Date birth;
private Integer age;
private Pet pet;
private String[] interests;
private List<String> animal;
private Map<String,Object> score;
private Set<Double> salarys;
private Map<String,List<Pet>> allPets;
}
对于properties文件类型的绑定
在这个类的头上就要绑定其咋写这个配置文件
@ConfigurationProperties(prefix = "person")
然后在application.properties下就可以自定义这个类的属性值了
person.userName=lee
person.age=18
对于yaml文件类型的绑定
在resource下创建一个文件类型为application.yaml 或者application.yml
然后在需要的类型的头上写
@ConfigurationProperties(prefix = "person")
@Component
那么在yml文件下定义属性值
person:
userName: zhangsan
boss: true
birth: 2019/12/9
age: 18
interests:
- 篮球
- 足球
- 18
animal: [阿猫,阿狗]
score: {english:80,math:90}
salarys:
- 9999
- 9999.9
- 9999.9
pet:
name: 阿狗
weight: 99.99
allPets:
sick:
- {name: 阿狗,weight: 10}
- name: 阿猫
weight: 12
health:
- {name: 阿虫,weight: 10}
小结:一定要注意左对齐和空格的问题!
而且存在配置文件优先级
properties>yml>yaml
因此,在yaml写的东西如果在properties中重名了,这个属性值会被properties的属性值覆盖
配置文件属性提示依赖
添加这个依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
以后配置文件中就会有提示
但是这个一般都在Spring initial 的时候就可以直接配置好
web场景开发
1.静态资源访问
访问方式:http://localhost:8080/资源名
那么资源存放在哪里?这四个目录都是默认的静态资源存放位置
原理:
为什么存放在这里的静态资源局能够直接被访问到呢?
测试,如果在controller里加入一个requestMapping动态请求
@RestController
public class HelloController {
@RequestMapping("/bug.png")
public String hello(){
return "aaaa";
}
}
发现请求走的是aaaa
那么这个资源的执行流程就是:
url请求进来,先去找controller能不能去处理,不能处理的所有请求又都交给静态资源处理器,如果都不能找到的话几句404
2.静态资源访问前缀
默认是无前缀的
那么如果需要自定义资源文件夹res的话,就需要在配置文件中配置,配置如下
spring:
mvc:
static-path-pattern: /res/**
那么现在配置后应该咋访问到静态资源呢?
静态资源 = 当前项目 + static-path-pattern + 静态资源名
- 网页首页放在static目录下
- 网页小图标 favicon.ico
3.静态资源配置原理,没看懂
配置这个类的条件分析
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class})
// 如果没有这个support下面的才生效
@ConditionalOnMissingBean({WebMvcConfigurationSupport.class})
@AutoConfigureOrder(-2147483638)
//要等后面三个配完之后才能配当前这个类
@AutoConfigureAfter({DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class})
public class WebMvcAutoConfiguration {}
那么在这个类配置了哪些内容呢?
HandlerMapping: 处理器映射,保存了每一个Handler能够处理哪些请求
4.请求参数处理
表单提交rest原理
#开启页面表单的Rest功能
spring:
mvc:
hiddenmethod:
filter:
enabled: true
在表单提交的时候带_method的参数 = PUT
发送请求后,会被filter拦截
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
HttpServletRequest requestToUse = request;
// 1.先要判断表单提交的方式是不是POST,然后看当前请求是否有什么错误
if ("POST".equals(request.getMethod()) && request.getAttribute("javax.servlet.error.exception") == null) {
String paramValue = request.getParameter(this.methodParam);
if (StringUtils.hasLength(paramValue)) {
//2.将请求的参数值转换成大写
String method = paramValue.toUpperCase(Locale.ENGLISH);
//3.转换为大写后,看是不是系统允许的请求
if (ALLOWED_METHODS.contains(method)) {
//4.经过一个wrapper,然后将原生的请求包装成了rest的请求
requestToUse = new HiddenHttpMethodFilter.HttpMethodRequestWrapper(request, method);
}
}
}
filterChain.doFilter((ServletRequest)requestToUse, response);
}
这里就是第3步兼容的请求
static {
ALLOWED_METHODS = Collections.unmodifiableList(Arrays.asList(
HttpMethod.PUT.name(),
HttpMethod.DELETE.name(),
HttpMethod.PATCH.name()));
}
第4的Wrapper, 将一个请求,和一个新的请求方式(put,delete,patch)作为参数放到了HttpMethodRequestWrapper方法中,方法的作用就是重新包装了这个method
private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper {
private final String method;
public HttpMethodRequestWrapper(HttpServletRequest request, String method) {
super(request);
this.method = method;
}
public String getMethod() {
return this.method;
}
}
而且也能看到包装类HttpMethodRequestWrapper最本质都是实现了ServletR equest接口
然后过滤器在放行的时候用wrapper
而且为了避免这些麻烦,都提供了更为直接的注释,直接使用即可
5.使用
访问后得到的值
6.请求转发
@Controller
public class RequestController {
@GetMapping("/goto")
public String goToPage(HttpServletRequest request){
request.setAttribute("msg","123133");
request.setAttribute("code",200);
return "forward:/success";
}
@ResponseBody
@GetMapping("/success")
public Map success(@RequestAttribute("msg") String msg,
@RequestAttribute("code")Integer code,
HttpServletRequest request){
Object msg1 = request.getAttribute("msg");
Map<String,Object> map = new HashMap<>();
map.put("RequestAttribute_msg",msg);
map.put("getAttribute_msg",msg1);
return map;
}
}
走的goto,但是请求转发到了success
@requestbody的作用是在当前对象获取整个http请求的body里面的所有数据
@ResponseBody的作用其实是将java对象转为json格式的数据,返回结果直接写入 HTTP response body 中,一般在异步获取数据时使用【也就是AJAX】
因此在这里将goto中的对象转换成了json数据,然后success再获取到responseBody中的信息
7.矩阵变量
面试题:如果页面被禁用了cookie,session里的内容怎么使用?
因为浏览器访问过程是,每次请求携带cookie,而cookie中有jsession,这个jsession能够使得获取session中的内容。
那么禁用了的话,通过url重写,把cookie的值使用矩阵变量的方式传递
而矩阵变量需要手动开启组件 false
矩阵变量必须有url路径变量才能被解析
视图解析
thymeleaf
1.引入thmeleaf-starter
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
web应用开发
1.可能开发使用的网页添加到静态资源static
2.要用的模版放到template里
为啥要放到template里,是因为thymeleaf配置的时候,默认找到文件的前缀就在template下
有可能在上一个项目8080端口还在被用没有terminate
解决方法:
- 要么改变当前项目的端口
- 要么kill掉进程
lsof -i:8080
kill 47454
构建一个main主页,和一个login登陆页
表单如果转发的话,刷新后又会重复提交页面,出现类似信息
因此可以通过用一个中间商main来重定向,这样就能够避免post的表单重复提交
@Controller
public class IndexController {
@GetMapping({"/","/login"})
public String loginPage(){
return "login";
}
@PostMapping("/login")
public String main(String name,String password){
return "redirect:/main.html";
}
@GetMapping("/main.html")
public String mainPage(){
return "main";
}
}
那么还有个问题 如何给一个用户,创建一个session呢?
@Controller
public class IndexController {
/**
* 来登陆页面
* @return
*/
@GetMapping({"/","/login"})
public String loginPage(){
return "login";
}
@PostMapping("/login")
public String main(User user, HttpSession session, Model model){
if(StringUtils.hasLength(user.getUserName()) && ("123456").equals(user.getPassword())){
session.setAttribute("loginUser",user);
return "redirect:/main.html";
}else {
model.addAttribute("msg","账号密码错误");
return "login";
}
}
@GetMapping("/main.html")
public String mainPage(HttpSession session,Model model){
//是否判断
Object loginUser = session.getAttribute("loginUser");
if(loginUser!=null){
return "main";
}else {
model.addAttribute("msg","请重新登陆");
return "login";
}
}
}
这个msg就是通过model传到视图解析上的,也就是html页面上的内容,因此在login.html下添加一个提示框
<label style="color: red" th:text="${msg}"></label>
如实现
或者session过期了
在html中获取存到session中的值
[[${session.loginUser.user}]]
然后继续对于主页中的表格进行处理
一定要添加这个,静态资源才能在模版文件夹中去找,否则页面找不到。
<html lang="en" xmlns:th="http://www.thymeleaf.org">
如果用id的形式
<div id="commonscript">
<script th:src="@{/js/jquery-1.10.2.min.js}"></script>
<script th:src="@{/js/jquery-ui-1.9.2.custom.min.js}"></script>
<script th:src="@{/js/jquery-migrate-1.2.1.min.js}"></script>
<script th:src="@{/js/bootstrap.min.js}"></script>
<script th:src="@{/js/modernizr.min.js}"></script>
<script th:src="@{/js/jquery.nicescroll.js}"></script>
</div>
那么在用模版引擎引入公共部分的话,就用#选择器
<div th:include="common::#commonscript"></div>
如果用th:fragment的形式
<div id="leftmenu" class="left-side sticky-left-side">
...
</div>
那么就引入方式就如下
<link th:include="common::commonheader">
拦截器HandlerInterceptor
使用该接口需要实现三个方法
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return HandlerInterceptor.super.preHandle(request, response, handler);
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
那么对于当前网页管理系统,用户应该完成了登陆界面才可以访问主页
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//登陆检查逻辑
HttpSession session = request.getSession();
Object loginUser = session.getAttribute("loginUser");
if(loginUser!=null){
return true;
}
return false;
}
该拦截器就判断了是否登陆了,那么判断完是否登陆了就需要交给config进行设置,成功了干啥,不成功干啥,这下面这段就代表着,向registry加入了一个拦截器,下面为放行的规定,当拦截器返回值为假的时候(代表没有session存在):拦截所有,除了login /
@Configuration
public class AdminWebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
//代表除了/ /login 这些登陆页的访问方式可以放行,其他的都不行
//只要当登陆的情况下,session!= null的时候拦截器才放行
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**") //静态资源所有请求都会被拦截
.excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**","/js/**"); //放行的请求
}
}
配置连接数据库
- 使用Hikari
步骤:只需要添加关于数据库的配置即可
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=GMT
username: root
password:
# type: com.zaxxer.hikari.HikariDataSource
jdbc:
template:
query-timeout: 3
测试使用:
@Slf4j
@SpringBootTest
class Boot03ApplicationTests {
@Autowired
JdbcTemplate jdbcTemplate;
@Test
void contextLoads() {
Long aLong = jdbcTemplate.queryForObject("select count(*) from user", Long.class);
log.info("记录总数" + aLong);
}
}
- 使用druid
添加依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.6</version>
</dependency>
在以前用Spring配置数据源的原生办法
给容器中放一个dataSource,而这些信息都在这个配置中配好
但是在SpringBoot中,由于DataSourceAutoConfiguration这个类
默认的自动配置是:判断没有hikari才会配置默认数据源
@ConditionalOnMissingBean({DataSource.class, XADataSource.class})
那么现在如果想自己配置自定义的数据源,就可以创建一个配置类,这个配置类里面的内容就是自定义数据源的配置信息
public class MyDateSourceConfig {
@ConfigurationProperties("spring.datasource")
@Bean
public DataSource dataSource(){
DruidDataSource druidDataSource = new DruidDataSource();
return druidDataSource;
}
}
引入官方配置
- 配置类
- 系统中存在数据源才进行配置
- 该类配置在DataSourceAutoConfiguration之前
- 而这个数据源自动配置默认是hikari数据源
- 因此以自定义数据源优先,这样就把官方的取缔掉了
- 使配置文件生效
- 在DruidStatProperties是和prefix="spring.datasource.druid"的配置文件绑定
- 在DataSourceProperties是和prefix = "spring.datasource"绑定的
- 引入这些类
- DruidSpringAopConfiguration.class ,监控springbean的
- DruidStatViewServletConfiguration.class 监控页的配置spring.datasource.druid.stat-view-servlet.enabled = true
- DruidWebStatFilterConfiguration.class web监控配置spring.datasource.druid.stat-view-servlet.enabled= true
- DruidFilterConfiguration.class spring.datasource.druid.stat-view-servlet.enabled = true,里面还包括了很多组件比如编码,log4j
@Configuration
@ConditionalOnClass({DruidDataSource.class})
@AutoConfigureBefore({DataSourceAutoConfiguration.class})
@EnableConfigurationProperties({DruidStatProperties.class, DataSourceProperties.class})
@Import({DruidSpringAopConfiguration.class, DruidStatViewServletConfiguration.class, DruidWebStatFilterConfiguration.class, DruidFilterConfiguration.class})
public class DruidDataSourceAutoConfigure {
private static final Logger LOGGER = LoggerFactory.getLogger(DruidDataSourceAutoConfigure.class);
public DruidDataSourceAutoConfigure() {
}
@Bean(
initMethod = "init"
)
@ConditionalOnMissingBean
public DataSource dataSource() {
LOGGER.info("Init DruidDataSource");
return new DruidDataSourceWrapper();
}
}
通过配置文件进行druid数据源的配置,根据官方文档就可以进行配置
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=GMT
username: root
password: 19971231
druid:
aop-patterns: com.lee.boo3.* #监控SpringBean
filters: stat,wall # 底层开启功能,stat(sql监控),wall(防火墙)
stat-view-servlet: # 配置监控页功能
enabled: true
login-username: admin
login-password: admin
resetEnable: false
web-stat-filter: # 监控web
enabled: true
urlPattern: /*
exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*'
filter:
stat: # 对上面filters里面的stat的详细配置
slow-sql-millis: 1000
logSlowSql: true
enabled: true
wall:
enabled: true
config:
drop-table-allow: false
整合Mybatis,配置文件版
添加依赖
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
如果使用之前的配置文件方式
- SqlSessionFactoryBuilder
- SqlSessionFactory
- SqlSession
- Mapper
- sql语句
使用starter配置
- 配置类
- 要保证配置mybatis的时候有SqlSessionFactory和SqlSessionFactoryBean这两个类
- 当前容器中只有一个数据源
- MyBatis的配置绑定类,其中包含了需要配置的信息
@Configuration
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties({MybatisProperties.class})
@AutoConfigureAfter({DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class})
public MybatisAutoConfiguration{}
配置项就和mybatis开头的所有信息绑定
MybatisProperties
@ConfigurationProperties(
prefix = "mybatis"
)
public class MybatisProperties {
public static final String MYBATIS_PREFIX = "mybatis";
private static final ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
private String configLocation;
private String[] mapperLocations;
private String typeAliasesPackage;
private Class<?> typeAliasesSuperType;
private String typeHandlersPackage;
private boolean checkConfigLocation = false;
private ExecutorType executorType;
private Class<? extends LanguageDriver> defaultScriptingLanguageDriver;
private Properties configurationProperties;
@NestedConfigurationProperty
private Configuration configuration;
...
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
factory.setDataSource(dataSource);
}
@Bean
@ConditionalOnMissingBean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
ExecutorType executorType = this.properties.getExecutorType();
return executorType != null ? new SqlSessionTemplate(sqlSessionFactory, executorType) : new SqlSessionTemplate(sqlSessionFactory);
}
配置流程
- 创建mybatis-config配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 由于Spring Boot自动配置缘故,此处不必配置,只用来做做样。-->
</configuration>
- 在配置信息中加入mybatis配置文件的位置,以及mapper的位置
mybatis:
config-location: classpath:mybatis/mybatis-config.xml
# 全局配置文件位置,其实可以不写,因为如果后面要写关于mybatis的一些其他配置信息,会报错,因为configuration和config- location不能同时存在,所有配置信息都会放在configuration下
mapper-locations: classpath:mybatis/mapper/*.xml #sql映射文件的位置
- 声明Mapper接口
@Mapper
public interface UserMapper {
public User getUser(int id);
}
- 绑定映射xml
<?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.lee.boot03.mapper.UserMapper">
<select id="getUser" resultType="com.lee.boot03.bean.User">
select * from user where id=#{id}
</select>
</mapper>
5.数据库提取的对象pojo
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int id;
private String name;
private String pwd;
}
6.service层提供一个操作数据库相关方法
@Service
public class UserService {
@Autowired
UserMapper userMapper;
public User c(int id){
return userMapper.getUser(id);
}
}
7.controller层再通过service去调用方法
@Autowired
UserService userService;
@GetMapping("/user")
public User getById(@RequestParam("id") int id){
return userService.getUserById(id);
}
总结
- 导入mybatis官方的starter
- 编写mapper接口(标注@mapper)
- 编写sqlmapper文件,并绑定mapper接口
- application.yaml中指定mapper文件的位置,以及全局配置文件信息
整合mybatis,注解版
创建表
use mybatis;
CREATE TABLE city(
id int(11) primary key AUTO_INCREMENT,
name varchar(30),
state varchar(30),
country varchar(30)
);
- pojo city
略 - dao接口
@Mapper
public interface CityMapper {
@Select("select * from city where id=#{id}")
public City getById(int id);
}
- service
@Service
public class CityService {
@Autowired
CityMapper cityMapper;
public City getCityById(int id){
return cityMapper.getById(id);
}
}
- controller使用
@Autowired
CityService cityService;
@ResponseBody
@GetMapping("/city")
public City getCityById(@RequestParam("id") int id){
return cityService.getCityById(id);
}
因为有的时候 对于处理复杂的数据库关系,比如一对多多对一 还是用xml的方式比较好,因此需要结合在一起使用
所以可以在cityMapper接口写一些简单的数据库逻辑,例如上述的
同时在citymapper.xml配置文件中写一些复杂的逻辑时,简单的写在注解上,复杂的用xml来写
使用@MapperScan(“com.lun.boot.mapper”) 简化,Mapper接口就可以不用标注@Mapper注解。
mybatis plus 整合
use mybatis;
create table user(
id bigint(20) not null comment'主键ID',
name varchar(30) null default null comment '姓名',
age int(11) null default null comment'年龄',
email varchar(50) null default null comment '邮箱',
primary key(id)
);
insert into user(id,name,age,email) values
(1,'Jone',18,'test1@baomiidou.com'),
(2,'Jack',20,'test2@baomiidou.com'),
(3,'Tom',28,'test3@baomiidou.com'),
(4,'Sandy',21,'test4@baomiidou.com'),
(5,'Billie',24,'test5@baomiidou.com');
添加依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
自动配置原理
- 配置类
- 存在这些类吗?
- 是唯一的数据源吗?
- 绑定的配置信息的配置类,前缀为mybatis-plus
- 在数据源和 driver配好了的环境下才开始mybatisplus的自动配置
@Configuration
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties({MybatisPlusProperties.class})
@AutoConfigureAfter({DataSourceAutoConfiguration.class, MybatisPlusLanguageDriverAutoConfiguration.class})
public class MybatisPlusAutoConfiguration implements InitializingBean {}
自动配置类中
- sqlSessionFactory 自动配置好
- mapperLocations 自动配置好 classpath*:/mapper/**/*.xml,也就是任意包的类路径下的所有mapper文件夹下的所有xml都是映射文件,因此建议sql的映射文件都放在mapper文件夹下
- sqlSessionTemplate 自动配置好
- @Mapper 标注的接口也会被自动扫描
mybatis plus的优点就是,继承basemapper就自带了数据库的一些简单方法
public interface UserMapper extends BaseMapper<User> {
}
service接口
public interface UserService extends IService<User> {
}
serviceimpl实现
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}
都是为了减轻对于数据库操作的实现