3万字:SpringBoot入门及原理

目录

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 中使用依赖注入可以通过如下四种方式:

 

  1. 基于接口
  2. 基于 Set 方法
  3. 基于构造函数
  4. 基于注解

 

由于注解的方便好用,目前几乎所有系统都会使用注解的方式来完成依赖注入。其实使用注解的依赖注入方式我们已经很熟悉了,在之前的小节中我们已经用过 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 时
@ConditionalOnClassclasspath 里有指定的类时
@ConditionalOnMissingClassclasspath 里没有指定的类时
@ConditionalOnExpression给定的 Spring Expression Language(SpEL)表达式计算结果为 true 时
@ConditionalOnJavaJVM 的版本匹配特定值或者一个范围时
@ConditionalOnJndi参数中给定的 JNDI 位置至少存在一个时(如果没有给参数,则要有 JNDI InitialContext)
@ConditionalOnProperty指定的属性为指定的值时
@ConditionalOnResourceclasspath 里有指定的资源时
@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), 会经历如下步骤:

 

  1. DispatcherServlet 接收到请求
  2. 通过 HandlerMapping 找到对应的 handler
  3. 然后通过 HandlerAdapter 调用 Controller 进行后续业务逻辑处理(3-4)
  4. 处理完业务逻辑后,Controller 将视图名返回给 HandlerAdapter
  5. DispatcherServlet 选择合适的 ViewResolver 生成 View 对象
  6. 最后 View 渲染并返回响应数据

 

三个核心组件:

  • Handler
  • HandlerMapping
  • HanderAdapter

Handler 是用来做具体事情的,对应的是 Controller 里面的方法,所有有 @RequestMapping 标注的方法都可以看做为一个 Handler。

HandlerMapping 是用来找到 Handler 的,是请求路径与 Handler 的映射关系。

HandlerAdapter 从名字看,可以知道它是一个适配器。它是用来跟具体的 Handler 配合使用的。可以简单理解为各种电子产品与电源适配器(充电器)的关系。

DispatcherServlet 最核心的方法就是 doDispatch ,doDispatch 主要做了四件事:

  1. 根据 request 找到 Handler
  2. 根据 Handler 找到对应的 HanderAdapter
  3. 用 HanderAdapter 处理 Handler
  4. 处理经过以上步骤的结果
注解作用域说明
@ControllerController标识
@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 即可看到应用运行状态。

{"status":"UP"}


如果需要看到详细信息,则需要做添加配置:

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值