spring boot 属性注入会自动注入datasource吗

Spring Boot干货系列:(九)数据存储篇-SQL关系型数据库之MyBatis的使用
Spring Boot干货系列:(九)数据存储篇-SQL关系型数据库之MyBatis的使用
正文项目框架还是跟上一篇一样使用Spring Boot的ace后端模板,你可以基于它来跟着博主一起来调整代码,如果没看过上一篇,那就下载本篇源码研究吧。跟上篇一样先添加基础的依赖和数据源。添加依赖这里需要添加mybatis-spring-boot-starter依赖跟mysql依赖org.mybatis.spring.bootmybatis-spring-boot-starter1.3.0mysqlmysql-connector-java这里不引入spring-boot-starter-jdbc依赖,是由于mybatis-spring-boot-starter中已经包含了此依赖。博主开始整理的时候发现mybatis-spring-boot-starter有新版本了,这里就集成最新的,匹配Spring Boot1.5版本。MyBatis-Spring-Boot-Starter依赖将会提供如下:自动检测现有的DataSource将创建并注册SqlSessionFactory的实例,该实例使用SqlSessionFactoryBean将该DataSource作为输入进行传递将创建并注册从SqlSessionFactory中获取的SqlSessionTemplate的实例。自动扫描您的mappers,将它们链接到SqlSessionTemplate并将其注册到Spring上下文,以便将它们注入到您的bean中。就是说,使用了该Starter之后,只需要定义一个DataSource即可(application.properties中可配置),它会自动创建使用该DataSource的SqlSessionFactoryBean以及SqlSessionTemplate。会自动扫描你的Mappers,连接到SqlSessionTemplate,并注册到Spring上下文中。数据源配置在src/main/resources/application.properties中配置数据源信息。spring.datasource.url = jdbc:mysql://localhost:3306/spring?useUnicode=true&characterEncoding=utf-8spring.datasource.username = rootspring.datasource.password = rootspring.datasource.driver-class-name = com.mysql.jdbc.Driver自定义数据源Spring Boot默认使用tomcat-jdbc数据源,如果你想使用其他的数据源,比如这里使用了阿里巴巴的数据池管理,除了在application.properties配置数据源之外,你应该额外添加以下依赖:com.alibabadruid1.0.19修改Application.java@SpringBootApplicationpublic class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);}@Autowiredprivate E//destroy-method=”close”的作用是当数据库连接不使用的时候,就把该连接重新放到数据池中,方便下次使用调用.@Bean(destroyMethod = “close”)public DataSource dataSource() {DruidDataSource dataSource = new DruidDataSource();dataSource.setUrl(env.getProperty(“spring.datasource.url”));dataSource.setUsername(env.getProperty(“spring.datasource.username”));//用户名dataSource.setPassword(env.getProperty(“spring.datasource.password”));//密码dataSource.setDriverClassName(env.getProperty(“spring.datasource.driver-class-name”));dataSource.setInitialSize(2);//初始化时建立物理连接的个数dataSource.setMaxActive(20);//最大连接池数量dataSource.setMinIdle(0);//最小连接池数量dataSource.setMaxWait(60000);//获取连接时最大等待时间,单位毫秒。dataSource.setValidationQuery(“SELECT 1”);//用来检测连接是否有效的sqldataSource.setTestOnBorrow(false);//申请连接时执行validationQuery检测连接是否有效dataSource.setTestWhileIdle(true);//建议配置为true,不影响性能,并且保证安全性。dataSource.setPoolPreparedStatements(false);//是否缓存preparedStatement,也就是PSCachereturn dataSok这样就算自己配置了一个DataSource,Spring Boot会智能地选择我们自己配置的这个DataSource实例。脚本初始化CREATE DATABASE /!32312 IF NOT EXISTS/spring!40100 DEFAULT CHARACTER SET utf8/;USE;DROP TABLE IF EXISTSlearn_resourceCREATE TABLE(idbigint(20) NOT NULL AUTO_INCREMENT COMMENT ‘ID’,authorvarchar(20) DEFAULT NULL COMMENT ‘作者’,titlevarchar(100) DEFAULT NULL COMMENT ‘描述’,urlvarchar(100) DEFAULT NULL COMMENT ‘地址链接’,PRIMARY KEY ()) ENGINE=MyISAM AUTO_INCREMENT=1029 DEFAULT CHARSET=utf8;insert into,) values (999,’官方SpriongBoot例子’,’官方SpriongBoot例子’,’/spring-projects/spring-boot/tree/master/spring-boot-samples‘);) values (1000,’龙果学院’,’Spring Boot 教程系列学习’,’/article/detail/124661) values (1001,’嘟嘟MD独立博客’,’Spring Boot干货系列’,’http://tengj.top/) values (1002,’后端编程嘟’,’Spring Boot视频教程’,’/m3553/注解方式跟XML配置方式共同的模块编码不管是注解方式还是XML配置的方式,以下代码模块都是一样的实体对象public class LearnResouce {private Lprivate Sprivate Sprivate S// SET和GET方法Controller层/** 教程页面* Created by tengj on .*/@Controller@RequestMapping(“/learn”)public class LearnController {private LearnService learnSprivate Logger logger = LoggerFactory.getLogger(this.getClass());@RequestMapping(&&) public String learn(){ return &learn-resource&; } @RequestMapping(value = &/queryLeanList&,method = RequestMethod.POST,produces=&application/charset=UTF-8&) @ResponseBody public void queryLearnList(HttpServletRequest request ,HttpServletResponse response){ String page = request.getParameter(&page&); // 取得当前页数,注意这是jqgrid自身的参数 String rows = request.getParameter(&rows&); // 取得每页显示行数,,注意这是jqgrid自身的参数 String author = request.getParameter(&author&); String title = request.getParameter(&title&); Map&String,Object& params = new HashMap&String,Object&(); params.put(&page&, page); params.put(&rows&, rows); params.put(&author&, author); params.put(&title&, title); List&LearnResouce& learnList=learnService.queryLearnResouceList(params); PageInfo&LearnResouce& pageInfo =new PageInfo&LearnResouce&(learnList); JSONObject jo=new JSONObject(); jo.put(&rows&, learnList); jo.put(&total&, pageInfo.getPages());//总页数 jo.put(&records&,pageInfo.getTotal());//查询出的总记录数 ServletUtil.createSuccessResponse(200, jo, response); } /** * 新添教程 * @param request * @param response */ @RequestMapping(value = &/add&,method = RequestMethod.POST) public void addLearn(HttpServletRequest request , HttpServletResponse response){ JSONObject result=new JSONObject(); String author = request.getParameter(&author&); String title = request.getParameter(&title&); String url = request.getParameter(&url&); if(StringUtil.isNull(author)){ result.put(&message&,&作者不能为空!&); result.put(&flag&,false); ServletUtil.createSuccessResponse(200, result, response); } if(StringUtil.isNull(title)){ result.put(&message&,&教程名称不能为空!&); result.put(&flag&,false); ServletUtil.createSuccessResponse(200, result, response); } if(StringUtil.isNull(url)){ result.put(&message&,&地址不能为空!&); result.put(&flag&,false); ServletUtil.createSuccessResponse(200, result, response); } LearnResouce learnResouce = new LearnResouce(); learnResouce.setAuthor(author); learnResouce.setTitle(title); learnResouce.setUrl(url); int index=learnService.add(learnResouce); if(index&0){ result.put(&message&,&教程信息添加成功!&); result.put(&flag&,true); }else{ result.put(&message&,&教程信息添加失败!&); result.put(&flag&,false); } ServletUtil.createSuccessResponse(200, result, response); } /** * 修改教程 * @param request * @param response */ @RequestMapping(value = &/update&,method = RequestMethod.POST) public void updateLearn(HttpServletRequest request , HttpServletResponse response){ JSONObject result=new JSONObject(); String id = request.getParameter(&id&); LearnResouce learnResouce=learnService.queryLearnResouceById(Long.valueOf(id)); String author = request.getParameter(&author&); String title = request.getParameter(&title&); String url = request.getParameter(&url&); if(StringUtil.isNull(author)){ result.put(&message&,&作者不能为空!&); result.put(&flag&,false); ServletUtil.createSuccessResponse(200, result, response); } if(StringUtil.isNull(title)){ result.put(&message&,&教程名称不能为空!&); result.put(&flag&,false); ServletUtil.createSuccessResponse(200, result, response); } if(StringUtil.isNull(url)){ result.put(&message&,&地址不能为空!&); result.put(&flag&,false); ServletUtil.createSuccessResponse(200, result, response); } learnResouce.setAuthor(author); learnResouce.setTitle(title); learnResouce.setUrl(url); int index=learnService.update(learnResouce); System.out.println(&修改结果=&+index); if(index&0){ result.put(&message&,&教程信息修改成功!&); result.put(&flag&,true); }else{ result.put(&message&,&教程信息修改失败!&); result.put(&flag&,false); } ServletUtil.createSuccessResponse(200, result, response); } /** * 删除教程 * @param request * @param response */ @RequestMapping(value=&/delete&,method = RequestMethod.POST) @ResponseBody public void deleteUser(HttpServletRequest request ,HttpServletResponse response){ String ids = request.getParameter(&ids&); System.out.println(&ids===&+ids); JSONObject result = new JSONObject(); //删除操作 int index = learnService.deleteByIds(ids.split(&,&)); if(index&0){ result.put(&message&,&教程信息删除成功!&); result.put(&flag&,true); }else{ result.put(&message&,&教程信息删除失败!&); result.put(&flag&,false); } ServletUtil.createSuccessResponse(200, result, response); }Service层package com.dudu.public interface LearnService {int add(LearnResouce learnResouce);int update(LearnResouce learnResouce);int deleteByIds(String[] ids);LearnResouce queryLearnResouceById(Long learnResouce);List queryLearnResouceList(Map
本文仅代表作者观点,不代表百度立场。系作者授权百家号发表,未经许可不得转载。
百家号 最近更新:
简介: CSDN精彩内容推荐
作者最新文章Spring Boot多数据源配置与使用 - 简书
Spring Boot多数据源配置与使用
之前在介绍使用JdbcTemplate和Spring-data-jpa时,都使用了单数据源。在单数据源的情况下,Spring Boot的配置非常简单,只需要在application.properties文件中配置连接参数即可。但是往往随着业务量发展,我们通常会进行数据库拆分或是引入其他数据库,从而我们需要配置多个数据源,下面基于之前的JdbcTemplate和Spring-data-jpa例子分别介绍两种多数据源的配置方式。
多数据源配置
创建一个Spring配置类,定义两个DataSource用来读取application.properties中的不同配置。如下例子中,主数据源配置为spring.datasource.primary开头的配置,第二数据源配置为spring.datasource.secondary开头的配置。
@Configuration
public class DataSourceConfig {
@Bean(name = "primaryDataSource")
@Qualifier("primaryDataSource")
@ConfigurationProperties(prefix="spring.datasource.primary")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
@Bean(name = "secondaryDataSource")
@Qualifier("secondaryDataSource")
@ConfigurationProperties(prefix="spring.datasource.secondary")
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().build();
对应的application.properties配置如下:
spring.datasource.primary.url=jdbc:mysql://localhost:3306/test1
spring.datasource.primary.username=root
spring.datasource.primary.password=root
spring.datasource.primary.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.secondary.url=jdbc:mysql://localhost:3306/test2
spring.datasource.secondary.username=root
spring.datasource.secondary.password=root
spring.datasource.secondary.driver-class-name=com.mysql.jdbc.Driver
JdbcTemplate支持
对JdbcTemplate的支持比较简单,只需要为其注入对应的datasource即可,如下例子,在创建JdbcTemplate的时候分别注入名为primaryDataSource和secondaryDataSource的数据源来区分不同的JdbcTemplate。
@Bean(name = "primaryJdbcTemplate")
public JdbcTemplate primaryJdbcTemplate(
@Qualifier("primaryDataSource") DataSource dataSource) {
return new JdbcTemplate(dataSource);
@Bean(name = "secondaryJdbcTemplate")
public JdbcTemplate secondaryJdbcTemplate(
@Qualifier("secondaryDataSource") DataSource dataSource) {
return new JdbcTemplate(dataSource);
接下来通过测试用例来演示如何使用这两个针对不同数据源的JdbcTemplate。
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(Application.class)
public class ApplicationTests {
@Autowired
@Qualifier("primaryJdbcTemplate")
protected JdbcTemplate jdbcTemplate1;
@Autowired
@Qualifier("secondaryJdbcTemplate")
protected JdbcTemplate jdbcTemplate2;
public void setUp() {
jdbcTemplate1.update("DELETE
jdbcTemplate2.update("DELETE
public void test() throws Exception {
// 往第一个数据源中插入两条数据
jdbcTemplate1.update("insert into user(id,name,age) values(?, ?, ?)", 1, "aaa", 20);
jdbcTemplate1.update("insert into user(id,name,age) values(?, ?, ?)", 2, "bbb", 30);
// 往第二个数据源中插入一条数据,若插入的是第一个数据源,则会主键冲突报错
jdbcTemplate2.update("insert into user(id,name,age) values(?, ?, ?)", 1, "aaa", 20);
// 查一下第一个数据源中是否有两条数据,验证插入是否成功
Assert.assertEquals("2", jdbcTemplate1.queryForObject("select count(1) from user", String.class));
// 查一下第一个数据源中是否有两条数据,验证插入是否成功
Assert.assertEquals("1", jdbcTemplate2.queryForObject("select count(1) from user", String.class));
Spring-data-jpa支持
对于数据源的配置可以沿用上例中DataSourceConfig的实现。
新增对第一数据源的JPA配置,注意两处注释的地方,用于指定数据源对应的Entity实体和Repository定义位置,用@Primary区分主数据源。
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
entityManagerFactoryRef="entityManagerFactoryPrimary",
transactionManagerRef="transactionManagerPrimary",
basePackages= { "com.didispace.domain.p" }) //设置Repository所在位置
public class PrimaryConfig {
@Autowired @Qualifier("primaryDataSource")
private DataSource primaryDataS
@Bean(name = "entityManagerPrimary")
public EntityManager entityManager(EntityManagerFactoryBuilder builder) {
return entityManagerFactoryPrimary(builder).getObject().createEntityManager();
@Bean(name = "entityManagerFactoryPrimary")
public LocalContainerEntityManagerFactoryBean entityManagerFactoryPrimary (EntityManagerFactoryBuilder builder) {
return builder
.dataSource(primaryDataSource)
.properties(getVendorProperties(primaryDataSource))
.packages("com.didispace.domain.p") //设置实体类所在位置
.persistenceUnit("primaryPersistenceUnit")
@Autowired
private JpaProperties jpaP
private Map&String, String& getVendorProperties(DataSource dataSource) {
return jpaProperties.getHibernateProperties(dataSource);
@Bean(name = "transactionManagerPrimary")
public PlatformTransactionManager transactionManagerPrimary(EntityManagerFactoryBuilder builder) {
return new JpaTransactionManager(entityManagerFactoryPrimary(builder).getObject());
新增对第二数据源的JPA配置,内容与第一数据源类似,具体如下:
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
entityManagerFactoryRef="entityManagerFactorySecondary",
transactionManagerRef="transactionManagerSecondary",
basePackages= { "com.didispace.domain.s" }) //设置Repository所在位置
public class SecondaryConfig {
@Autowired @Qualifier("secondaryDataSource")
private DataSource secondaryDataS
@Bean(name = "entityManagerSecondary")
public EntityManager entityManager(EntityManagerFactoryBuilder builder) {
return entityManagerFactorySecondary(builder).getObject().createEntityManager();
@Bean(name = "entityManagerFactorySecondary")
public LocalContainerEntityManagerFactoryBean entityManagerFactorySecondary (EntityManagerFactoryBuilder builder) {
return builder
.dataSource(secondaryDataSource)
.properties(getVendorProperties(secondaryDataSource))
.packages("com.didispace.domain.s") //设置实体类所在位置
.persistenceUnit("secondaryPersistenceUnit")
@Autowired
private JpaProperties jpaP
private Map&String, String& getVendorProperties(DataSource dataSource) {
return jpaProperties.getHibernateProperties(dataSource);
@Bean(name = "transactionManagerSecondary")
PlatformTransactionManager transactionManagerSecondary(EntityManagerFactoryBuilder builder) {
return new JpaTransactionManager(entityManagerFactorySecondary(builder).getObject());
完成了以上配置之后,主数据源的实体和数据访问对象位于:com.didispace.domain.p,次数据源的实体和数据访问接口位于:com.didispace.domain.s。
分别在这两个package下创建各自的实体和数据访问接口
主数据源下,创建User实体和对应的Repository接口
public class User {
@GeneratedValue
@Column(nullable = false)
@Column(nullable = false)
public User(){}
public User(String name, Integer age) {
this.name =
this.age =
// 省略getter、setter
public interface UserRepository extends JpaRepository&User, Long& {
从数据源下,创建Message实体和对应的Repository接口
public class Message {
@GeneratedValue
@Column(nullable = false)
@Column(nullable = false)
public Message(){}
public Message(String name, String content) {
this.name =
this.content =
// 省略getter、setter
public interface MessageRepository extends JpaRepository&Message, Long& {
接下来通过测试用例来验证使用这两个针对不同数据源的配置进行数据操作。
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(Application.class)
public class ApplicationTests {
@Autowired
private UserRepository userR
@Autowired
private MessageRepository messageR
public void test() throws Exception {
userRepository.save(new User("aaa", 10));
userRepository.save(new User("bbb", 20));
userRepository.save(new User("ccc", 30));
userRepository.save(new User("ddd", 40));
userRepository.save(new User("eee", 50));
Assert.assertEquals(5, userRepository.findAll().size());
messageRepository.save(new Message("o1", "aaaaaaaaaa"));
messageRepository.save(new Message("o2", "bbbbbbbbbb"));
messageRepository.save(new Message("o3", "cccccccccc"));
Assert.assertEquals(3, messageRepository.findAll().size());
会一些前端,懂一些后端,做过一些管理,弄过一些运维,搞过一些推广,我是一枚爱折腾的团队万金油……
我的微信:zhaiyongchao1987
微信公众号:didispace
我的博客:
拥有最强Spring Cloud研究者阵容的社区:
Spring技术框架一网打尽!深入Spring Boot:那些注入不了的Spring占位符(${}表达式)
时间: 00:17:04
&&&& 阅读:287
&&&& 评论:
&&&& 收藏:0
标签:&&&&&&&&&&&&&&&&&&&&&&&&&&&
Spring里的占位符
spring里的占位符通常表现的形式是:
&bean id="dataSource" destroy-method="close" class="mons.dbcp.BasicDataSource"&
&property name="url" value="${jdbc.url}"/&
@Configuration
@ImportResource("classpath:/com/acme/properties-config.xml")
public class AppConfig {
@Value("${jdbc.url}")
Spring应用在有时会出现占位符配置没有注入,原因可能是多样的。
本文介绍两种比较复杂的情况。
占位符是在Spring生命周期的什么时候处理的
Spirng在生命周期里关于Bean的处理大概可以分为下面几步:
加载Bean定义(从xml或者从@Import等)
处理BeanFactoryPostProcessor
实例化Bean
处理Bean的property注入
处理BeanPostProcessor
当然这只是比较理想的状态,实际上因为Spring Context在构造时,也需要创建很多内部的Bean,应用在接口实现里也会做自己的各种逻辑,整个流程会非常复杂。
那么占位符(${}表达式)是在什么时候被处理的?
实际上是在org.springframework.context.support.PropertySourcesPlaceholderConfigurer里处理的,它会访问了每一个bean的BeanDefinition,然后做占位符的处理
PropertySourcesPlaceholderConfigurer实现了BeanFactoryPostProcessor接口
PropertySourcesPlaceholderConfigurer的 order是Ordered.LOWEST_PRECEDENCE,也就是最低优先级的
结合上面的Spring的生命周期,如果Bean的创建和使用在PropertySourcesPlaceholderConfigurer之前,那么就有可能出现占位符没有被处理的情况。
例子1:Mybatis 的 MapperScannerConfigurer引起的占位符没有处理
例子代码:
首先应用自己在代码里创建了一个DataSource,其中${db.user}是希望从application.properties里注入的。代码在运行时会打印出user的实际值。
@Configuration
public class MyDataSourceConfig {
@Bean(name = "dataSource1")
public DataSource dataSource1(@Value("${db.user}") String user) {
System.err.println("user: " + user);
JdbcDataSource ds = new JdbcDataSource();
ds.setURL("jdbc:h2:&/test");
ds.setUser(user);
然后应用用代码的方式来初始化mybatis相关的配置,依赖上面创建的DataSource对象
@Configuration
public class MybatisConfig1 {
@Bean(name = "sqlSessionFactory1")
public SqlSessionFactory sqlSessionFactory1(DataSource dataSource1) throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
org.apache.ibatis.session.Configuration ibatisConfiguration = new org.apache.ibatis.session.Configuration();
sqlSessionFactoryBean.setConfiguration(ibatisConfiguration);
sqlSessionFactoryBean.setDataSource(dataSource1);
sqlSessionFactoryBean.setTypeAliasesPackage("sample.mybatis.domain");
return sqlSessionFactoryBean.getObject();
MapperScannerConfigurer mapperScannerConfigurer(SqlSessionFactory sqlSessionFactory1) {
MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactory1");
mapperScannerConfigurer.setBasePackage("sample.mybatis.mapper");
return mapperScannerC
当代码运行时,输出结果是:
user: ${db.user}
为什么会user这个变量没有被注入?
分析下Bean定义,可以发现MapperScannerConfigurer它实现了BeanDefinitionRegistryPostProcessor。这个接口在是Spring扫描Bean定义时会回调的,远早于BeanFactoryPostProcessor。
所以原因是:
MapperScannerConfigurer它实现了BeanDefinitionRegistryPostProcessor,所以它会Spring的早期会被创建
从bean的依赖关系来看,mapperScannerConfigurer依赖了sqlSessionFactory1,sqlSessionFactory1依赖了dataSource1
MyDataSourceConfig里的dataSource1被提前初始化,没有经过PropertySourcesPlaceholderConfigurer的处理,所以@Value("${db.user}") String user&里的占位符没有被处理
要解决这个问题,可以在代码里,显式来处理占位符:
environment.resolvePlaceholders("${db.user}")
package org.mybatis.spring.
import static org.springframework.util.Assert.notN
import java.lang.annotation.A
import java.util.M
import org.apache.ibatis.session.SqlSessionF
import org.mybatis.spring.SqlSessionT
import org.springframework.beans.PropertyV
import org.springframework.beans.PropertyV
import org.springframework.beans.factory.BeanNameA
import org.springframework.beans.factory.InitializingB
import org.springframework.beans.factory.config.BeanD
import org.springframework.beans.factory.config.ConfigurableListableBeanF
import org.springframework.beans.factory.config.PropertyResourceC
import org.springframework.beans.factory.config.TypedStringV
import org.springframework.beans.factory.support.BeanDefinitionR
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostP
import org.springframework.beans.factory.support.BeanNameG
import org.springframework.beans.factory.support.DefaultListableBeanF
import org.springframework.context.ApplicationC
import org.springframework.context.ApplicationContextA
import org.springframework.context.ConfigurableApplicationC
import org.springframework.util.StringU
* BeanDefinitionRegistryPostProcessor that searches recursively starting from a base package for
* interfaces and registers them as {@code MapperFactoryBean}. Note that only interfaces with at
* least one metho concrete classes will be ignored.
* This class was a {code BeanFactoryPostProcessor} until 1.0.1 version. It changed to
* {@code BeanDefinitionRegistryPostProcessor} in 1.0.2. See https://jira.springsource.org/browse/SPR-8269
* for the details.
* The {@code basePackage} property can contain more than one package name, separated by either
* commas or semicolons.
* This class supports filtering the mappers created by either specifying a marker interface or an
* annotation. The {@code annotationClass} property specifies an annotation to search for. The
* {@code markerInterface} property specifies a parent interface to search for. If both properties
* are specified, mappers are added for interfaces that match &em&either&/em& criteria. By default,
* these two properties are null, so all interfaces in the given {@code basePackage} are added as
* mappers.
* This configurer enables autowire for all the beans that it creates so that they are
* automatically autowired with the proper {@code SqlSessionFactory} or {@code SqlSessionTemplate}.
* If there is more than one {@code SqlSessionFactory} in the application, however, autowiring
* cannot be used. In this case you must explicitly specify either an {@code SqlSessionFactory} or
* an {@code SqlSessionTemplate} to use via the &em&bean name&/em& properties. Bean names are used
* rather than actual objects because Spring does not initialize property placeholders until after
* this class is processed.
* Passing in an actual object which may require placeholders (i.e. DB user password) will fail.
* Using bean names defers actual object creation until later in the startup
* process, after all placeholder substituation is completed. However, note that this configurer
* does support property placeholders of its &em&own&/em& properties. The &code&basePackage&/code&
* and bean name properties all support &code&${property}&/code& style substitution.
* Configuration sample:
* &pre class="code"&
&bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"&
&property name="basePackage" value="org.mybatis.spring.sample.mapper" /&
&!-- optional unless there are multiple session factories defined --&
&property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" /&
* @author Hunter Presnall
* @author Eduardo Macarron
* @see MapperFactoryBean
* @see ClassPathMapperScanner
public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
private String baseP
private boolean addToConfig = true;
private SqlSessionFactory sqlSessionF
private SqlSessionTemplate sqlSessionT
private String sqlSessionFactoryBeanN
private String sqlSessionTemplateBeanN
private Class&? extends Annotation& annotationC
private Class&?& markerI
private ApplicationContext applicationC
private String beanN
private boolean processPropertyPlaceH
private BeanNameGenerator nameG
例子2:Spring boot自身实现问题,导致Bean被提前初始化
例子代码:
Spring Boot里提供了@ConditionalOnBean,这个方便用户在不同条件下来创建bean。里面提供了判断是否存在bean上有某个注解的功能。
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnBean {
* The annotation type decorating a bean that should be checked. The condition matches
* when any of the annotations specified is defined on a bean in the
* {@link ApplicationContext}.
* @return the class-level annotation types to check
Class&? extends Annotation&[] annotation() default {};
比如用户自己定义了一个Annotation:
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
然后用下面的写法来创建abc这个bean,意思是当用户显式使用了@MyAnnotation(比如放在main class上),才会创建这个bean。
@Configuration
public class MyAutoConfiguration {
// if comment this line, it will be fine.
@ConditionalOnBean(annotation = { MyAnnotation.class })
public String abc() {
return "abc";
这个功能很好,但是在spring boot 1.4.5 版本之前都有问题,会导致FactoryBean提前初始化。
在例子里,通过xml创建了javaVersion这个bean,想获取到的版本号。这里使用的是spring提供的一个调用static函数创建bean的技巧。
&bean id="sysProps" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean"&
&property name="targetClass" value="java.lang.System" /&
&property name="targetMethod" value="getProperties" /&
&bean id="javaVersion" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean"&
&property name="targetObject" ref="sysProps" /&
&property name="targetMethod" value="getProperty" /&
&property name="arguments" value="${java.version.key}" /&
我们在代码里获取到这个javaVersion,然后打印出来:
@SpringBootApplication
@ImportResource("classpath:/demo.xml")
public class DemoApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(DemoApplication.class, args);
System.err.println(context.getBean("javaVersion"));
在实际运行时,发现javaVersion的值是null。
这个其实是spring boot的锅,要搞清楚这个问题,先要看@ConditionalOnBean的实现。
@ConditionalOnBean实际上是在ConfigurationClassPostProcessor里被处理的,它实现了BeanDefinitionRegistryPostProcessor
BeanDefinitionRegistryPostProcessor是在spring早期被处理的
@ConditionalOnBean的具体处理代码在org.springframework.boot.autoconfigure.condition.OnBeanCondition里
OnBeanCondition在获取bean的Annotation时,调用了beanFactory.getBeanNamesForAnnotation
private String[] getBeanNamesForAnnotation(
ConfigurableListableBeanFactory beanFactory, String type,
ClassLoader classLoader, boolean considerHierarchy) throws LinkageError {
String[] result = NO_BEANS;
@SuppressWarnings("unchecked")
Class&? extends Annotation& typeClass = (Class&? extends Annotation&) ClassUtils
.forName(type, classLoader);
result = beanFactory.getBeanNamesForAnnotation(typeClass);
beanFactory.getBeanNamesForAnnotation&会导致FactoryBean提前初始化,创建出javaVersion里,传入的${java.version.key}没有被处理,值为null。
spring boot 1.4.5 修复了这个问题:
实现spring boot starter要注意不能导致bean提前初始化
用户在实现spring boot starter时,通常会实现Spring的一些接口,比如BeanFactoryPostProcessor接口,在处理时,要注意不能调用类似beanFactory.getBeansOfType,beanFactory.getBeanNamesForAnnotation&这些函数,因为会导致一些bean提前初始化。
而上面有提到PropertySourcesPlaceholderConfigurer的order是最低优先级的,所以用户自己实现的BeanFactoryPostProcessor接口在被回调时很有可能占位符还没有被处理。
对于用户自己定义的@ConfigurationProperties对象的注入,可以用类似下面的代码:
@ConfigurationProperties(prefix = "spring.my")
public class MyProperties {
public static MyProperties buildMyProperties(ConfigurableEnvironment environment) {
MyProperties myProperties = new MyProperties();
if (environment != null) {
MutablePropertySources propertySources = environment.getPropertySources();
new RelaxedDataBinder(myProperties, "spring.my").bind(new PropertySourcesPropertyValues(propertySources));
return myP
占位符(${}表达式)是在PropertySourcesPlaceholderConfigurer里处理的,也就是BeanFactoryPostProcessor接口
spring的生命周期是比较复杂的事情,在实现了一些早期的接口时要小心,不能导致spring bean提前初始化
在早期的接口实现里,如果想要处理占位符,可以利用spring自身的api,比如&environment.resolvePlaceholders("${db.user}")
&http://blog.csdn.net/hengyunabc/article/details/
&标签:&&&&&&&&&&&&&&&&&&&&&&&&&&&
&&国之画&&&& &&&&chrome插件&&
版权所有 京ICP备号-2
迷上了代码!

我要回帖

更多关于 spring boot 依赖注入 的文章

 

随机推荐