@项目中使用到的Spring注解及其作用

目录

启动类相关注解

@SpringBootApplication 背后到底是什么? 

@SpringBootConfiguration 

@EnableAutoConfiguration

@ComponentScan

@Configuration

@Configuration和@Component的区别是什么?

SpringApplication.run做了哪些事?

@Bean

异常处理相关注解

         为什么要用统一异常接收?

@ControllerAdvice

@ResponseBody

@Controller、@Service、@Repository、@Component

 面试题:将一个类声明为 Spring 的 bean 的注解有哪些?

@RequestMapping

@CrossOrigin

@Autowired

@PostConstruct

@Transactional

 @Transactional注解可以作用于哪些地方?

@Transactional注的常用属性

当声明式事务 @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

@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,用于标识一个类,这个类中被以下三种注解标识的方法:

  1. 全局异常处理(@ExceptionHandler
  2. 全局数据绑定(@ModelAttribute
  3. 全局数据预处理(@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的协议,域名,端口相同。浏览器出于安全方面的考虑,不同源的客户端脚本在没有明确授权的情况下,不能读写对方的资源。

那些不受同源策略的限制:

  1. 页面中的<a>跳转、表单提交不会受到同源策略限制的。
  2. 静态资源引入也不会受到同源策略限制。如嵌入到页面中的 <script src=""> <img src=""> <link href=""> 等。 最容易收到同源策略影响的是Ajax请求

跨域请求

当请求的URL的协议,域名,端口三者中任意一个与当前页面URL不同时即为跨域。浏览器在执行JavaScript脚本时,会检查当前请求是否同源,如果不是同源,就不会被执行。

详解Cross-origin跨域请求 - 掘金--未学习

@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

Spring @Autowired 注入小技巧 - 掘金

  • 1、同一类型可以使用@Autowired注入多次,并且所有注入的实例都是同一个实例;
  • 2、当对接口进行注入时,应该为每个实现类指明相应的id,则Spring将报错;

@Autowired和@Resource两个注解的区别:

  1. @Autowired默认按照byType方式进行bean匹配,@Resource默认按照byName方式进行bean匹配
  2. @Autowired是Spring的注解,@Resource是J2EE的注解,根据导入注解的包名就可以知道。
  3. 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注解详解 - 掘金

@PostConstruct注解是Spring提供的?今天讲点不一样的 - 掘金

@Transactional

@Transactional 是开发中很常用的一个注解,它能保证方法内多个数据库操作要么同时成功、要么同时失败。@Transactional 会在方法执行前,会自动开启事务;在方法成功执行完,会自动提交事务;如果方法在执行期间,出现了异常,那么它会自动回滚事务。使用@Transactional注解时需要注意许多的细节,不然会发现@Transactional总是莫名其妙的失效。

@Transactional 是基于 AOP 实现的, AOP 又是使用动态代理实现的。
@Transactional 在开始执行业务之前,通过代理先开启事务,在执行成功之后再提交事务。如果中途遇到的异常,则回滚事务。

事务

事务管理在系统开发中是不可缺少的一部分,Spring提供了很好事务管理机制,主要分为编程式事务声明式事务两种。

编程式事务:是指在代码中手动的管理事务的提交、回滚等操作,代码侵入性比较强,如下示例:

try {
    //TODO something
     transactionManager.commit(status);
} catch (Exception e) {
    transactionManager.rollback(status);
    throw new InvoiceApplyException("异常失败");
}

声明式事务:基于AOP面向切面的,它将具体业务与事务处理部分解耦,代码侵入性很低,所以在实际开发中声明式事务用的比较多。声明式事务也有两种实现方式,一是基于TXAOP的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 遇到以下场景时,事务会失效:

  1. 非 public 修饰的方法;
  2. timeout 设置过小;
  3. 代码中使用 try/catch 处理异常;
  4. 调用类内部 @Transactional 方法;
  5. 数据库不支持事务。
  6. 传播特性设置了不支持事务的方式,也会导致事务失效

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 吗? - 掘金--有些点上面两篇没有

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值