Post

重识Spring全家桶

Spring

快速上手

要想让一个类交由IOC容器管理,在Spring框架中需要我们在配置文件中显式配置该类,包括依赖注入。完成配置后便可以通过ClassPathXmlApplicationContext类获取到Bean了。

注解开发

@Component注解和三个衍生注解@Service、@Repository、@Controller都是一个作用,即取代了手动通过Bean标签配置文件中配置Bean的过程。
@Configuration注解取代了配置文件中初始的配置文本,使得我们可以通过配置类替代配置文件,@ComponetScan指定了扫描Bean的路径。
@AutoWired针对同名bean需要通过@Qualifier注解开启指定名称装配bean,一般不需要使用,使用@Value实现简单数据类型注入,配合@PropertySource加载外部配置文件,从而使Value注解能够直接通过属性名读取值。

AOP

为了在不惊动原始设计的基础上为其进行功能增强。在连接点的切入点上执行通知类定义的通知方法,切面就是切入点和通知之间的关系。
基本使用流程为导入AOP相关坐标 – 定义Dao和实现类 – 定义通知类 – 定义切入点 – 绑定切入点和通知关系,并指定通知添加到连接点的具体执行位置 – 定义通知类受Spring容器管理,并定义当前类为切面类。
切入点表达式可以使用通配符快速描述。”*“是单个独立的任意符号,可以独立出现,也可作为前后缀匹配符出现。”..”是多个连续的任意符号,常用于简化包名与参数的书写。”+”专用于匹配子类类型。
各种通知都可以通过JoinPoint对象获取原方法的调用参数,其必须设置为第一个形参,返回后通知和抛出异常后通知可以通过设置returning形参和throwing形参接收对应的返回值和异常对象,环绕通知可以通过手写原方法调用接受返回值和异常对象。

事务

基本使用流程是在业务层接口上通过@Transactional注解添加Spring事务管理,再根据实现技术设置事务管理器,最后开启注解式事务驱动。
事务传播行为就是事务协调员对事务管理员所携带事务的处理态度。可以设置多个事务协调员对于事务管理员是否开启事务所作出的响应,如是加入管理员事务还是新开一个事务。应用场景比如转账日志记录,即使失败了日志记录的操作不应该回滚。

Spring MVC

快速上手

基本使用流程需先导入SpringMVC坐标与Servlet坐标,创建SpringMVC控制类(等同于Servlet功能),配置SpringMVC环境,设定SpringMVC加载对应的Bean,然后初始化Servlet容器加载SpringMVC环境,设置通过SpringMVC技术处理的请求即请求拦截路径。
SpringMVC在启动时通常会构建两套上下文,Root ApplicationContextDispatcherServlet的上下文,如果不对业务层和控制器隔离处理,Controller 会被两个上下文都加载,导致实例重复、配置冲突、AOP、拦截器、生效环境不一致等问题。可以通过对ComponetScan注解进行调整,即只扫描需要的包或扫描所有的包并排除Controller包实现分层。
Param支持五种类型的参数传递。普通参数,请求参数与形参名相同可以自动映射;Pojo类其属性和形参名对以上也可以自动映射,Pojo参数还可以嵌套Pojo参数;数组和集合类似,集合要在前加上@RequestParam注解以绑定参数关系。
Body支持三种json数据的传递。数组使用[…]直接进行传递,Pojo使用{…}内部字段就代表着对象属性,Pojo集合则是使用{}嵌套进行传递。

异常处理器

定义异常处理类,主要是在类上使用@RestControllerAdvice注解,定义处理异常逻辑的doException方法并使用@ExceptionHandler指定要处理的异常类型。
自定义异常类主要通过拓展RuntimeException类实现,内有多个构造方法,按需取用即可。

拦截器

