目录
@SpringBootApplication 背后到底是什么?
@Configuration和@Component的区别是什么?
@Controller、@Service、@Repository、@Component
面试题:将一个类声明为 Spring 的 bean 的注解有哪些?
当声明式事务 @Transactional 遇到以下场景时,事务会失效:
启动类相关注解
项目中的启动类
@SpringBootApplication 背后到底是什么?
@SpringBootApplication注解实际上是SpringBoot提供的一个复合注解,看源码:
很清楚,其是一个合成体,但其中最重要的三个注解分别是:
-
@SpringBootConfiguration
-
@EnableAutoConfiguration
-
@ComponentScan
如果不怕麻烦,在 SpringBoot 应用的启动类上用这个三个注解代替@SpringBootApplication 注解发现也是没问题的:
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan
public class TestSpringBootApplication {
...
}
@SpringBootConfiguration
这说明 @SpringBootConfiguration 也是来源于 @Configuration,二者功能都是将当前类标注为配置类,并将当前类里以 @Bean 注解标记的方法的实例注入到srping容器中,实例名即为方法名。
至于@Configuration,作用是配置Spring容器,也即 JavaConfig 形式的 Spring IoC 容器的配置类所使用。
@EnableAutoConfiguration
@EnableAutoConfiguration 注解启用自动配置,其可以帮助 SpringBoot 应用将所有符合条件的 @Configuration 配置都加载到当前 IoC 容器之中,可以简要用图形示意如下:
从宏观一点的角度 概括总结 上述这一过程那就是:
从 ClassPath下扫描所有的 META-INF/spring.factories 配置文件,并将spring.factories 文件中的 EnableAutoConfiguration 对应的配置项通过反射机制实例化为对应标注了 @Configuration 的形式的IoC容器配置类,然后注入IoC容器。
@ComponentScan
@ComponentScan 对应于XML配置形式中的 <context:component-scan>,用于将一些标注了特定注解的bean定义批量采集注册到Spring的IoC容器之中,这些特定的注解大致包括:
- @Controller
- @Entity
- @Component
- @Service
- @Repository
等等
对于该注解,还可以通过 basePackages 属性来更细粒度的控制该注解的自动扫描范围,比如:
@ComponentScan(basePackages = {"cn.codesheep.controller","cn.codesheep.entity"})
此部分参考此文章https://www.jianshu.com/p/9dc7a385d19e --@SpringBootApplication注解背后的三体结构探秘
此部分参考文章:
一个面试题引起的SpringBoot启动解析 - 掘金-----很不错
Spring容器启动流程(源码解读) - 掘金---果然很源码,看着很懵
SpringBoot 应用程序启动过程探秘 - 掘金---细致总结
SpringBoot 中启动 Tomcat 流程 - 掘金---Tomcat相关
从Spring启动过程来理解IoC、AOP和bean的生命周期 - 掘金----宏观学习
@Configuration
由于上面的@SpringBootConfiguration注解底层主要使用的是@Configuration,那么,再来深入理解下@Configuration。
JavaConfig形式的Spring Ioc容器的配置类使用的那个@Configuration,SpringBoot社区推荐使用基于JavaConfig的配置形式,所以,这里的启动类标注了@Configuration之后,本身其实也是一个IoC容器的配置类。
@Configuration的主要目的是搭配@Bean注解替代XML配置文件来向Spring容器注入Bean
任何一个标注了@Configuration的Java类定义都是一个JavaConfig配置类。
任何一个标注了@Bean的方法,其返回值将作为一个bean定义注册到Spring的IoC容器,方法名将默认成该bean定义的id。
可以看到@Configuration的底层是@Component,但是两者又有不同
@Configuration和@Component的区别是什么?
这两个注解都是配置类注解,作用于类上,申明该类为组件。不同之处在于:
1、@Component是一个元注解,可以注解其他类注解。@Configuration注解里面也是被@Component注解修饰的。
2、bean设置的类属性不同。
- 如果是 @Configuration 并且属性 proxyBeanMethods 为 true(默认的),则为 full
- 如果是 @Component @ComponentScan @Import @ImportSource 则为 lite
3、返回bean实例不同。
- @Configuration 注解修饰的类,并且该注解中的 proxyBeanMethods 属性的值为 true(默认的),则会使用cglib动态代理,为这个 bean 创建一个代理类,该代理类会拦截所有被 @Bean 修饰的方法,在拦截的方法逻辑中,会从容器中返回所需要的单例对象。
- @Component 注解修饰的类,则不会为这个 bean 创建一个代理类。 那么我们就会直接执行用户的方法,所以每次都会返回一个新的对象。
Spring注解中@Configuration、@Component、@Bean的用法区别 - 掘金
此部分参考的好文
一个面试题引起的SpringBoot启动解析 - 掘金-----有一部分参考
原创 | 我被面试官给虐懵了,竟然是因为我不懂Spring中的@Configuration - 掘金----这篇最棒
Spring注解中@Configuration、@Component、@Bean的用法区别 - 掘金
Spring中@Component和@Configuration的区别 - 掘金
SpringApplication.run做了哪些事?
SpringApplication.run
一共做了两件事,分别是
-
创建
SpringApplication
对象 -
利用创建好的
SpringApplication
对象,调用run
方法
1.创建SpringApplication
对象
这个类中封装了程序的入口
2.调用run
方法
【肥朝】面试官问我,SpringApplication.run做了哪些事? - 掘金-----简单理解版
SpringBoot 应用程序启动过程探秘 - 掘金----深入理解版
总结:
SpringApplication.run一共做了两件事,一件是创建SpringApplication对象,在该对象初始化时,找到配置的事件监听器,并保存起来.第二件事就是运行run方法,此时会将刚才保存的事件监听器根据当前时机触发不同的事件,比如容器初始化,容器创建完成等.同时也会刷新IoC容器,进行组件的扫描、创建、加载等工作。
@Bean
异常处理相关注解
为什么要用统一异常接收?
通常在service层抛异常,涉及到事务时不会进行try-catch,需要在controller里处理异常。即使能够进行try-catch的地方也需要统一返回格式,造成重复代码很多,可读性比较差。所以在项目中进行统一异常处理;具体做法是:直接在service层抛出异常,controller中返回正常的结果,当将异常抛到controller时,由标注了@ControllerAdvice的类对异常进行处理(需要配合@ExceptionHandler使用)。可以对异常进行统一处理,规定返回的json格式或是跳转到一个错误页面。
返回对应请求的返回处理结果 "success" 或 "fail"
若status = success,则data内返回前端需要的json数据
若status = fail,则data内使用通用的错误码格式
@ControllerAdvice
@ControllerAdvice
是一个特殊的@Component
,用于标识一个类,这个类中被以下三种注解标识的方法:
- 全局异常处理(
@ExceptionHandler
) - 全局数据绑定(
@ModelAttribute
) - 全局数据预处理(
@InitBinder
)
Spring进阶之@ControllerAdvice与统一异常处理 - 掘金
项目中主要用于全局异常处理,这里就只介绍这块的内容;全局异常处理时,@ControllerAdvice配合@ExceptionHandler 注解用来指明异常的处理类型,使用很简单,在类上面添加ControllerAdvice注解,在方法上面添加ExceptionHandler注解,就可以在方法里处理相应的异常信息了。即如果这里指定NullpointerException,则数组越界异常就会进到这个方法中来。
@ExceptionHandler(Exception.class)
项目中是@ExceptionHandler(Exception.class),那么Exception体系下的异常类都会进入到此异常接收类中。
SpringBoot源码解析-ExceptionHandler处理异常的原理 - 掘金----原理刨析(未学习)
@ResponseBody
@ResponseBody注解告诉控制器,返回的对象需要自动化序列成JSON,并通过HttpResponseBody返回给客户端,在项目中主要是发生异常时不需要进行页面跳转而是直接返回数据;这个时候就需要在方法上,添加注解:@ResponseBody
默认情况下,使用@ResponseBody返回的数据只能是String类型,其它类型返回时会出现异常;
在项目中添加了JSON格式的转换器-----jackson框架依赖;Jackson库实现了Java对象和JSON的相互转换,保证返回给前端JSON数据。
Java分享SpringMVC@ResponseBody注解 - 掘金
SpringMVC中@RequestBody和@ResponseBody两个注解的区别 - 掘金
RequestBody和ResponseBody踩坑 - 掘金
@RequestBody
和主要是用来接收前端传给后端的json数据;参数放在请求体中,传入后台需要用@RequestBody
进行接收;
@RequestParam
常见的接口请求类型和@RequestBody、@RequestParam的使用 - 掘金
@Controller、@Service、@Repository、@Component
@Component
通用的注解,可标注任意类为 Spring 的组件。如果一个 Bean 不知道属于哪个层,可以使用 @Component 注解标注;spring扫描的时候扫描的是这个注解。
标记@Component的类在spring的beanName中是类首字母小写,其余字母都一样。因为其它几个常用典型注解也是Component,所以他们与Component有相同的beanName命名方式。
@Controller
对应 Spring MVC 控制层,主要用来接受用户请求并调用 Service 层返回数据给前端页面;它通常与@RequestMapping 注解一起使用。允许实现类能够被自动扫描到。
@Service
应用的业务逻辑通常在service层实现,因为我们一般使用@Service注解标记这个类属于业务逻辑层。允许实现类通过类路径的扫描扫描到。
@Repository
对应持久层即 Dao 层,主要用于数据库相关操作。允许实现类能够被自动扫描到。
总结:
上面四个注解标记的类都能够通过@ComponentScan 扫描到,上面四个注解最大的区别就是使用的场景和语义不一样,比如你定义一个Service类想要被Spring进行管理,你应该把它定义为@Service 而不是@Controller因为我们从语义上讲,@Service更像是一个服务的类,而不是一个控制器的类,@Component通常被称作组件,它可以标注任何你没有严格予以说明的类,比如说是一个配置类,它不属于MVC模式的任何一层,这个时候你更习惯于把它定义为 @Component。@Controller,@Service,@Repository 的注解上都有@Component,所以这三个注解都可以用@Component进行替换。项目小的时候对这些注解没有特别的区分,但项目如果变得越来越大,那么就要划分的细致一些有一定的规则才方便大家的维护。
面试题:将一个类声明为 Spring 的 bean 的注解有哪些?
-
@Component :通用的注解,可标注任意类为 Spring 的组件。如果一个 Bean 不知道属于哪个层,可以使用 @Component 注解标注;Spring扫描的时候扫描的是这个注解。
-
@Configuration :声明该类为一个配置类,可以在此类中声明一个或多个 @Bean 方法。
-
@Controller :对应 Spring MVC 控制层,主要用来接受用户请求并调用 Service 层返回数据给前端页面。
-
@Service :对应服务层,主要设计一些复杂的逻辑,需要用到 Dao 层。
-
@Repository :对应持久层即 Dao 层,主要用于数据库相关操作。
@RequestMapping
@RequestMapping 是 Spring Web 应用程序中最常被用到的注解之一,这个注解会将 HTTP 请求映射到 SpringMVC 和 REST 控制器的处理方法上。@RequestMapping用来匹配客户端发送的请求,可以在方法上使用,也可以在类上使用。
方法:表示用来匹配要处理的请求
类上:表示为当前类的所有方法的请求地址添加一个前置路径,访问的时候必须要添加此路径。
超详细 Spring @RequestMapping 注解使用技巧 - 掘金----这篇棒
2、SpringMVC:请求处理之@RequestMapping - 掘金
SpringMVC的@RequestMapping用法 - 掘金--不错
@CrossOrigin
SpringMVC提供了注解 @CrossOrigin 解决跨域问题,在方法上方添加该注解即可,注解里加入发出请求的URL路径,就实现了前后端分离中的跨域请求。
同源策略
同源策略是浏览器的一个安全功能,同源,指的是两个URL的协议,域名,端口相同。浏览器出于安全方面的考虑,不同源的客户端脚本在没有明确授权的情况下,不能读写对方的资源。
那些不受同源策略的限制:
- 页面中的
<a>
跳转、表单提交不会受到同源策略限制的。 - 静态资源引入也不会受到同源策略限制。如嵌入到页面中的
<script src=""> <img src=""> <link href="">
等。 最容易收到同源策略影响的是Ajax请求
跨域请求
当请求的URL的协议,域名,端口三者中任意一个与当前页面URL不同时即为跨域。浏览器在执行JavaScript脚本时,会检查当前请求是否同源,如果不是同源,就不会被执行。
@Autowired
Spring的一大核心功能就是IOC,它能帮助我们实现自动装配,基本上每天我们都会使用到@Autowired注解来为我们自动装配属性;Spring在容器启动阶段,会先实例化bean,然后再对bean进行初始化操作。
@Autowired采取的默认策略为按照类型注入(by-type)。要求容器中一定要有这个类型的对象,如果没有将会报错,抛出异常。也可以通过设置可以@Autowired(required = false),来告诉容器,如果没有可以不注入。
- @Autowired 先根据类型(byType)查找,如果存在多个(Bean)再根据名称(byName)进行查找;
- @Autowired 只支持设置一个 required 的参数
@Autowired依赖注入,常见依赖注入有以下 3 种实现:
属性注入
构造方法注入
@RestController
public class UserController {
// 构造方法注入
private UserService userService;
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
}
Setter 注入
@RestController
public class UserController {
// Setter 注入
private UserService userService;
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
}
源码级问题:
@Autowired注解是如何实现自动装配的?
-
@Autowired注解的实现是通过后置处理器AutowiredAnnotationBeanPostProcessor类的postProcessPropertyValues()方法实现的。
当为类型为A的Bean装配类型为B的属性时,如果此时Spring容器中存在多个类型为B的bean,此时Spring是如何处理的?
-
当自动装配时,从容器中如果发现有多个同类型的属性时,@Autowired注解会先根据类型判断,然后根据@Primary、@Priority注解判断,最后根据属性名与beanName是否相等来判断,如果还是不能决定注入哪一个bean时,就会抛出NoUniqueBeanDefinitionException异常。
自动装配的模型是什么?有哪几种?和Autowired注解有什么关联?
-
@Autowired自动装配中byName、byType与自动装配的模型中的byName、byTYpe没有任何关系,两者含义完全不一样,前者是实现技术的手段,后者是用来定义BeanDefiniton中autowireMode属性的值的类型。
注意点:
@Autowired
可以根据类型(Type
)进行自动注入,并且默认注入的bean为单例(SingleTon
)
- 1、同一类型可以使用
@Autowired
注入多次,并且所有注入的实例都是同一个实例; - 2、当对接口进行注入时,应该为每个实现类指明相应的id,则Spring将报错;
@Autowired和@Resource两个注解的区别:
- @Autowired默认按照byType方式进行bean匹配,@Resource默认按照byName方式进行bean匹配
- @Autowired是Spring的注解,@Resource是J2EE的注解,根据导入注解的包名就可以知道。
- Spring属于第三方的,J2EE是Java自己的东西。因此,建议使用@Resource注解,以减少代码和Spring之间的耦合。
@Autowired与@Resource有何区别? - 掘金
面试突击78:@Autowired 和 @Resource 有什么区别? - 掘金-----好文
@Autowired注解的实现原理 - 掘金----好文
虽然项目中使用了@Autowired;现在不推荐使用它,理由如下:
Spring为啥不推荐使用@Autowired注解? - 掘金
你还在用@Autowired和@Resource? - 掘金
@PostConstruct
@PostConstruct注解是Java自己提供的注解; Java中该注解的说明:@PostConstruct该注解被用来修饰一个非静态的void()方法。被@PostConstruct修饰的方法只会被服务器执行一次。
该注解的方法在整个Bean初始化中的执行顺序:
需要注意:子类实例化过程中会调用父类中的@PostConstruct方法!
- 该注解只能作用于方法上,执行依赖注入后执行任何初始化操作。必须在类投入服务之前调用此方法。
- 应用PostConstruct的方法可以是公共的、受保护的、包私有的或私有的,但不能是静态的。
- 被注解方法不能有任何参数。
eg:项目中用于对线程池参数的初始化、对缓存配置的初始化。
一文带你深入理解SpringBean生命周期之PostConstruct、PreDestroy详解 - 掘金
深入理解Spring IOC之扩展篇(三)、InitializingBean、@PostConstruct、SmartInitializingSingleton - 掘金
@PostConstruct注解是Spring提供的?今天讲点不一样的 - 掘金
@Transactional
@Transactional
是开发中很常用的一个注解,它能保证方法内多个数据库操作要么同时成功、要么同时失败。@Transactional 会在方法执行前,会自动开启事务;在方法成功执行完,会自动提交事务;如果方法在执行期间,出现了异常,那么它会自动回滚事务。使用@Transactional
注解时需要注意许多的细节,不然会发现@Transactional
总是莫名其妙的失效。
事务
事务管理在系统开发中是不可缺少的一部分,Spring
提供了很好事务管理机制,主要分为编程式事务
和声明式事务
两种。
编程式事务:是指在代码中手动的管理事务的提交、回滚等操作,代码侵入性比较强,如下示例:
try {
//TODO something
transactionManager.commit(status);
} catch (Exception e) {
transactionManager.rollback(status);
throw new InvoiceApplyException("异常失败");
}
声明式事务:基于AOP
面向切面的,它将具体业务与事务处理部分解耦,代码侵入性很低,所以在实际开发中声明式事务用的比较多。声明式事务也有两种实现方式,一是基于TX
和AOP
的xml配置文件方式,第二种就是基于@Transactional注解。
@Transactional
@GetMapping("/test")
public String test() {
int insert = cityInfoDictMapper.insert(cityInfoDict);
}
@Transactional注解可以作用于哪些地方?
@Transactional 可以作用在接口
、类
、类方法
。
-
作用于类:当把@Transactional 注解放在类上时,表示所有该类的
public
方法都配置相同的事务属性信息。 -
作用于方法:当类配置了@Transactional,方法也配置了@Transactional,方法的事务会覆盖类的事务配置信息。
-
作用于接口:不推荐这种使用方法,因为一旦标注在Interface上并且配置了Spring AOP 使用CGLib动态代理,将会导致@Transactional注解失效
@Transactional注的常用属性
isolation
:事务的隔离级别,默认值为 Isolation.DEFAULT
。
timeout
:事务的超时时间,默认值为 -1。如果超过该时间限制但事务还没有完成,则自动回滚事务。
readOnly
:指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。
rollbackFor
:用于指定能够触发事务回滚的异常类型,可以指定多个异常类型。
noRollbackFor
:抛出指定的异常类型,不回滚事务,也可以指定多个异常类型。
当声明式事务 @Transactional 遇到以下场景时,事务会失效:
- 非 public 修饰的方法;
- timeout 设置过小;
- 代码中使用 try/catch 处理异常;
- 调用类内部 @Transactional 方法;
- 数据库不支持事务。
- 传播特性设置了不支持事务的方式,也会导致事务失效
1.非 public 修饰的方法
非 public 修饰的方法上,即使加了 @Transactional 事务依然不会生效,原因是因为 @Transactional 使用的是 Spring AOP 实现的,而 Spring AOP 是通过动态代理实现的,而 @Transactional 在生成代理时会判断,如果方法为非 public 修饰的方法,则不生成代理对象,这样也就没办法自动执行事务了,它的部分实现源码如下:
2.try/catch 导致事务失效
@Transactional 执行流程是: @Transactional 会在方法执行前,会自动开启事务;在方法成功执行完,会自动提交事务;如果方法在执行期间,出现了异常,那么它会自动回滚事务。
然而如果在方法中自行添加了 try/catch 之后,事务就不会自动回滚了;造成这个问题的主要原因和 @Transactional 注解的实现有关,它的部分实现源码如下:
protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final InvocationCallback invocation)
throws Throwable {
// If the transaction attribute is null, the method is non-transactional.
final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);
final PlatformTransactionManager tm = determineTransactionManager(txAttr);
final String joinpointIdentification = methodIdentification(method, targetClass);
if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
// Standard transaction demarcation with getTransaction and commit/rollback calls.
// 自动开启事务
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
Object retVal = null;
try {
// This is an around advice: Invoke the next interceptor in the chain.
// This will normally result in a target object being invoked.
// 反射调用业务方法
retVal = invocation.proceedWithInvocation();
}
catch (Throwable ex) {
// target invocation exception
// 异常时,在 catch 逻辑中,自动回滚事务
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
cleanupTransactionInfo(txInfo);
}
// 自动提交事务
commitTransactionAfterReturning(txInfo);
return retVal;
}
else {
// .....
}
}
从上述实现源码可以看出:当执行的方法中出现了异常,@Transactional 才能感知到,然后再执行事务回滚,而当开发者自行添加了 try/catch 之后,@Transactional 就感知不到异常了,从而就不会触发事务的自动回滚了,这就是为什么当 @Transactional 遇到 try/catch 之后就不会自动回滚(事务)的原因。
3.调用类内用的 @Transactional 方法
当调用类内部的 @Transactional 修饰的方法时,事务也不会生效,如下代码所示:
@RequestMapping("/save")
public int saveMappping(UserInfo userInfo) {
return save(userInfo);
}
@Transactional
public int save(UserInfo userInfo) {
// 非空效验
if (userInfo == null ||
!StringUtils.hasLength(userInfo.getUsername()) ||
!StringUtils.hasLength(userInfo.getPassword()))
return 0;
int result = userService.save(userInfo);
int num = 10 / 0; // 此处设置一个异常
return result;
}
上述代码在添加用户之后即使遇到了异常,程序也没有执行回滚,这是因为 @Transactional 是基于 Spring AOP 实现的,而 Spring AOP 又是基于动态代理实现的,而当调用类内部的方法时,不是通过代理对象完成的,而是通过 this 对象实现的,这样就绕过了代理对象,从而事务就失效了。
聊聊spring事务失效的12种场景,太坑了 - 掘金-----牛
一口气说出 6种,@Transactional注解的失效场景 - 掘金-----牛(里面未学习的东西还有很多)
百度一面:谈谈 @Transactional 的原理和坑 - 掘金---源码很深
你真的会用 @Transactional 吗? - 掘金--有些点上面两篇没有