2023年2月10日 11:27 周五转载自https://www.jianshu.com/p/4e268828db48(添加小部分笔记)感谢作者!
还没看完
在上篇文章中,我们讲解了MyBatis的启动流程,以及启动过程中涉及到的组件,在本篇文中,我们继续探索SqlSession,SqlSessionFactory,SqlSessionFactoryBuilder的关系。SqlSession作为MyBatis的核心组件,可以说MyBatis的所有操作都是围绕SqlSession来展开的。对SqlSession理解透彻,才能全面掌握MyBatis。
1. SqlSession初识
#
SqlSession在一开始就介绍过是高级接口,类似于JDBC操作的connection对象,它包装了数据库连接,通过这个接口我们可以实现增删改查,提交/回滚事物,关闭连接,获取代理类等操作。SqlSession是个接口,其默认实现是DefaultSqlSession。SqlSession是线程不安全的,每个线程都会有自己唯一的SqlSession,不同线程间调用同一个SqlSession会出现问题,因此在使用完后需要close掉。
![img](img/6aa056bf515cfd3c4bc96542f4b99250_MD5.webp)
SqlSession的方法
2. SqlSession的创建
#
SqlSessionFactoryBuilder的build()方法使用建造者模式创建了SqlSessionFactory接口对象,SqlSessionFactory接口的默认实现是DefaultSqlSessionFactory。SqlSessionFactory使用实例工厂模式来创建SqlSession对象。SqlSession,SqlSessionFactory,SqlSessionFactoryBuilder的关系如下(图画得有点丑…):
![img](img/dda341fb9f426f5b159a42798973cbba_MD5.webp)
类图
DefaultSqlSessionFactory中openSession是有两种方法一种是openSessionFromDataSource,另一种是openSessionFromConnection。这两种是什么区别呢?从字面意义上将,一种是从数据源中获取SqlSession对象,一种是由已有连接获取SqlSession。SqlSession实际是对数据库连接的一层包装,数据库连接是个珍贵的资源,如果频繁的创建销毁将会影响吞吐量,因此使用数据库连接池化技术就可以复用数据库连接了。因此openSessionFromDataSource会从数据库连接池中获取一个连接,然后包装成一个SqlSession对像。openSessionFromConnection则是直接包装已有的连接并返回SqlSession对像。
openSessionFromDataSource 主要经历了以下几步:
- 从获取configuration中获取Environment对象,Environment包含了数据库配置
- 从Environment获取DataSource数据源
- 从DataSource数据源中获取Connection连接对象
- 从DataSource数据源中获取TransactionFactory事物工厂
- 从TransactionFactory中创建事物Transaction对象
- 创建Executor对象
- 包装configuration和Executor对象成DefaultSqlSession对象
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {
try {
boolean autoCommit;
try {
autoCommit = connection.getAutoCommit();
} catch (SQLException e) {
// Failover to true, as most poor drivers
// or databases won't support transactions
autoCommit = true;
}
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
final Transaction tx = transactionFactory.newTransaction(connection);
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
3. SqlSession的使用
#
SqlSession 获取成功后,我们就可以使用其中的方法了,比如直接使用SqlSession发送sql语句,或者通过mapper映射文件的方式来使用,在上两篇文章中我们都是通过mapper映射文件来使用的,接下来就介绍第一种,直接使用SqlSession发送sql语句。
...
2023年2月10日 11:04 周五转载自https://www.jianshu.com/p/7d6b891180a3(添加小部分笔记)感谢作者!
在上篇文章中,我们举了一个例子如何使用MyBatis,但是对其中dao层,entity层,mapper层间的关系不得而知,从此篇文章开始,笔者将从MyBatis的启动流程着手,真正的开始研究MyBatis源码了。
1. MyBatis启动代码示例
#
在上篇文章中,介绍了MyBatis的相关配置和各层代码编写,本文将以下代码展开描述和介绍MyBatis的启动流程,并简略的介绍各个模块的作用,各个模块的细节部分将在其它文章中呈现。
回顾下上文中使用mybatis的部分代码,包括七步。每步虽然都是一行代码,但是隐藏了很多细节。接下来我们将围绕这起步展开了解。
@Slf4j
public class MyBatisBootStrap {
public static void main(String[] args) {
try {
// 1. 读取配置
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
// 2. 创建SqlSessionFactory工厂
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 3. 获取sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 4. 获取Mapper
TTestUserMapper userMapper = sqlSession.getMapper(TTestUserMapper.class);
// 5. 执行接口方法
TTestUser userInfo = userMapper.selectByPrimaryKey(16L);
System.out.println("userInfo = " + JSONUtil.toJsonStr(userInfo));
// 6. 提交事物
sqlSession.commit();
// 7. 关闭资源
sqlSession.close();
inputStream.close();
} catch (Exception e){
log.error(e.getMessage(), e);
}
}
}
2. 读取配置
#
// 1. 读取配置
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
在mybatis-config.xml中我们配置了属性,环境,映射文件路径等,其实不仅可以配置以上内容,还可以配置插件,反射工厂,类型处理器等等其它内容。在启动流程中的第一步我们就需要读取这个配置文件,并获取一个输入流为下一步解析配置文件作准备。
...
2023年2月10日 08:54 周五转载自https://www.jianshu.com/p/ada025f97a07(添加小部分笔记)感谢作者!
作为Java码农,无论在面试中,还是在工作中都会遇到MyBatis的相关问题。笔者从大学开始就接触MyBatis,到现在为止都是会用,知道怎么配置,怎么编写xml,但是不知道Mybatis核心原理,一遇到问题就复制错误信息百度解决。为了改变这种境地,鼓起勇气开始下定决心阅读MyBatis源码,并开始记录阅读过程,希望和大家分享。
1. 初识MyBatis
#
还记得当初接触MyBatis时,觉得要配置很多,而且sql要单独写在xml中,相比Hibernate来说简直不太友好,直到后来出现了复杂的业务需求,需要编写相应的复杂的sql,此时用Hibernate反而更加麻烦了,用MyBatis是真香了。因此笔者对MyBatis的第一印象就是将业务关注的sql和java代码进行了解耦,在业务复杂变化的时候,相应的数据库操作需要相应进行修改,如果通过java代码构建操作数据逻辑,这不断变动的需求对程序员的耐心是极大的考验。如果将sql统一的维护在一个文件里,java代码用接口定义,在需求变动时,只用改相应的sql,从而减少了修改量,提高开发效率。以上也是经常在面试中经常问到的Hibernate和MyBatis间的区别一点。
切到正题,Mybatis是什么呢?
Mybatis SQL 映射框架使得一个面向对象构建的应用程序去访问一个关系型数据库变得更容易。MyBatis使用XML描述符或注解将对象与存储过程或SQL语句耦合。与对象关系映射工具相比,简单性是MyBatis数据映射器的最大优势。
以上是Mybatis的官方解释,其中“映射”,“面向对象”,“关系型”,“xml”等等都是Mybatis的关键词,也是我们了解了Mybatis原理后,会恍然大悟的地方。笔者现在不详述这些概念,在最后总结的时候再进行详述。我们只要知道Mybatis为我们操作数据库提供了很大的便捷。
2. 源码下载
#
这里建议使用maven即可,在pom.xml添加以下依赖
<dependencies>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.32</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<!--这里还添加了一些辅助的依赖-->
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
</dependency>
<!--日志模块-->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.17.1</version>
</dependency>
</dependencies>
然后在ExternalLibraries 的mybatis:3.5.6里找到,就能看到目录结构 ,随便找一个进去 idea右上角会出现DownloadSource之类的字样 ,点击即可
![ly-20241212142014144](img/ly-20241212142014144.png)
我们首先要从github上下载源码,
仓库地址,然后在IDEA中clone代码
![img](img/ly-20241212142014403.png)
在打开中的IDEA中,选择vsc -> get from version control -> 复制刚才的地址
![img](img/ly-20241212142014541.png)
image.png
点击clone即可
![img](img/ly-20241212142014681.png)
...
2023年2月9日 16:34 周四转载自https://github.com/Snailclimb/JavaGuide(添加小部分笔记)感谢作者!
部分疑问参考自
https://blog.csdn.net/Gherbirthday0916 感谢作者!
#{} 和 ${} 的区别是什么?
#
注:这道题是面试官面试我同事的。
答:
${}
是 Properties 文件中的变量占位符,它可以用于标签属性值和 sql 内部,属于静态文本替换,比如${driver}会被静态替换为com.mysql.jdbc. Driver
。#{}
是 sql 的参数占位符,MyBatis 会将 sql 中的#{}
替换为? 号,在 sql 执行前会使用 PreparedStatement 的参数设置方法,按序给 sql 的? 号占位符设置参数值,比如 ps.setInt(0, parameterValue),#{item.name}
的取值方式为使用反射从参数对象中获取 item 对象的 name 属性值,相当于 param.getItem().getName()
。 [这里用到了反射]
在底层构造完整SQL语句时,MyBatis的两种传参方式所采取的方式不同。#{Parameter}
采用预编译的方式构造SQL,避免了 SQL注入 的产生。而**${Parameter}
采用拼接的方式构造SQL,在对用户输入过滤不严格**的前提下,此处很可能存在SQL注入
xml 映射文件中,除了常见的 select、insert、update、delete 标签之外,还有哪些标签?
#
注:这道题是京东面试官面试我时问的。
答:还有很多其他的标签, <resultMap>
、 <parameterMap>
、 <sql>
、 <include>
、 <selectKey>
,加上动态 sql 的 9 个标签, trim|where|set|foreach|if|choose|when|otherwise|bind
等,其中 <sql>
为 sql 片段标签,通过 <include>
标签引入 sql 片段, <selectKey>
为不支持自增的主键生成策略标签。
...
2023年2月9日 15:38 周四两个测试方向
#
方向1:两个maven项目
#
详见git上的 conditional_on_class_main 项目以及 conditional_on_class2 项目
基础maven项目 conditional_on_class2
pom文件
<?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>
<groupId>org.example</groupId>
<artifactId>conditional_on_class_2</artifactId>
<version>1.0-SNAPSHOT</version>
</project>
java类
package com;
public class LyReferenceImpl {
public String sayWord() {
return "hello one";
}
}
简单的SpringBoot项目 conditional_on_class_main
<!--pom文件-->
<?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>
<groupId>org.example</groupId>
<artifactId>conditional_on_class_main</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.8</version>
</parent>
<dependencies>
<!--把1配置的bean引用进来-->
<dependency>
<groupId>org.example</groupId>
<artifactId>conditional_on_class_2</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<!-- 默认会将conditional_on_class_2 打包进去,现在会配置SayExist
如果放开注释,那么会配置SayNotExist-->
<!--<dependency>
<groupId>org.example</groupId>
<artifactId>conditional_on_class_2</artifactId>
</dependency>-->
</excludes>
<jvmArguments>-Dfile.encoding=UTF-8</jvmArguments>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal><!--可以把依赖的包都打包到生成的Jar包中 -->
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
//两个配置类
//配置类1
package com.config;
import com.service.ISay;
import com.service.SayExist;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
//不要放在方法里面,否则会报错"java.lang.ArrayStoreException: sun.reflect.annotation.TypeNotPresentExceptionProxy"
@ConditionalOnClass(value = com.LyReferenceImpl.class)
public class ExistConfiguration {
@Bean
public ISay getISay1(){
return new SayExist();
}
}
//配置类2
package com.config;
import com.service.ISay;
import com.service.SayNotExist;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConditionalOnMissingClass("com.LyReferenceImpl")
public class NotExistConfiguration {
@Bean
public ISay getISay1(){
return new SayNotExist();
}
}
方向2:3个maven项目(建议用这个理解)
#
注意,这里可能还漏了一个问题,那就是 这个conditional_on_class1 的configuration之所以能够被自动装配,是因为和 conditional_on_class_main1的Application类是同一个包,所以不用特殊处理。如果是其他包名的话,那么是需要用到spring boot的自动装配机制的:在conditional_on_class1 工程的 resources 包下创建META-INF/spring.factories
,并写上Config类的全类名
...
2023年2月9日 10:37 周四转载自https://github.com/Snailclimb/JavaGuide(添加小部分笔记)感谢作者!
每次问到 Spring Boot, 面试官非常喜欢问这个问题:“讲述一下 SpringBoot 自动装配原理?”。
我觉得我们可以从以下几个方面回答:
- 什么是 SpringBoot 自动装配?
- SpringBoot 是如何实现自动装配的?如何实现按需加载?
- 如何实现一个 Starter?
篇幅问题,这篇文章并没有深入,小伙伴们也可以直接使用 debug 的方式去看看 SpringBoot 自动装配部分的源代码。
前言
#
使用过 Spring 的小伙伴,一定有被 XML 配置统治的恐惧。即使 Spring 后面引入了基于注解的配置,我们在开启某些 Spring 特性或者引入第三方依赖的时候,还是需要用 XML 或 Java 进行显式配置。
举个例子。没有 Spring Boot 的时候,我们写一个 RestFul Web 服务,还首先需要进行如下配置。
@Configuration
public class RESTConfiguration
{
@Bean
public View jsonTemplate() {
MappingJackson2JsonView view = new MappingJackson2JsonView();
view.setPrettyPrint(true);
return view;
}
@Bean
public ViewResolver viewResolver() {
return new BeanNameViewResolver();
}
}
spring-servlet.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context/ http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc/ http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<context:component-scan base-package="com.howtodoinjava.demo" />
<mvc:annotation-driven />
<!-- JSON Support -->
<bean name="viewResolver" class="org.springframework.web.servlet.view.BeanNameViewResolver"/>
<bean name="jsonTemplate" class="org.springframework.web.servlet.view.json.MappingJackson2JsonView"/>
</beans>
但是,Spring Boot 项目,我们只需要添加相关依赖,无需配置,通过启动下面的 main
方法即可。
...
2023年2月8日 20:18 周三转载自https://github.com/Snailclimb/JavaGuide(添加小部分笔记)感谢作者!
“JDK 中用到了哪些设计模式? Spring 中用到了哪些设计模式? ”这两个问题,在面试中比较常见。
我在网上搜索了一下关于 Spring 中设计模式的讲解几乎都是千篇一律,而且大部分都年代久远。所以,花了几天时间自己总结了一下。
由于我的个人能力有限,文中如有任何错误各位都可以指出。另外,文章篇幅有限,对于设计模式以及一些源码的解读我只是一笔带过,这篇文章的主要目的是回顾一下 Spring 中的设计模式。
控制反转(IoC)和依赖注入(DI)
#
IoC(Inversion of Control,控制反转) 是 Spring 中一个非常非常重要的概念,它不是什么技术,而是一种解耦的设计思想。IoC 的主要目的是借助于“第三方”(Spring 中的 IoC 容器) 实现具有依赖关系的对象之间的解耦(IOC 容器管理对象,你只管使用即可),从而降低代码之间的耦合度。
IoC 是一个原则,而不是一个模式,以下模式(但不限于)实现了 IoC 原则。
![ioc-patterns](img/ly-20241212142011328.jpg)
Spring IoC 容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。 IoC 容器负责创建对象,将对象连接在一起,配置这些对象,并从创建中处理这些对象的整个生命周期,直到它们被完全销毁。
在实际项目中一个 Service 类如果有几百甚至上千个类作为它的底层,我们需要实例化这个 Service,你可能要每次都要搞清这个 Service 所有底层类的构造函数,这可能会把人逼疯。如果利用 IOC 的话,你只需要配置好,然后在需要的地方引用就行了,这大大增加了项目的可维护性且降低了开发难度。
关于 Spring IOC 的理解,推荐看这一下知乎的一个回答:https://www.zhihu.com/question/23277575/answer/169698662 ,非常不错。
控制反转怎么理解呢? 举个例子:"对象 a 依赖了对象 b,当对象 a 需要使用 对象 b 的时候必须自己去创建。但是当系统引入了 IOC 容器后, 对象 a 和对象 b 之前就失去了直接的联系。这个时候,当对象 a 需要使用 对象 b 的时候, 我们可以指定 IOC 容器去创建一个对象 b 注入到对象 a 中"。 对象 a 获得依赖对象 b 的过程,由主动行为变为了被动行为,控制权反转,这就是控制反转名字的由来。
...
2023年2月8日 16:08 周三转载自https://github.com/Snailclimb/JavaGuide(添加小部分笔记)感谢作者!
前段时间答应读者的 Spring 事务 分析总结终于来了。这部分内容比较重要,不论是对于工作还是面试,但是网上比较好的参考资料比较少。
什么是事务?
#
事务是逻辑上的一组操作,要么都执行,要么都不执行。
相信大家应该都能背上面这句话了,下面我结合我们日常的真实开发来谈一谈。
我们系统的每个业务方法可能包括了多个原子性的数据库操作,比如下面的 savePerson()
方法中就有两个原子性的数据库操作。这些原子性的数据库操作是有依赖的,它们要么都执行,要不就都不执行。
public void savePerson() {
personDao.save(person);
personDetailDao.save(personDetail);
}
另外,需要格外注意的是:事务能否生效数据库引擎是否支持事务是关键。比如常用的 MySQL 数据库默认使用支持事务的 innodb
引擎。但是,如果把数据库引擎变为 myisam
,那么程序也就不再支持事务了!
事务最经典也经常被拿出来说例子就是转账了。假如小明要给小红转账 1000 元,这个转账会涉及到两个关键操作就是:
- 将小明的余额减少 1000 元。
- 将小红的余额增加 1000 元。
万一在这两个操作之间突然出现错误比如银行系统崩溃或者网络故障,导致小明余额减少而小红的余额没有增加,这样就不对了。事务就是保证这两个关键操作要么都成功,要么都要失败。
public class OrdersService {
private AccountDao accountDao;
public void setOrdersDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
@Transactional(propagation = Propagation.REQUIRED,
isolation = Isolation.DEFAULT, readOnly = false, timeout = -1)
public void accountMoney() {
//小红账户多1000
accountDao.addMoney(1000,xiaohong);
//模拟突然出现的异常,比如银行中可能为突然停电等等
//如果没有配置事务管理的话会造成,小红账户多了1000而小明账户没有少钱
int i = 10 / 0;
//小王账户少1000
accountDao.reduceMoney(1000,xiaoming);
}
}
另外,数据库事务的 ACID 四大特性是事务的基础,下面简单来了解一下。
...
2023年2月8日 14:56 周三转载自https://github.com/Snailclimb/JavaGuide(添加小部分笔记)感谢作者!
0.前言
#
可以毫不夸张地说,这篇文章介绍的 Spring/SpringBoot 常用注解基本已经涵盖你工作中遇到的大部分常用的场景。对于每一个注解我都说了具体用法,掌握搞懂,使用 SpringBoot 来开发项目基本没啥大问题了!
为什么要写这篇文章?
最近看到网上有一篇关于 SpringBoot 常用注解的文章被转载的比较多,我看了文章内容之后属实觉得质量有点低,并且有点会误导没有太多实际使用经验的人(这些人又占据了大多数)。所以,自己索性花了大概 两天时间简单总结一下了。
因为我个人的能力和精力有限,如果有任何不对或者需要完善的地方,请帮忙指出!Guide 哥感激不尽!
1. @SpringBootApplication
#
这里先单独拎出@SpringBootApplication
注解说一下,虽然我们一般不会主动去使用它。
Guide 哥:这个注解是 Spring Boot 项目的基石,创建 SpringBoot 项目之后会默认在主类加上。
@SpringBootApplication
public class SpringSecurityJwtGuideApplication {
public static void main(java.lang.String[] args) {
SpringApplication.run(SpringSecurityJwtGuideApplication.class, args);
}
}
我们可以把 @SpringBootApplication
看作是 @Configuration
、@EnableAutoConfiguration
、@ComponentScan
注解的集合。
package org.springframework.boot.autoconfigure;
@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 {
......
}
package org.springframework.boot;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}
根据 SpringBoot 官网,这三个注解的作用分别是:
...