本节主要讲解SpringBoot的自动配置
回想一下,在用原生Spring aop功能的时候,需要哪些配置?
引入aspectjweaver jar包
定义切面
配置切面
开启aop功能
为什么在SpringBoot中,只需要引入spring-boot-starter-aop就可以直接使用aop功能?
那是因为SpringBoot定义了AOP配置类,帮我们完成了这些工作。
在spring-boot-auto-configure jar包中有很多SpringBoot帮我们配置好的类,其中就有AopAutoConfiguration,提供自动开启aop等功能
现在来写一个简易版的Springboot自动配置
当我们新增了AopConfiguration配置类后,需要在MySpringBootApplication中显示import该类,如果AutoConfiguration配置类很多的情况,就要在import标签上面引入很多类。
利用ImportSelector接口,可以将需要加载的类在selectImports方法中,返回该类的全限定名称。
public class MyImportSelector implements DeferredImportSelector { @Override public String[] selectImports(AnnotationMetadata importingClassMetadata) { return new String[0]; } }
在该方法中,可以显示的将需要引入的配置类全路径全部加载一个数组中,然后返回,再通过@MySpringBootApplication Import 该MyImportSelector类就可以加载MyImportSelector中返回的类。
这样实现的弊端,
在代码中写死加载的类,需要新增时,则需要动代码。
无法通过外部配置来动态加载所需要的bean。
这里我们利用java spi机制来实现动态加载
Java SPI(Service Provider Interface)是Java官方提供的一种服务发现机制,它允许在运行时动态地加载实现特定接口的类,而不需要在代码中显式地指定该类,从而实现解耦和灵活性。
定义一个特定的接口类 AutoConfiguration
在resource文件夹下新建META-INF/services文件夹,在该services文件夹下新建AutoConfiguration全限定名的文件。
在AutoConfiguration全限定名的文件下配置需要加载的类全限定名称。
在MyImportSelector中实现加载java spi机制的类]
将@MySpringBootApplication 中的Import标签改为引入MyImportSelector
AutoConfiguration接口定义
package cn.axj.springboot.my.config; /** * 该类仅是一个标识作用 */ public interface AutoConfiguration { }
新建
AutoConfiguration文件,cn.axj.springboot.my.config.AutoConfiguration
, AutoConfiguration中配置如下
cn.axj.springboot.my.config.WebServerAutoConfiguration cn.axj.springboot.my.config.AopAutoConfiguration
此时WebServerAutoConfiguration和AopAutoConfiguration需要实现AutoConfiguration
MyImportSelector实现
package cn.axj.springboot.my.config; import org.springframework.context.annotation.DeferredImportSelector; import org.springframework.core.type.AnnotationMetadata; import java.util.ArrayList; import java.util.List; import java.util.ServiceLoader; public class MyImportSelector implements DeferredImportSelector { @Override public String[] selectImports(AnnotationMetadata importingClassMetadata) { /** * 主要这里加载的是所有命名为cn.axj.springboot.my.config.AutoConfiguration的文件下的定义的类,不仅仅指该spring-boot jar包. * 其他jar包只要在resources目录下定义了AutoConfiguration的类,都会被加载进来。 * 这里可以提供动态扩展能力。 */ ServiceLoader<AutoConfiguration> serviceLoader = ServiceLoader.load(AutoConfiguration.class); List<String> list = new ArrayList<>(); for (AutoConfiguration autoConfiguration : serviceLoader) { list.add(autoConfiguration.getClass().getName()); } return list.toArray(new String[0]); } }
MySpringBootApplication中改造
@Documented @Retention(RetentionPolicy.RUNTIME) @Configuration @ComponentScan @Target(ElementType.TYPE) @Import(MyImportSelector.class) public @interface MySpringBootApplication { }
当前结构图
启动user-service模块,并在MySpringApplication 中容器创建完成后,打开debug。
可以看到AopAutoConfiguration 和 WebServerAutoConfiguration 均加载到了容器中。
当其他三方组件也想整合进springboot怎么办?由于其他组件无法获取到AutoConfiguration接口,所以需要将AutoConfiguration接口提出来,专门弄一个jar包供三方组件使用。
新建模块 my-spring-boot-configuration,并将AutoConfiguration类迁移过去。
在my-spring-boot模块中引入 my-spring-boot-configuration
项目结构如上
下面来构建mybatis 整合springboot的jar包
spring整合mybatis 主要是整合SqlSessionFactoryBean,将这个bean交给Spring管理
SqlSessionFactoryBean 主要设置DataSource,MapperLocation,ConfigLocaltion等信息。
新建my-mybatis-spring-boot-starter模块
引入依赖jar包
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>cn.axj</groupId> <artifactId>spring-boot-base</artifactId> <version>1.0-SNAPSHOT</version> </parent> <artifactId>my-mybatis-spring-boot-starter</artifactId> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <!-- MyBatis --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.9</version> </dependency> <!-- MyBatis-Spring 整合包 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.4</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.18</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.19</version> </dependency> <dependency> <groupId>cn.axj</groupId> <artifactId>my-spring-boot-configuration</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.3.18</version> </dependency> </dependencies> </project>
创建自动配置的MybatisAutoConfiguration
类
package cn.axj.mybatis.springboot.config; import cn.axj.springboot.my.config.AutoConfiguration; import com.alibaba.druid.pool.DruidDataSource; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.mapper.MapperScannerConfigurer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import javax.sql.DataSource; import java.io.IOException; @Configuration public class MyBatisAutoConfiguration implements AutoConfiguration { @Bean public DataSource dataSource(){ DruidDataSource druidDataSource = new DruidDataSource(); druidDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); druidDataSource.setUsername("root"); druidDataSource.setPassword("123456"); druidDataSource.setUrl("jdbc:mysql://192.168.56.102:3306/springtest?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC"); return druidDataSource; } @Bean public SqlSessionFactoryBean sqlSessionFactory() throws IOException { SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); PathMatchingResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver(); sqlSessionFactoryBean.setDataSource(dataSource()); sqlSessionFactoryBean.setConfigLocation(resourcePatternResolver.getResource("classpath:mybatis-config.xml")); sqlSessionFactoryBean.setMapperLocations(resourcePatternResolver.getResources("classpath:mapper/*Mapper.xml")); return sqlSessionFactoryBean; } }
为了简便,将数据源这些信息写死,这些信息可以提供配置类,给用户使用。
- 在resources目录下创建mybatis-config.xml
<?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> </configuration>
- 创建自动配置的文件路径META-INF/services,并创建文件
cn.axj.springboot.my.config.AutoConfiguration
,并将MyBatisAutoConfiguration的全限定名称配置进去
cn.axj.mybatis.springboot.config.MyBatisAutoConfiguration
下面开始测试
在user-service模块中引入my-mybatis-spring-boot-starter,并引入mysql连接包
<dependency> <groupId>cn.axj</groupId> <artifactId>my-mybatis-spring-boot-starter</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.33</version> </dependency>
- 创建Model包,并创建User
package cn.axj.user.model; public class User { private Integer id; private String username; ...省略get set 方法 @Override public String toString() { return "User{" + "id=" + id + ", username='" + username + '\'' + '}'; } }
- 创建mapper包,并新增UserMapper
package cn.axj.user.mapper; import cn.axj.user.model.User; import org.apache.ibatis.annotations.Mapper; @Mapper public interface UserMapper { User selectById(Integer id); }
- 在resources目录下创建mapper文件,并创建UserMapper.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="cn.axj.user.mapper.UserMapper"> <select id="selectById" resultType="cn.axj.user.model.User"> select id,username from user where id = #{id} </select> </mapper>
- 改造UserService,引入UserMapper查询User
package cn.axj.user.service; import cn.axj.user.mapper.UserMapper; import cn.axj.user.model.User; import org.springframework.stereotype.Service; import javax.annotation.Resource; @Service public class UserService { @Resource UserMapper userMapper; public User test(Integer id){ User user = userMapper.selectById(id); return user; } }
- 改造TestController,查询返回User对象
package cn.axj.user.controller; import cn.axj.user.service.UserService; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; @RestController public class TestController { @Resource private UserService userService; @GetMapping(value = "/test") public String test(Integer id){ return userService.test(id).toString(); } }
这里由于没有配置HttpMessageConverter,无法将对象转换成字符串形式,所以用了toString()方法。
6.在UserApplication主类上,新增@MapperScan(“cn.axj.user.mapper”),扫描mapper文件。
这个问题,我在MyBatisAutoConfiguration中配置MapperScannerConfigurer,但是无法生效,mybatis不会去扫描响应的包,并生成代理对象,不知道什么原因,但是加在主类上就可以?难道mybatis将SqlSessionFactoryBean交给Spring容器后,就不自己扫描了吗?
项目结构图如下
至此,启动项目。这里记得引入默认的tomcat 容器,不然会无法启动。
通过浏览器访问 localhost:8080/test?id=1 或者 id = 2
可以看到整合mybatis成功,成功返回数据。