拦截器是用于SpringMVC中动态拦截控制器方法执行的。可阻止原方法调用,也可用于转为执行预设代码。
和过滤器Filter的区别在于Filter属于Servlet技术,拦截器Interceptor属于SpringMVC技术。Filter是对所有访问进行增强,而Interceptor只对MVC访问进行增强。
拦截器实现是通过实现HandlerInterceptor接口,再在配置类中注册拦截器,配置类通过实现WebMvcConfigurer类完成拦截器的注册(SpringBoot无法简化该配置类)。
拦截器主要经历preHandle -> controller -> postHandle -> afterCompletion四个方法。若是在preHandle中return false就代表着请求被拦截,将无法执行接下来的步骤。当拦截器运行中断时,仅运行配置在前面的拦截器的afterCompletion操作。

Mybatis

Mybatis是为了简化传统的JDBC开发诞生的,JDBC存在着硬编码且操作繁琐的缺点,MyBatis通过配置文件和自动完成结果封装等操作过程来进行简化开发。
有注解开发和Mapper代理两种开发方式,前者适用于简单SQL,后者更为泛用,注解开发较为简单,因此在本文中只讨论Mapper代理开发方式相关的细节。
首先就是要定义和SQL映射文件同名的Mapper接口,SQL映射文件的namepspace属性也需要是Mapper接口的全限定名。在Mapper接口中定义的方法,方法名就是SQL映射文件中sql语句的id,需要保持参数类型和返回类型一致。

1
2
3
4
5
6
7
8
9
10
<!-- SQL映射文件初步配置 -->
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.kurisu.demo.mapper.PersonMapper">
    <select id ="getAll" resultType ="person">
        select * from tb_user;
    </select>
</mapper>

不传参查询

查询所有数据不涉及到参数的传递,主要需要注意返回结果的映射处理。只有表中列名和实体类中属性命一致才能让返回结果自动封装到实体类中。如果没办法做到标准命名,我们可以通过在SQL语句中起别名来实现自动映射,但这种方式一旦涉及到的字段多起来操作会很麻烦。
所以我们还可以使用resultMap来实现映射,这种方式是最为主流的,在resultMap中我们可以定义好数据库表的字段名称和实体类的属性名称的映射,在编写SQL语句时直接标注resultMap替换resultType即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.kurisu.demo.mapper.PersonMapper">
    <!-- 此处定义resultMap -->
    <resultMap id="personResultMap" type="person">
        <!-- 定义列名和属性名的映射 -->
        <result column="username" property="name" />
    </resultMap>
    <!-- 返回由resultType变为resultMap -->
    <select id ="getAll" resultMap="personResultMap">
        select * from tb_user;
    </select>
</mapper>

传参查询

传参时要采用#{id}的方式来进行,其原理是在sql语句中先将参数替换为?再赋值进去,而${id}是拼接sql,会出现sql注入的问题,一般只在表名或者列名不固定的情况下使用。
在xml里特殊字符处理可以采用转义字符、CDATA区的方式来处理。特殊字符少用转义字符方便,特殊字符要处理的多用CDATA区更好。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    <!-- 这里parameterType属性可以省略 -->
    <select id="getById" parameterType="int" resultMap="personResultMap">
        select * from tb_user where id = #{id};
    </select>

    <!-- 特殊字符处理 -->
    <select id="getById" parameterType="int" resultMap="personResultMap">
        <!-- “&lt;”转义为小于号 -->
        select * from tb_user where id &lt; #{id};
        <!-- CDATA区 -->
        select * from tb_user where id
        <![CDATA[
            <
        ]]>
        #{id};
    </select>

多条件查询,and连接各个条件即可,对象传参要注意对象属性名称和参数占位符的名称一致。而条件不会是每一次查询都是固定的,用户可能会只限定某几个条件进行查询,MyBatis也对动态sql提供了强大的支撑。
我们可以通过if标签编写条件判断逻辑动态修改sql语句,对于第一个条件不需要and运算符的问题,可以在开头加入恒等式统一格式,也可以将where改用where标签
choose(when, otherwise)标签类似于switch语句,也可以实现相同的效果。这之中otherwise主要是保证一个条件都没有的时候sql语句不会出错,where标签可以起到同样的作用。

