目录
springboot基础使用
引入 web 模块
如何做单元测试
热启动
自定义 Filter
自定义 Property
log配置
数据库操作
Mybatis
springboot原理
Aop
IOC
自动配置
SpringMvc
SpringBoot Actuator
如何使用:
常用的监控端点
常用端点详解
springboot基础使用
引入 web 模块
1、pom.xml中添加支持web的模块:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency> |
pom.xml 文件中默认有两个模块:
spring-boot-starter :核心模块,包括自动配置支持、日志和 YAML,如果引入了 spring-boot-starter-web web 模块可以去掉此配置,因为 spring-boot-starter-web 自动依赖了 spring-boot-starter。
spring-boot-starter-test :测试模块,包括 JUnit、Hamcrest、Mockito。
2、编写 Controller 内容:
@RestController
public class HelloWorldController {
@RequestMapping("/hello")
public String index() {
return "Hello World";
}
} |
@RestController 的意思就是 Controller 里面的方法都以 json 格式输出。
3、启动主程序,打开浏览器访问 http://localhost:8080/hello,就可以看到效果了。
如何做单元测试
打开的src/test/下的测试入口,编写简单的 http 请求来测试;使用 mockmvc 进行,利用MockMvcResultHandlers.print()打印出执行结果。
@RunWith(SpringRunner.class)
@SpringBootTest
public class HelloTests {
private MockMvc mvc;
@Before
public void setUp() throws Exception {
mvc = MockMvcBuilders.standaloneSetup(new HelloWorldController()).build();
}
@Test
public void getHello() throws Exception {
mvc.perform(MockMvcRequestBuilders.get("/hello").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().string(equalTo("Hello World")));
}
} |
热启动
热启动需要添加以下的配置:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<fork>true</fork>
</configuration>
</plugin>
</plugins>
</build> |
该模块在完整的打包环境下运行的时候会被禁用。如果你使用 java -jar启动应用或者用一个特定的 classloader 启动,它会认为这是一个“生产环境”。
json 接口开发
在以前使用 Spring 开发项目,需要提供 json 接口时需要做哪些配置呢
添加 jackjson 等相关 jar 包
配置 Spring Controller 扫描
对接的方法添加 @ResponseBody
就这样我们会经常由于配置错误,导致406错误等等,Spring Boot 如何做呢,只需要类添加 @RestController 即可,默认类中的方法都会以 json 的格式返回
@RestController
public class HelloController {
@RequestMapping("/getUser")
public User getUser() {
User user=new User();
user.setUserName("小明");
user.setPassWord("xxxx");
return user;
}
} |
如果需要使用页面开发只要使用@Controller注解即可,下面会结合模板来说明
自定义 Filter
我们常常在项目中会使用 filters 用于录调用日志、排除有 XSS 威胁的字符、执行权限验证等等。Spring Boot 自动添加了 OrderedCharacterEncodingFilter 和 HiddenHttpMethodFilter,并且我们可以自定义 Filter。
两个步骤:
实现 Filter 接口,实现 Filter 方法
添加@Configuration 注解,将自定义Filter加入过滤链
@Configuration
public class WebConfiguration {
@Bean
public RemoteIpFilter remoteIpFilter() {
return new RemoteIpFilter();
}
@Bean
public FilterRegistrationBean testFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new MyFilter());
registration.addUrlPatterns("/*");
registration.addInitParameter("paramName", "paramValue");
registration.setName("MyFilter");
registration.setOrder(1);
return registration;
}
public class MyFilter implements Filter {
@Override
public void destroy() {
// TODO Auto-generated method stub
}
@Override
public void doFilter(ServletRequest srequest, ServletResponse sresponse, FilterChain filterChain)
throws IOException, ServletException {
// TODO Auto-generated method stub
HttpServletRequest request = (HttpServletRequest) srequest;
System.out.println("this is MyFilter,url :"+request.getRequestURI());
filterChain.doFilter(srequest, sresponse);
}
@Override
public void init(FilterConfig arg0) throws ServletException {
// TODO Auto-generated method stub
}
}
} |
自定义 Property
在 Web 开发的过程中,我经常需要自定义一些配置文件,如何使用呢
配置在 application.properties 中
com.zzj.title=title
com.zzj.description=description
自定义配置类
@Component
public class NeoProperties {
@Value("${com.neo.title}")
private String title;
@Value("${com.neo.description}")
private String description; |
log配置
配置输出的地址和输出级别
logging.path=/user/local/log
logging.level.com.favorites=DEBUG
logging.level.org.springframework.web=INFO
logging.level.org.hibernate=ERROR
path 为本机的 log 地址,logging.level 后面可以根据包路径配置不同资源的 log 级别
数据库操作
1、添加相 jar 包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency> |
2、添加配置文件
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.jpa.properties.hibernate.hbm2ddl.auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.show-sql= true |
其实这个 hibernate.hbm2ddl.auto 参数的作用主要用于:自动创建 更新 验证数据库表结构,有四个值:
create: 每次加载 hibernate 时都会删除上一次的生成的表,然后根据你的 model 类再重新来生成新表,哪怕两次没有任何改变也要这样执行,这就是导致数据库表数据丢失的一个重要原因。
create-drop :每次加载 hibernate 时根据 model 类生成表,但是 sessionFactory 一关闭,表就自动删除。
update:最常用的属性,第一次加载 hibernate 时根据 model 类会自动建立起表的结构(前提是先建立好数据库),以后加载 hibernate 时根据 model 类自动更新表结构,即使表结构改变了但表中的行仍然存在不会删除以前的行。要注意的是当部署到服务器后,表结构是不会被马上建立起来的,是要等 应用第一次运行起来后才会。
validate :每次加载 hibernate 时,验证创建数据库表结构,只会和数据库中的表进行比较,不会创建新表,但是会插入新值。
dialect 主要是指定生成表名的存储引擎为 InnoDBD
show-sql 是否打印出自动生成的 SQL,方便调试的时候查看
3、添加实体类和 Dao
@Entity
public class User implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue
private Long id;
@Column(nullable = false, unique = true)
private String userName;
@Column(nullable = false)
private String passWord;
@Column(nullable = false, unique = true)
private String email;
@Column(nullable = true, unique = true)
private String nickName;
@Column(nullable = false)
private String regTime;
|
4、测试
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(Application.class)
public class UserRepositoryTests {
@Autowired
private UserRepository userRepository;
@Test
public void test() throws Exception {
Date date = new Date();
DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG);
String formattedDate = dateFormat.format(date);
userRepository.save(new User("aa1", "aa@126.com", "aa", "aa123456",formattedDate));
userRepository.save(new User("bb2", "bb@126.com", "bb", "bb123456",formattedDate));
userRepository.save(new User("cc3", "cc@126.com", "cc", "cc123456",formattedDate));
Assert.assertEquals(9, userRepository.findAll().size());
Assert.assertEquals("bb", userRepository.findByUserNameOrEmail("bb", "cc@126.com").getNickName());
userRepository.delete(userRepository.findByUserName("aa1"));
}
}
|
Mybatis
首先引入mybatis-spring-boot-starter
的 Pom 文件
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
|
无配置文件注解
1 添加相关 Maven 文件
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
|
2、application.properties
添加相关配置
mybatis.type-aliases-package=com.neo.model
spring.datasource.url=jdbc:mysql://localhost:3306/test?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
Spring Boot 会自动加载 spring.datasource.*
相关配置,数据源就会自动注入到 sqlSessionFactory 中,sqlSessionFactory 会自动注入到 Mapper 中。
在启动类中添加对 mapper 包扫描@MapperScan
@SpringBootApplication
@MapperScan("com.neo.mapper")
public class MybatisAnnotationApplication {
public static void main(String[] args) {
SpringApplication.run(MybatisAnnotationApplication.class, args);
}
}
或者直接在 Mapper 类上面添加注解@Mapper,建议使用上面那种,不然每个 mapper 加个注解也挺麻烦的
|
3、开发 Mapper
public interface UserMapper {
@Select("SELECT * FROM users")
@Results({
@Result(property = "userSex", column = "user_sex", javaType = UserSexEnum.class),
@Result(property = "nickName", column = "nick_name")
})
List<UserEntity> getAll();
@Select("SELECT * FROM users WHERE id = #{id}")
@Results({
@Result(property = "userSex", column = "user_sex", javaType = UserSexEnum.class),
@Result(property = "nickName", column = "nick_name")
})
UserEntity getOne(Long id);
@Insert("INSERT INTO users(userName,passWord,user_sex) VALUES(#{userName}, #{passWord}, #{userSex})")
void insert(UserEntity user);
@Update("UPDATE users SET userName=#{userName},nick_name=#{nickName} WHERE id =#{id}")
void update(UserEntity user);
@Delete("DELETE FROM users WHERE id =#{id}")
void delete(Long id);
}
|
user_sex、nick_name 两个属性在数据库加了下划线和实体类属性名不一致,另外 user_sex 使用了枚举
- @Select 是查询类的注解,所有的查询均使用这个
- @Result 修饰返回的结果集,关联实体类属性和数据库字段一一对应,如果实体类属性和数据库属性名保持一致,就不需要这个属性来修饰。
- @Insert 插入数据库使用,直接传入实体类会自动解析属性到对应的值
- @Update 负责修改,也可以直接传入对象
- @delete 负责删除
https://mybatis.org/mybatis-3/zh/java-api.html更多属性api
注意,使用#符号和$符号的不同:
// This example creates a prepared statement, something like select * from teacher where name = ?;
@Select("Select * from teacher where name = #{name}")
Teacher selectTeachForGivenName(@Param("name") String name);
// This example creates n inlined statement, something like select * from teacher where name = 'someName';
@Select("Select * from teacher where name = '${name}'")
Teacher selectTeachForGivenName(@Param("name") String name);
4、使用
上面三步就基本完成了相关 Mapper 层开发,使用的时候当作普通的类注入进入就可以了
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserMapperTest {
@Autowired
private UserMapper userMapper;
@Test
public void testInsert() throws Exception {
userMapper.insert(new User("aa1", "a123456", UserSexEnum.MAN));
userMapper.insert(new User("bb1", "b123456", UserSexEnum.WOMAN));
userMapper.insert(new User("cc1", "b123456", UserSexEnum.WOMAN));
Assert.assertEquals(3, userMapper.getAll().size());
}
@Test
public void testQuery() throws Exception {
List<User> users = userMapper.getAll();
System.out.println(users.toString());
}
@Test
public void testUpdate() throws Exception {
User user = userMapper.getOne(30l);
System.out.println(user.toString());
user.setNickName("neo");
userMapper.update(user);
Assert.assertTrue(("neo".equals(userMapper.getOne(30l).getNickName())));
}
}
|
极简 xml 版本
极简 xml 版本保持映射文件的老传统,接口层只需要定义空方法,系统会自动根据方法名在映射文件中找对应的 Sql .
1、配置
pom 文件和上个版本一样,只是application.properties
新增以下配置
mybatis.config-location=classpath:mybatis/mybatis-config.xml
mybatis.mapper-locations=classpath:mybatis/mapper/*.xml
指定了 Mybatis 基础配置文件和实体类映射文件的地址
mybatis-config.xml 配置
<configuration>
<typeAliases>
<typeAlias alias="Integer" type="java.lang.Integer" />
<typeAlias alias="Long" type="java.lang.Long" />
<typeAlias alias="HashMap" type="java.util.HashMap" />
<typeAlias alias="LinkedHashMap" type="java.util.LinkedHashMap" />
<typeAlias alias="ArrayList" type="java.util.ArrayList" />
<typeAlias alias="LinkedList" type="java.util.LinkedList" />
</typeAliases>
</configuration>
这里也可以添加一些 Mybatis 基础的配置
2、添加 User 的映射文件
<mapper namespace="com.neo.mapper.UserMapper" >
<resultMap id="BaseResultMap" type="com.neo.entity.UserEntity" >
<id column="id" property="id" jdbcType="BIGINT" />
<result column="userName" property="userName" jdbcType="VARCHAR" />
<result column="passWord" property="passWord" jdbcType="VARCHAR" />
<result column="user_sex" property="userSex" javaType="com.neo.enums.UserSexEnum"/>
<result column="nick_name" property="nickName" jdbcType="VARCHAR" />
</resultMap>
<sql id="Base_Column_List" >
id, userName, passWord, user_sex, nick_name
</sql>
<select id="getAll" resultMap="BaseResultMap" >
SELECT
<include refid="Base_Column_List" />
FROM users
</select>
<select id="getOne" parameterType="java.lang.Long" resultMap="BaseResultMap" >
SELECT
<include refid="Base_Column_List" />
FROM users
WHERE id = #{id}
</select>
<insert id="insert" parameterType="com.neo.entity.UserEntity" >
INSERT INTO
users
(userName,passWord,user_sex)
VALUES
(#{userName}, #{passWord}, #{userSex})
</insert>
<update id="update" parameterType="com.neo.entity.UserEntity" >
UPDATE
users
SET
<if test="userName != null">userName = #{userName},</if>
<if test="passWord != null">passWord = #{passWord},</if>
nick_name = #{nickName}
WHERE
id = #{id}
</update>
<delete id="delete" parameterType="java.lang.Long" >
DELETE FROM
users
WHERE
id =#{id}
</delete>
</mapper> |
其实就是把上个版本中 Mapper 的 Sql 搬到了这里的 xml 中了
3、编写 Mapper 层的代码
public interface UserMapper {
List<UserEntity> getAll();
UserEntity getOne(Long id);
void insert(UserEntity user);
void update(UserEntity user);
void delete(Long id);
} |
对比上一步,这里只需要定义接口方法
如何选择
两种模式各有特点,注解版适合简单快速的模式,其实像现在流行的这种微服务模式,一个微服务就会对应一个自已的数据库,多表连接查询的需求会大大的降低,会越来越适合这种模式。
老传统模式比适合大型项目,可以灵活的动态生成 Sql ,方便调整 Sql 。
总结
使用 Spring Boot 可以非常方便、快速搭建项目,使我们不用关心框架之间的兼容性,适用版本等各种问题,我们想使用任何东西,仅仅添加一个配置就可以,所以使用 Spring Boot 非常适合构建微服务。
springboot原理
Aop
日志记录:
@GetMapping
public Result aspect(String message) {
log.info("aspect controller");
return Result.sucess(message);
}
@Pointcut("execution(public * com.imooc.springboot.aop.*.*(..))")
public void pointCut() {
}
@Before(value = "pointCut()")
public void before(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
String className = joinPoint.getTarget().getClass().getName();
Object[] args = joinPoint.getArgs();
String[] parameterNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames();
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
Map<String, Object> paramMap = new HashMap<>();
for (int i = 0; i < parameterNames.length; i++) {
paramMap.put(parameterNames[i], args[i]);
}
log.info("path:{}",request.getServletPath());
log.info("class name:{}",className);
log.info("method name:{}",methodName);
log.info("args:{}",paramMap.toString());
}
@After(value = "pointCut()")
public void after(JoinPoint joinPoint) {
log.info("{} after", joinPoint.getSignature().getName());
}
@AfterReturning(value = "pointCut()", returning = "returnVal")
public void afterReturning(JoinPoint joinPoint, Object returnVal) {
log.info("{} after return, returnVal: {}", joinPoint.getSignature().getName(), returnVal);
} |
@Pointcut 用来定义切点;execution 是用来匹配连接点的执行方法;public 代表要匹配访问权限为 public 的方法;第一个 * 代表返回值为任意类型;com.imooc.springboot.aop 为包路径;第二个 * 代表前面包路径下的任意类;第三个 * 代表任意方法;(…) 代表任意参数。三个 * 可以换成具体的类或者方法。
异常处理:
@GetMapping("/exception")
public Result exception() {
throw new RuntimeException("runtime exception");
}
@AfterThrowing(value = "pointCut()", throwing = "e")
public void afterThrowing(JoinPoint joinPoint, Exception e) {
log.info("{} after throwing, message: {}", joinPoint.getSignature().getName(), e.getMessage());
} |
执行顺序
原理解析
Spring 的 AOP 是用代理的方式实现的。每个切面都是 Spring 容器中的一个 Bean,在目标方法被调用时,Spring 把切面应用到目标对象,为目标对象动态创建代理,这个过程叫 Weaving(织入)。切面在指定(符合切点条件)的连接点织入到目标对象中。
如上图所示,当调用者调用目标对象时,调用请求会被代理类拦截,在目标对象真正被调用之前,会先织入切面逻辑。当应用需要目标对象时,Spring 才会去创建代理对象,因为 Spring 采用的是运行期织入的实现方式。
IOC
它的实现方式主要有两种,一种是依赖查找,另一种是依赖注入。两者的主要区别在于查找是主动行为,而注入则是被动行为。依赖查找会主动寻找对象所需的依赖,获取依赖对象的时机也是可以自行控制的;依赖注入则是被动的等待容器为其注入依赖对象,容器通过类型或者名称将被依赖对象注入到相应的对象中去。
依赖注入:
依赖注入是控制反转最常见的实现方式,这在很大程度上得益于 Spring 在 Java 领域的垄断地位。在 Spring 中使用依赖注入可以通过如下四种方式:
- 基于接口
- 基于 Set 方法
- 基于构造函数
- 基于注解
由于注解的方便好用,目前几乎所有系统都会使用注解的方式来完成依赖注入。其实使用注解的依赖注入方式我们已经很熟悉了,在之前的小节中我们已经用过 N 多次了。使用 @Controller、@Service、@Component 等注解将类标记为可依赖,然后使用 @Autowire 注解来注入依赖对象。
自动配置
@SpringBootApplication
@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 {
......
}
其中:
@SpringBootConfiguration,表示被注解的元素为一个 Spring Boot 配置类
@EnableAutoConfiguration,负责开启自动配置的注解,这一小节最靓的仔
@ComponentScan,用于配置扫描的包路径 |
@EnableAutoConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
......
}
// 自动配置的工作就是在 AutoConfigurationImportSelector 类中完成的。通过 getAutoConfigurationEntry 方法得到一个需要自动配置的列表: |
Spring Boot 的自动配置再一次践行了约定优于配置的原则。它的自动配置并不是一股脑的将所有预设列表全部加载进来,而是非常智能的 “按需配置”。能做到这一点要归功于 @Conditional 注解和 Condition 接口。它们使得各种配置只有在符合一定的条件时才会被加载。
DataSourceAutoConfiguration 通过 @ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class }) 告诉 Spring,只有当 classpath 下存在 DataSource.class 和 EmbeddedDatabaseType.class 时,DataSourceAutoConfiguration 才会被加载。
@ConditionalOnClass 是 @Conditional 的衍生注解,由 @Conditional 和 OnClassCondition 类组成,源码如下:
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {
......
} |
OnClassCondition 是一个实现了 Condition 接口的类,@ConditionalOnClass 表示 classpath 里有指定的类时加载配置。它是 @Conditional 众多衍生注解中的一个,Spring Boot 提供了一些基于 @Conditional 的衍生注解:
注解 | 说明 |
---|
@ConditionalOnBean | 当容器里有指定 Bean 时 |
@ConditionalOnMissingBean | 当容器里没有指定 Bean 时 |
@ConditionalOnClass | classpath 里有指定的类时 |
@ConditionalOnMissingClass | classpath 里没有指定的类时 |
@ConditionalOnExpression | 给定的 Spring Expression Language(SpEL)表达式计算结果为 true 时 |
@ConditionalOnJava | JVM 的版本匹配特定值或者一个范围时 |
@ConditionalOnJndi | 参数中给定的 JNDI 位置至少存在一个时(如果没有给参数,则要有 JNDI InitialContext) |
@ConditionalOnProperty | 指定的属性为指定的值时 |
@ConditionalOnResource | classpath 里有指定的资源时 |
@ConditionalOnWebApplication | 当前应用是 Web 应用时 |
@ConditionalOnNotWebApplication | 当前应用不是 Web 应用时 |
EnableAutoConfigurationImportSelector
其中主方法为selectImports,
这个@EnableAutoConfiguration注解通过@SpringBootApplication被间接的标记在了Spring Boot的启动类上。在SpringApplication.run(...)的内部就会执行selectImports()方法,找到所有JavaConfig自动配置类的全限定名对应的class,然后将所有自动配置类加载到Spring容器中。
如ServletWebServerFactoryAutoConfiguration
在ServletWebServerFactoryAutoConfiguration类上,有一个@EnableConfigurationProperties注解:开启配置属性,而它后面的参数是一个ServerProperties类
在这个类上,我们看到了一个非常熟悉的注解:@ConfigurationProperties,它的作用就是从配置文件中绑定属性到对应的bean上,而@EnableConfigurationProperties负责导入这个已经绑定了属性的bean到spring容器中。那么所有其他的和这个类相关的属性都可以在全局配置文件中定义,也就是说,真正“限制”我们可以在全局配置文件中配置哪些属性的类就是这些XxxxProperties类,它与配置文件中定义的prefix关键字开头的一组属性是唯一对应的。
至此,我们大致可以了解。在全局配置的属性如:server.port等,通过@ConfigurationProperties注解,绑定到对应的XxxxProperties配置实体类上封装为一个bean,然后再通过@EnableConfigurationProperties注解导入到Spring容器中。
而诸多的XxxxAutoConfiguration自动配置类,就是Spring容器的JavaConfig形式,作用就是为Spring 容器导入bean,而所有导入的bean所需要的属性都通过xxxxProperties的bean来获得。
Spring Boot启动的时候会通过@EnableAutoConfiguration注解找到META-INF/spring.factories配置文件中的所有自动配置类,并对其进行加载,而这些自动配置类都是以AutoConfiguration结尾来命名的,它实际上就是一个JavaConfig形式的Spring容器配置类,它能通过以Properties结尾命名的类中取得在全局配置文件中配置的属性如:server.port,而XxxxProperties类是通过@ConfigurationProperties注解与全局配置文件中对应的属性进行绑定的。
https://afoo.me/posts/2015-07-09-how-spring-boot-works.html
https://blog.csdn.net/liaokailin/article/details/49559951?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase
https://blog.csdn.net/u014745069/article/details/83820511
SpringMvc
Spring MVC 最核心的思想在于 DispatcherServlet
。在现在的开发模式中,我们主要使用的也是 Spring MVC 的这一核心功能。那么 DispatcherServlet
究竟是何方神圣呢?
Spring MVC 内部处理流程如下:
浏览器发起一个请求(如:http://localhost:8080/hello), 会经历如下步骤:
- DispatcherServlet 接收到请求
- 通过 HandlerMapping 找到对应的 handler
- 然后通过 HandlerAdapter 调用 Controller 进行后续业务逻辑处理(3-4)
- 处理完业务逻辑后,Controller 将视图名返回给 HandlerAdapter
- DispatcherServlet 选择合适的 ViewResolver 生成 View 对象
- 最后 View 渲染并返回响应数据
三个核心组件:
- Handler
- HandlerMapping
- HanderAdapter
Handler 是用来做具体事情的,对应的是 Controller 里面的方法,所有有 @RequestMapping 标注的方法都可以看做为一个 Handler。
HandlerMapping 是用来找到 Handler 的,是请求路径与 Handler 的映射关系。
HandlerAdapter 从名字看,可以知道它是一个适配器。它是用来跟具体的 Handler 配合使用的。可以简单理解为各种电子产品与电源适配器(充电器)的关系。
DispatcherServlet 最核心的方法就是 doDispatch ,doDispatch 主要做了四件事:
- 根据 request 找到 Handler
- 根据 Handler 找到对应的 HanderAdapter
- 用 HanderAdapter 处理 Handler
- 处理经过以上步骤的结果
注解 | 作用域 | 说明 |
---|
@Controller | 类 | Controller标识 |
@RequestMapping | 类/方法 | URL映射 |
@ResponseBody | 类/方法 | 以Json方式返回 |
@RequestParam | 参数 | 按名字接收参数 |
@RequestBody | 参数 | 接收Json参数 |
@PathVariable | 参数 | 接收URL中的参数 |
数据验证
注解 | 说明 |
---|
@Null | 被注释的元素必须为 null |
@NotNull | 被注释的元素必须不为 null |
@AssertTrue | 被注释的元素必须为 true |
@AssertFalse | 被注释的元素必须为 false |
@Min(value) | 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 |
@Max(value) | 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 |
@DecimalMin(value) | 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 |
@DecimalMax(value) | 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 |
@Size(max, min) | 被注释的元素的大小必须在指定的范围内 |
@Digits (integer, fraction) | 被注释的元素必须是一个数字,其值必须在可接受的范围内 |
@Past | 被注释的元素必须是一个过去的日期 |
@Future | 被注释的元素必须是一个将来的日期 |
@Pattern(value) | 被注释的元素必须符合指定的正则表达式 |
@Email | 被注释的元素必须是电子邮箱地址 |
@Length | 被注释的字符串的大小必须在指定的范围内 |
@NotEmpty | 被注释的字符串的必须非空 |
@Range | 被注释的元素必须在合适的范围内 |
拦截器
Spring MVC 中所有的拦截器都实现/继承自 HandlerInterceptor
接口。我们想要写一个自定义拦截器的话,需要实现/继承 HandlerInterceptor
或其子接口/实现类。下图是 Spring MVC 中拦截器的类图
SpringBoot Actuator
介绍:Actuator 是 Spring Boot 的附加功能,可以帮助我们监控和管理我们的应用。支持使用 HTTP 端点或 JMX 来管理和监视应用程序。审计、健康状况和指标收集也可以自动应用到我们的应用程序中。
如何使用:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency> |
Actuator 默认只开放 health 和 info 两个端点,可以通过添加一些配置,来获取更多的信息。如在 application.yml 文件中添加如下配置来暴露所有端点,并显示 health 详情:
# actuator监控
management:
server:
# 设置监控服务端口,如果没写默认就是服务器的端口
port: 8081
endpoints:
# 设置端点是否可用 默认只有shutdown可用
enabled-by-default: true
web:
# 设置是否暴露端点 默认只有health和info可见
exposure:
# 包括所有端点
include: "*" # 注意需要添加引号
# 排除端点
exclude: shutdown
endpoint:
health:
show-details: always
#可以关闭指定的端点
shutdown:
enabled: false |
更多配置属性可以参考:https://docs.spring.io/spring-boot/docs/current/reference/html/appendix-application-properties.html#actuator-properties
添加management.endpoints.web.exposure.include=*
配置后启动应用,访问 http://127.0.0.1:8080/actuator 我们可以看到所有的 Actuator 端点列表。
常用的监控端点
端点 | 描述 |
---|
beans | 获取应用中所有的 Spring Beans 的完整关系列表 |
caches | 获取公开可以用的缓存 |
conditions | 获取自动配置条件信息,记录哪些自动配置条件通过和没通过的原因 |
configprops | 获取所有配置属性,包括默认配置,显示一个所有 @ConfigurationProperties 的整理列版本 |
env | 获取所有环境变量 |
flyway | 获取已应用的所有Flyway数据库迁移信息,需要一个或多个 Flyway Bean |
liquibase | 获取已应用的所有Liquibase数据库迁移。需要一个或多个 Liquibase Bean |
health | 获取应用程序健康指标(运行状况信息) |
httptrace | 获取HTTP跟踪信息(默认情况下,最近100个HTTP请求-响应交换)。需要 HttpTraceRepository Bean |
info | 获取应用程序信息 |
integrationgraph | 显示 Spring Integration 图。需要依赖 spring-integration-core |
loggers | 显示和修改应用程序中日志的配置 |
logfile | 返回日志文件的内容(如果已设置logging.file.name或logging.file.path属性) |
metrics | 获取系统度量指标信息 |
mappings | 显示所有@RequestMapping路径的整理列表 |
scheduledtasks | 显示应用程序中的计划任务 |
sessions | 允许从Spring Session支持的会话存储中检索和删除用户会话。需要使用Spring Session的基于Servlet的Web应用程序 |
shutdown | 关闭应用,要求endpoints.shutdown.enabled设置为true,默认为 false |
threaddump | 获取系统线程转储信息 |
heapdump | 返回hprof堆转储文件 |
jolokia | 通过HTTP公开JMX bean(当Jolokia在类路径上时,不适用于WebFlux)。需要依赖 jolokia-core |
prometheus | 以Prometheus服务器可以抓取的格式公开指标。需要依赖 micrometer-registry-prometheus |
常用端点详解
health
主要用来检测应用的运行状况,是使用最多的一个监控点。监控软件通常使用该接口实时监测应用运行状况,在系统出现故障时把报警信息推送给相关人员,如磁盘空间使用情况、数据库和缓存等的一些健康指标。
默认情况下 health 端点是开放的,访问 http://127.0.0.1:8080/actuator/health 即可看到应用运行状态。
如果需要看到详细信息,则需要做添加配置:
management.endpoint.health.show-details=always
访问返回信息如下:
{"status":"UP","details":{"diskSpace":{"status":"UP","details":{"total":180002725888,"free":8687988736,"threshold":10485760}}}} |
在合适的时候,sprinboot会自动配置以下健康指标
返回结果如下:
{
"status":"DOWN",
"components":{
"db":{
"status":"UP",
"details":{
"database":"MySQL",
"validationQuery":"isValid()"
}
},
"diskSpace":{
"status":"UP",
"details":{
"total":512107737088,
"free":480970215424,
"threshold":10485760,
"exists":true
}
},
"ping":{
"status":"UP"
},
"redis":{
"status":"DOWN",
"details":{
"error":"org.springframework.data.redis.RedisConnectionFailureException: Unable to connect to Redis; nested exception is io.lettuce.core.RedisConnectionException: Unable to connect to localhost:6379"
}
}
}
} |
同时也可自定义健康指标:
1:继承 AbstractHealthIndicator
2:实现 HealthIndicator
返回值必须是一个Health,Health本身也是一个构建器。
env
通过 env 可以获取到所有关于当前 Spring Boot 应用程序的运行环境信息,如:操作系统信息(systemProperties)、环境变量信息、JDK 版本及 ClassPath 信息、当前启用的配置文件(activeProfiles)、propertySources、应用程序配置信息(applicationConfig)等。
可以通过 http://127.0.0.1:8080/actuator/env/{name} ,name表示想要查看的信息,可以独立显示。
如在配置文件中配置如下
spring:
datasource:
url: jdbc:mysql://192.168.151.115:3306/test?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver |
输入http://127.0.0.1:8080/actuator/env/spring.datasource.url ,
"property":{
"source":"applicationConfig: [classpath:/application.yml]",
"value":"jdbc:mysql://192.168.151.115:3306/test?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true"
}
|
mappings
查看所有 URL 映射,即所有 @RequestMapping 路径的整理列表。
访问 http://127.0.0.1:8080/actuator/mappings 返回部分信息如下:
{
"handler":"com.example.actuator.actuatordemo.TestController.MappingTestController#testMapping(Integer, String)",
"predicate":"{ /mappingTest/zzjTest}",
"details":{
"handlerMethod":{
"className":"com.example.actuator.actuatordemo.TestController.MappingTestController",
"name":"testMapping",
"descriptor":"(Ljava/lang/Integer;Ljava/lang/String;)Ljava/lang/Integer;"
},
"requestMappingConditions":{
"consumes":[
],
"headers":[
],
"methods":[
],
"params":[
],
"patterns":[
"/mappingTest/zzjTest"
],
"produces":[
]
}
}
} |
heapdump
访问:http://127.0.0.1:8080/actuator/heapdump会自动生成一个 GZip 压缩的 Jvm 的堆文件 heapdump,我们可以使用 JDK 自带的 Jvm 监控工具 VisualVM 打开此文件查看。
threaddump
获取系统线程的转储信息,主要展示了线程名、线程ID、线程的状态、是否等待锁资源等信息。在工作中,我们可以通过查看线程的情况来排查相关问题。
访问 http://127.0.0.1:8080/actuator/threaddump 返回部分信息如下:
{
"threadName":"Finalizer",
"threadId":3,
"blockedTime":-1,
"blockedCount":88,
"waitedTime":-1,
"waitedCount":20,
"lockName":"java.lang.ref.ReferenceQueue$Lock@6f4c4464",
"lockOwnerId":-1,
"lockOwnerName":null,
"inNative":false,
"suspended":false,
"threadState":"WAITING",
"stackTrace":[
{
"methodName":"wait",
"fileName":"Object.java",
"lineNumber":-2,
"className":"java.lang.Object",
"nativeMethod":true
},
{
"methodName":"remove",
"fileName":"ReferenceQueue.java",
"lineNumber":144,
"className":"java.lang.ref.ReferenceQueue",
"nativeMethod":false
},
{
"methodName":"remove",
"fileName":"ReferenceQueue.java",
"lineNumber":165,
"className":"java.lang.ref.ReferenceQueue",
"nativeMethod":false
},
{
"methodName":"run",
"fileName":"Finalizer.java",
"lineNumber":216,
"className":"java.lang.ref.Finalizer$FinalizerThread",
"nativeMethod":false
}
],
"lockedMonitors":[
],
"lockedSynchronizers":[
],
"lockInfo":{
"className":"java.lang.ref.ReferenceQueue$Lock",
"identityHashCode":1867269220
}
} |
metrics
访问 http://127.0.0.1:8080/actuator/metrics 可以获取系统度量指标信息项如下:
{
"names":[
"hikaricp.connections",
"hikaricp.connections.acquire",
"hikaricp.connections.active",
"hikaricp.connections.creation",
"hikaricp.connections.idle",
"hikaricp.connections.max",
"hikaricp.connections.min",
"hikaricp.connections.pending",
"hikaricp.connections.timeout",
"hikaricp.connections.usage",
"http.server.requests",
"jdbc.connections.max",
"jdbc.connections.min",
"jvm.buffer.count",
"jvm.buffer.memory.used",
"jvm.buffer.total.capacity",
"jvm.classes.loaded",
"jvm.classes.unloaded",
"jvm.gc.live.data.size",
"jvm.gc.max.data.size",
"jvm.gc.memory.allocated",
"jvm.gc.memory.promoted",
"jvm.gc.pause",
"jvm.memory.committed",
"jvm.memory.max",
"jvm.memory.used",
"jvm.threads.daemon",
"jvm.threads.live",
"jvm.threads.peak",
"jvm.threads.states",
"logback.events",
"process.cpu.usage",
"process.start.time",
"process.uptime",
"system.cpu.count",
"system.cpu.usage",
"tomcat.sessions.active.current",
"tomcat.sessions.active.max",
"tomcat.sessions.alive.max",
"tomcat.sessions.created",
"tomcat.sessions.expired",
"tomcat.sessions.rejected"
]
} |
官方文档:https://docs.spring.io/spring-boot/docs/2.2.1.RELEASE/reference/html/production-ready-features.html