SpringBoot应用简单示例
SpringBoot应用简单示例
HelloWorld
搭建项目
File -> new project 选择springboot项目初始化。
选择项目默认依赖。
在项目中建一个controller,代码如下:
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* hello world测试类
* @author Alone
*/
@RestController
public class HelloController {
@RequestMapping("/hello")
public String Hello() {
return "Hello,world!";
}
}
输出结果:
@ResponseBody的作用
将方法的返回值,以特定的格式写入到response的body区域,进而将数据返回给客户端。当方法上面没有写ResponseBody,底层会将方法的返回值封装为ModelAndView对象。如果返回值是字符串,那么直接将字符串写到客户端;如果是一个对象,会将对象转化为json串,然后写到客户端。
@RequestMapping、@ResponseBody 等这些注解是干什么的? - 果真真的回答 - 知乎
DispatcherServlet#doDispatch
@ComponentScan排除扫描bean
SpringBoot集成日志
SpringBoot日志初始化原理
有个loggingApplicationListener的监听器,监听了spring的事件,读取了spring容器中的日志配置,进行了日志的初始化。
(springboot-actuator可以实现动态调整日志级别,待研究)
消息转换器
to be continue
拦截器
拦截器是一种动态拦截方法调用的机制。可以在指定方法前后执行预先设定的代码以及阻止方法调用。
拦截器的实现需要实现HandlerInterceptor
接口:
/**
* 拦截器测试
* @author Alone
*/
@Component
public class InterceptConfiguration implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle...");
// 此处返回false导致后面不再执行
return false;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle...");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion");
}
}
将拦截器添加入SpringMVC的配置类:
/**
* @author Alone
*/
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private InterceptConfiguration interceptConfiguration;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(interceptConfiguration);
}
}
默认是返回true。
HandlerInterceptor
中三个方法的作用:
preHandler(HttpServletRequest request, HttpServletResponse response, Object handler)
: 方法在请求处理之前调用,在这个方法中进行一些前置预处理或初始化操作,也可以决定是否终止请求。当该方法返回false时,后续的interceptor和controller都不会执行。拦截器可以注册多个,调用时依据声明顺序依次执行。
handler
参数本质上是一个方法对象,有了它就可以操作原始执行的方法。postHandler(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
: 在当前请求处理之后,DispatcherServlet进行视图渲染之前调用。此时可以对controller处理之后的modelAndView对象进行操作。afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handle, Exception ex)
: 该方法在整个请求结束后进行调用,主要用于清理资源。
多拦截器的执行顺序:
过滤器
to be continue
操作数据库
Spring Data Jpa
- 引入依赖 pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
配置文件application.yml:
spring:
jpa:
show-sql: true
hibernate:
ddl-auto: update
datasource:
url: jdbc:mysql://localhost:3306/gotest?useUnicode=true&characterEncoding=UTF-8
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 我是密码
spring.jpa.hibernate.ddl-auto
属性的选项说明:
create
: 无论数据是否改变,每次hibernate
加载时都删除上一次生成的表create-drop
: 每次加载hibernate
时都根据实体类生成表,但是sessionFactory一关闭,表就自动删除。update
: 每次加载hibernate时根据model类会自动更新表的结构,不存在则新建。不会删除旧数据。validate
: 每次加载hibernate时,验证创建数据库表结构,只会和数据库中的表进行比较,不会创建新表,但是会插入新值。none
: 不做任何操作
实体类:
package com.alone.simpleusage.database.datajpa.entity;
import lombok.Data;
import javax.persistence.*;
/**
* 学生实体类
* @Entity 表示这是一个实体类
* @Table 定义数据库表名,jpa在操作的时候会先去寻找这张表,没有这张表的话就创建
*
* @author Alone
*/
@Data
@Entity
@Table(name = "student")
public class Student {
/**
* @Id 表示这是主键
* @GeneratedValue 主键生成策略
* @Column 配置属性和数据库字段的对应
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer urid;
@Column(name = "code")
private String code;
@Column
private String name;
}
dao接口:
package com.alone.simpleusage.database.datajpa.dao;
import com.alone.simpleusage.database.datajpa.entity.Student;
import org.springframework.data.jpa.repository.JpaRepository;
public interface StudentRepository extends JpaRepository<Student, Integer> {
}
运行测试:
@SpringBootTest
public class RepositoryTest {
@Autowired
private StudentRepository studentRepository;
@Test
public void testInsert() {
Student student = new Student();
student.setCode("1");
student.setName("顶不住了");
studentRepository.save(student);
}
}
运行结果:
Spring JPA实现的默认方法:
Spring jpa 动态查询:
public interface StudentRepository extends JpaRepository<Student, Integer>, JpaSpecificationExecutor<Student> {
}
@Test
public void testQuery() {
List<Student> userList = studentRepository.findAll(((root, criteriaQuery, criteriaBuilder) -> {
// 定义集合,用于存放动态查询条件
List<Predicate> predicateList = Lists.newArrayList();
predicateList.add(criteriaBuilder.like(root.get("code").as(String.class), "%1%"));
predicateList.add(criteriaBuilder.like(root.get("name").as(String.class), "%顶%"));
return criteriaBuilder.and(predicateList.toArray(new Predicate[predicateList.size()]));
}));
userList.forEach(System.out::println);
}
输出结果:
多表联查:
to be continue
分页查询:
@Test
public void testPageQuery() {
// 构造查询条件
Specification<Student> spec = new Specification<Student>() {
@Override
public Predicate toPredicate(Root<Student> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
return null;
}
};
// 分页查询接口
PageRequest pageRequest = PageRequest.of(0, 1);
Page<Student> students = studentRepository.findAll(spec, pageRequest);
System.out.println("查询总页数:" + students.getTotalPages());
System.out.println("查询总记录数:" + students.getTotalElements());
System.out.println("数据集合列表:" + students.getContent());
}
结果:
排序查询:
@Test
public void testSortQuery() {
Sort sort = Sort.by(Sort.Direction.DESC, "urid");
Specification<Student> spec = new Specification<Student>() {
@Override
public Predicate toPredicate(Root<Student> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
return null;
}
};
List<Student> studentList = studentRepository.findAll(spec, sort);
for (Student student : studentList) {
System.out.println(student);
}
}
Druid数据源
- 添加依赖引入druid数据源:
<!-- spring jdbc 操作模版 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- 引入druid数据源 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.4</version>
</dependency>
- 编写配置文件:
spring:
datasource:
url: jdbc:mysql://localhost:3306/gotest?useUnicode=true&characterEncoding=UTF-8
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 密码
type: com.alibaba.druid.pool.DruidDataSource
- 编写配置bean:
/**
* Druid数据源配置类
* @author Alone
*/
@Configuration
public class DruidConfig {
/**
* 配置绑定
* @return
*/
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DruidDataSource druid() {
return new DruidDataSource();
}
}
此处的前缀prefix要和application.yml中的前缀对应,不然会报java.sql.SQLException: url not set
的异常。
且该注解一定要标在对应bean上。
- 测试获取数据源
@Test
public void testGetDruidDataBase() throws SQLException {
System.out.println(dataSource.getClass());
Connection connection = dataSource.getConnection();
System.out.println(connection);
connection.close();
}
Mybatis-Plus
- 引入依赖:
<!-- mybatis-plus 依赖-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
- 配置文件:
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 开启sql语句打印
- 实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Long id;
private String name;
private Integer age;
private String email;
private Long managerId;
private LocalDateTime createTime;
}
此处简单说一下LocalDateTime相较于Date的优点:
- Date类型的打印出的日期可读性差
- Date使用
SimpleDateFormat
进行格式化,该对象线程不安全 - Date日期处理麻烦,
getYear()
getDay()
等方法均已被弃用
- 定义mapper接口
public interface UserMapper extends BaseMapper<User> {
}
- 启动类添加mapperscan注解
@SpringBootApplication
@MapperScan("com.alone.simpleusage.database.mpp")
public class SimpleusageApplication {
public static void main(String[] args) {
SpringApplication.run(SimpleusageApplication.class, args);
}
}
- 测试
@Test
public void testUserMapper() {
DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
User user = new User(6L, "test", 10, "123456@163.com", null, LocalDateTime.parse("2022-11-08 15:44:00", df));
userMapper.insert(user);
}
事务处理
操作缓存
AOP
相关概念
AOP(Aspect Oriented Programming)面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。主要用于分离系统的业务逻辑和系统服务。
通知:指切面要完成的工作,定义了切面是什么以及何时使用。
共有5种类型:
- 前置通知(Before):在目标方法被调用之前调用通知功能
- 后置通知(After):在目标方法完成之后调用通知,此时不关心方法的输出结果是什么
- 返回通知(After-returning):在目标方法成功执行之后调用通知
- 异常通知(After-throwing):在目标方法抛出异常后调用通知
- 环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为
连接点:应用在执行时能够插入切面的位置
切点:切面执行的位置
切面:切点和通知的结合
引入:在不修改现有类的基础上,向类中添加新方法和新属性。
织入:把切面应用到目标对象并创建新的代理对象的过程。可以在编译期(此时需要特殊的编译器,例如AspectJ),类加载期(切面在目标类加载到JVM时被织入,需要特殊的类加载器),运行期(应用运行的某一时刻,例如Spring AOP)织入切面。
栗子
springboot切面实现打印当前系统时间:
pom.xml:
<!-- aop依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
切面类:
package com.alone.simpleusage.aop.aspect;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;
/**
* 切面类
* @author Alone
*/
@Aspect
@Component
@Slf4j
public class SysOutTimeAspect {
/**
* 切入点方法
*/
@Pointcut("execution(public * com.alone.simpleusage.aop.controller.*.*(..))")
public void printTime() {
}
/**
* 前置通知
* 这里的方法名和入参都不能写错,不然会进不去需要被增强的方法
* @param joinPoint
*/
@Before("printTime()")
public void doBefore(JoinPoint joinPoint) {
log.info("开始打印");
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
log.info(df.format(System.currentTimeMillis()));
}
}
测试类:
package com.alone.simpleusage.aop.controller;
import org.springframework.web.bind.annotation.*;
/**
* 切面测试controller
* @author Alone
*/
@RestController
@RequestMapping("/aoptest")
public class AopController {
@RequestMapping("/hello")
public String hello(@RequestParam(value = "name", required = false) String name) {
System.out.println("进入hello方法");
return "hello" + name;
}
}
结果:
工作流程
- Spring容器启动
- 读取所有切面配置中的切入点,没有被切面引入的切点不会被加载。
- 初始化bean:判定bean对应的类中的方法是否匹配到任意切入点,如果匹配失败,则创建对象,匹配成功则创建原始对象的代理对象
- 获取bean执行方法:如果获取到的是bean对象自身则调用方法并执行,如果获取到代理对象则根据代理对象的运行模式运行原始方法与增强的内容。
切入点表达式书写规则
以类名描述:
execution (访问修饰符 返回值 包名.类/接口名.方法名 (参数) 异常名)
通配符:
*
:单个独立的任意符号
..
: 多个连续的任意符号
+
: 匹配子类
注意事项
如果是环绕通知,被增强方法的返回值是void时执行proceedingJoinPoint.proceed()
后得到的返回值是null
事件发布
主要用于操作解耦。
事件定义:
package com.alone.simpleusage.event;
import org.springframework.context.ApplicationEvent;
/**
* 事件定义
* @author Alone
*/
public class MailEvent extends ApplicationEvent {
private String message;
public MailEvent(Object source, String message) {
super(source);
this.message = message;
}
public String sendMail() {
return message;
}
}
事件发布:
package com.alone.simpleusage.event;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;
/**
* 事件发布
* @author Alone
*/
@Component
@Slf4j
public class MailEventPublisher {
@Autowired
private ApplicationEventPublisher applicationEventPublisher;
public void publish(String message) {
log.info("开始发送");
MailEvent mailEvent = new MailEvent(this, message);
applicationEventPublisher.publishEvent(mailEvent);
}
}
事件监听:
package com.alone.simpleusage.event;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import java.time.LocalTime;
/**
* 事件监听
* @author Alone
*/
@Component
@Slf4j
public class MailEventListener {
@EventListener
public void receiveMail(MailEvent mailEvent) {
String message = mailEvent.sendMail();
log.info("receive message: " + message + "time: " + LocalTime.now());
}
}
测试:
package com.alone.simpleusage.event;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
@SpringBootTest
public class EventTest {
@Resource
private MailEventPublisher sendEmailEventPublisher;
@Test
public void publish() {
sendEmailEventPublisher.publish("hello world");
}
}
整合Quartz
- 导入坐标
- 定义具体要执行的任务
- 定义工作明细与触发器,并绑定对应关系
参考资料
- springboot动态调整日志级别
- 玩转 Spring Boot 系列案例源码
- 优雅的使用spring boot: 消息转换器的介绍与使用
- SpringBoot实现过滤器、拦截器与切片
- 黑马程序员2022新版SSM框架教程
- SpringBoot图文教程12—SpringData Jpa的基本使用
- Springboot 系列(九)使用 Spring JDBC 和 Druid 数据源监控
- mybatis plus 看这篇就够了,一发入魂
- 学会了MybatisPlus,代码开发效率提高了10倍!
- Spring Boot (五): Redis缓存使用姿势盘点
- Spring boot学习(六)Spring boot实现AOP记录操作日志
- SpringBoot—集成AOP详解(面向切面编程Aspect)
- Spring框架中的事件订阅发布
- Spring入门(十):Spring AOP使用讲解