1
2
3
4
5
6
7
// 多参数传递接口编写
// 方法1:通过@Param注解指定将参数传递至哪个占位符
List<Person> selectByCondition(@Param("name") String name, @Param("addr") String addr, @Param("gender") Character gender)
// 方法2:POJO类传递,会自动去寻找Person类中的get方法进行传递
List<Person> selectByCondition(Person person);
// 方法3:Map集合传递,键名要和占位符保持一致
List<Person> selectByCondition(Map map);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<!-- and连接 -->
    <select id="selectByCondition" resultMap = "personResultMap">
        select * from tb_user
        where
            name = #{name}
            and addr like #{addr}
            and gender = #{gender}
    </select>
<!-- if动态条件查询 -->
    <select id="selectByCondition" resultMap = "personResultMap">
        select * from tb_user
        <where>
            <if test = "name != null and name != ''">
                and name = #{name}
            </if>
            <if test = "gender != null and gender != ''">
                and gender = #{gender}
            </if>
            <if test = "addr != null and addr != ''">
                and addr = #{addr}
            </if>
        </where>
    </select>
<!-- choose(when, otherwise)动态条件查询 -->
    <select id="selectByCondition" resultMap="personResultMap">
    SELECT * FROM tb_user
    <where>
        <choose>
        <when test="name != null and name != ''">
            AND name = #{name}
        </when>
        <when test="gender != null and gender != ''">
            AND gender = #{gender}
        </when>
        <when test="addr != null and addr != ''">
            AND addr = #{addr}
        </when>
        </choose>
    </where>
    </select>

数据操作

如果想要插入数据后返回主键信息,在insert标签中添加useGeneratedKeys=”true” keyProperty=”id”就可以将id作为返回值了,可以直接从执行方法的形参对象中通过get方法获取。对于动态属性的修改也同样是采用动态sql来实现,。

1
2
3
4
5
6
7
8
9
10
<!-- 常规插入 -->
    <insert id="addPerson">
        insert into tb_user(username, password, gender, addr)
        values (#{name}, #{password}, #{gender}, #{addr});
    </insert>
<!-- 主键返回,两个属性 -->
    <insert id="addPerson" useGeneratedKeys="true" keyProperty="id">
        insert into tb_user(username, password, gender, addr)
        values (#{name}, #{password}, #{gender}, #{addr});
    </insert>

删除主要使用foreach标签,实际上MyBatis会将数组参数封装为一个Map集合,默认键为array,值为原数组,在foreach标签中遍历需要在collection属性指定集合名称,这里要么使用array,要么在接口处用@Param注解改变Map集合的默认键的名称。
在传参中单个POJO类型、Map类型数据直接保证属性名和键名和参数占位符名称一致即可,在前面已经提过。单个集合类型会被默认封装为<键:集合>的Map,可以通过arg0、collection以及对应类型的array或list访问。 而多个参数也会被封装为Map集合,可以使用默认键名arg+下标如arg0、arg1进行访问,更推荐通过@Param注解改变Map集合的默认key的名称。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- 将批量id数组ids传入,需要@Param写别名 -->
    <delete id="deletePersonsByIds" parameterType="java.util.List">
    DELETE FROM tb_user
    WHERE id IN
    <foreach collection="ids" item="id" open="(" separator="," close=")">
        #{id}
    </foreach>
    </delete>
<!-- 使用默认键list访问 -->
    <delete id="deletePersonsByIds" parameterType="java.util.List">
    DELETE FROM tb_user
    WHERE id IN
    <foreach collection="list" item="id" open="(" separator="," close=")">
        #{id}
    </foreach>
    </delete>

SpringBoot

快速上手

SpringBoot项目的构建过程主要包括:

  1. 创建Maven项目
  2. 导入SpringBoot起步依赖
    最基本的就是继承spring-boot-starter-parent父工程,以及引入服务对应的spring-boot-starter-xxx依赖,比如spring-boot-starter-web依赖,是web开发的起步依赖。这里涉及到很重要的问题就是插件和依赖从maven仓库里也和docker一样很难拉下来。我给IDEA配置好了代理,连接测试通过,但maven此时不会正常走代理,大概又要去单独配置了。最后在所使用的maven文件夹下的settings.xml中配置proxies相关内容即可,同时我还配置了阿里云的镜像仓库,更加方便,经测试能够成功连接上阿里云的仓库。
    父工程中包含版本锁定,约定了各种技术依赖的版本,这样在pom文件引入依赖时就不再需要编写版本信息了,也能保证所使用的各种依赖之间不会发生冲突。同时依赖是可以传递的,诸如spring-boot-starter-web,其本身也是基于多条依赖的,但在项目中引入时,我们可以只需要引入web起步依赖就可以了。
  3. 定义Controller
  4. 编写引导类
    引导类是SpringBoot项目的入口,运行main方法就可以启动项目,主要特征是以Application结尾。
  5. 启动测试

这些过程都可以直接使用SpringInitializer创建项目来完成,了解即可。

配置文件

文件格式:
SpringBoot是基于约定的,很多配置都是有默认值的,若配置文件没有显式给出具体参数的值就会使用默认值。配置文件分为properties和yml两类,properties是键值对样式的,比如port = 8080; yml是冒号表示且子条目是带缩进的,比如port: 8080,yml要更为主流,推荐使用。
yml有对象、数组、纯量三种数据格式。对象就是分行缩进冒号表示的键值对,行内写法需要带大括号;数组,分行使用横杠“-”表示,行内写法需用中括号;纯良就是常量。参数引用采用${name}的方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 对象
spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/mybatis
    username: root
    password: 123456

enterprise:
    name: ljy
    age: 21
    tel: 135445
# 对象不换行写法
spring:
    application: { name: demo }
# 数组
subject: 
    - Java
    - 前端
    - 大数据
# 数组不换行写法
colors: [red, green, blue, orange]
// 纯量
name: ljy
key: value
number: 42
quoted: "some text"

数据读取:
配置文件中的值可通过@Value注解Environment类@ConfigurationProperties注解来获取。@Value注解主要结合${name}来使用;Environment类通过@Autowired注入对象后,调用getProperty方法获取特定属性的值;@ConfigurationProperties结合@Component注解使用,标注在获取配置数据的实体类上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// @Value注解读取配置文件数据
@Value(${name})
private String name;
// Environment对象加载配置信息
@Autowired
private Environment environment;
System.out.println(environment.getProperty("name"));
// @ConfigurationProperties注解
@ConfigurationProperties(prefix = "enterprise")
public class Enterprise {
    private String name; // 和上述yml文件中对应属性实现映射
    private Integer age;
    private String tel;
    ......
}

多环境开发配置:
profile是用于配置多套环境的,对不同环境的同一套参数,可以通过spring.config.active.on-profile标记名称,再用spring.profiles.active来激活不同的参数。此外还可以通过虚拟机启动参数、命令行参数来激活,命令行参数还适用于直接运行jar包。
内部配置文件优先级从高到底为项目根目录/config下的、项目根目录下的、classpath/config下的、classpath目录下的,高优先级会覆盖低优先级的配置。外部配置优先级常用方法从高到底为命令行、外部配置文件等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
spring:
    profiles:
        active: dev # 启用dev环境下的配置
--- #使用---区分不同环境
server:
    port: 8080
spring:
    profiles: dev
---  
server:
    port: 8081
spring:
    profiles: pro
---
server:
    port: 8082
spring:
    profiles: test

# SpringBoot新版本中标记profiles名称的写法
spring:
  config:
    activate:
      on-profile: dev

技术集成

Junit的引入:
用以编写单体测试。主要基于spring-boot-starter-test以及junit两个依赖,针对服务类进行测试,在测试类前使用@Runwith以及@SpringBootTest两个注解,自动注入服务类对象,在测试方法中调用要测试的服务类方法,注意测试方法前要用@Test注释标注。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@RunWith(SpringRunner.class)
@SpringBootTest(classes = DemoApplication.class)
public class DemoApplicationTest {

    @Autowired
    private PersonService personService;

    @Test
    public void testSelect() {
        List<Person> persons = personService.getAllPerson();
        System.out.println(persons);
    }

}

Redis的引入:
(环境待配置)
MyBatis的引入:
主要导入mybatis-spring-boot-startermysql-connector-j两个依赖,除了在yml文件里配置数据源外,还需要构建实体类目录以及映射类目录,比如常见的domain和mapper,如果是采用Mapper接口+XML文件的方式完成SQL查询任务的话,配置文件里还需要配置mapper映射文件路径以及实体包名。

1
2
3
4
5
6
7
8
9
10
11
# 数据库连接配置信息
spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/mybatis
    username: root
    password: 123456
# Mybatis信息配置
mybatis:
  mapper-locations: classpath:mapper/*Mapper.xml # mapper映射文件路径
  type-aliases-package: com.kurisu.demo.domain # 实体类所在包

SpringBoot自动配置原理

  1. Condition功能
    构建项目时我们需要思考一点,SpringBoot是怎么知道要创建哪个Bean的?他怎么知道依赖有没有被导入呢?
    这些主要是依赖Condition功能来实现的,Condition是Spring4.0增加的条件判断功能,通过这个功能可以实现选择性的Bean创建操作,可以为是否创建Bean提供条件判断前提,实际中SpringBoot提供了@ConditionOnClass等注解来实现动态的条件判断。
  2. Enable
    SpringBoot提供了很多Enable开头的注解,这些注解都是用于动态启用某些功能的,其底层原理是通过@Import注解导入一些配置类,实现Bean的动态加载,如果想要获取的一个类不在同一个包下,就必须使用Enable来启用,可以使用@ComponentScan指定包名,@Import指定类名,但这种方式较为不灵活,我们可以自定义Enable注解,这样在之后使用的时候只需要标注注解就可以实现Bean的加载了。
    @Import注解可以用来导入Bean、导入配置类、导入ImportSelector实现类,一般用于加载配置文件中的类、导入ImportBeanDefinitionRegister实现类。
    @EnableAutoConfiguration内部使用@Import(AutoConfigurationImportSelector.class)来加载配置类,配置文件位于META-INF/spring.factories,该配置文件中定义了大量的配置类,当SpringBoot应用启动时,会自动加载这些配置类,初始化Bean,在配置类中使用Condition来控制哪些Bean被初始化。
  3. 自定义starter
    SpringBoot提供的启动类已经非常完善,这一步主要是帮助我们能够更好地了解整个SpringBoot自动配置的原理。以自定义redis-starter为例,分为三步:
    创建redis-spring-boot-autoconfiguration模块;创建redis-spring-boot-starter模块,依赖于redis-spring-boot-autoconfiguration;在redis-spring-boot-autoconfiguration中初始化Jedis的Bean,定义META-INF/spring.factories文件。创建完毕后我们在测试模块中引入自定义的redis-starter依赖,测试是否能获取到Jedis的Bean。

SpringBoot监听:
我们可以实现监听器接口,在项目启动完成时进行一些操作。

  1. ApplicationContextInitializer,需要在/META-INF/spring.factories中配置,用于IOC容器还没准备好之前检测一些资源是否存在。
  2. SpringApplicationRunListener,涵盖了项目全生命周期的方法,需要在/META-INF/spring.factories中配置,还需要给实现类编写构造方法。
  3. CommandLineRunner和ApplicationRunner,两者都是在项目启动后执行重写的run方法,不需要进行配置,启动项目自动运行,可以用来做缓存预热,一般只选一个来用。

SpringBoot项目的构建流程和一些基本的原理就到这里了,对于细节的把控还是需要通过自己从0写一个项目来进行,想去挑一个感兴趣又实用的项目进行学习,但到现在也还没想好应该往哪方面靠。

This post is licensed under CC BY 4.0 by the author.

Trending Tags