40个Spring常用注解

2020/04/20

1、lombok的@Accessors支持链式编程

import lombok.Data;
import lombok.experimental.Accessors;

import java.io.Serializable;

@Data
@Accessors(chain = true)
public class Article  implements Serializable {

   private static final long serialVersionUID = 2330084508567540133L;
   private Integer id;
   private String author;
   private String title;
   private String content;
}

2、@CrossOrigin 允许跨域

添加注解的方式

@CrossOrigin
public class SpringBootDataStudyXjwApplication {

	public static void main(String[] args) {
		SpringApplication.run(SpringBootDataStudyXjwApplication.class, args);
	}
}

添加配置类的方式

@Configuration
public class CorsConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
            .allowedOrigins("*")
            .allowCredentials(true)
            .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
            .maxAge(3600);
    }
}

跨域的解决方案参考自己写的文章

3、@Mapper和@Repository的区别

@Mapper和@Repository是常用的两个注解,两者都是用在dao上,两者功能差不多

区别

  • @Repository需要在Spring中配置扫描地址,然后生成Dao层的Bean才能被注入到Service层中。

    在启动类中配置扫描地址:

    @SpringBootApplication   //添加启动类注解
    @MapperScan("com.anson.dao")  //配置mapper扫描地址
    public class application{
        public static   void main(String[] args)
        {
            SpringApplication.run(application.class,args);
        }
    }
    
  • @Mapper 不需要配置扫描地址,通过xml里面的namespace里面的接口地址,生成了Bean后注入到Service层中。

    结合Mybatis-Plus,在dao类只需加上@mapper注解就可以了。

4、@PostConstruct和@PreDestroy修饰方法

值得注意的是,这两个注解不属于Spring,它们是源于JSR-250中的两个注解,位于common-annotations.jar中。@PostConstruct注解用于标注在Bean被Spring初始化之前需要执行的方法。@PreDestroy注解用于标注Bean被销毁前需要执行的方法。下面是具体的示例代码:

从JDK5开始,servlet增加了两个影响生命周期的注解

  • @PostConstruct

    被@PostConstruct修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器调用一次,类似于Servlet的init()方法。被@PostConstruct修饰的方法会在构造函数之后,init()方法之前运行。

  • @PreDestroy

    被@PreDestroy修饰的方法会在服务器卸载Servlet的时候运行,并且只会被服务器调用一次,类似于Servlet的destroy()方法。被@PreDestroy修饰的方法会在destroy()方法之后运行,在Servlet被彻底卸载之前

项目经历

renren-fast的框架就用到@PostConstruct注解在项目启动时加载定时任务,放在业务层实现类,代码如下:

/**
	 * 项目启动时,初始化定时器
	 */
@PostConstruct
public void init(){
  // 定时任务列表
  List<ScheduleJobEntity> scheduleJobList = this.list();
  for(ScheduleJobEntity scheduleJob : scheduleJobList){
    CronTrigger cronTrigger = ScheduleUtils.getCronTrigger(scheduler, scheduleJob.getJobId());
    //如果不存在,则创建
    if(cronTrigger == null) {
      ScheduleUtils.createScheduleJob(scheduler, scheduleJob);
    }else {
      ScheduleUtils.updateScheduleJob(scheduler, scheduleJob);
    }
  }
}

照猫画虎,我就在框架中加了分布式流水号的初始化

@Service("serialNumberService")
public class SerialNumberServiceImpl implements SerialNumberService {
	@Autowired
	RedisUtils redisUtils;

	@Autowired
	ProjectService projectService;

	@Autowired
	EngineeringService engineeringService;

	private final String CURRENT_MAX_PROJECT_NO = "currentMaxProjectNo";

	private final String CURRENT_MAX_ENG_NO = "currentMaxEngNo";

	/**
	 * 初始化项目的流水号
	 */
	@PostConstruct
	public void initProjectNo(){
		Map<String,Object> map = projectService.getMap(new QueryWrapper<ProjectEntity>().select("max(project_no) maxProjectNo"));
		if(map ==null || map.size() == 0){
			redisUtils.set(CURRENT_MAX_PROJECT_NO,"P20190517000000",RedisUtils.NOT_EXPIRE);
		}else{
			redisUtils.set(CURRENT_MAX_PROJECT_NO,map.get("maxProjectNo").toString(),RedisUtils.NOT_EXPIRE);
		}
	}

	/**
	 * 初始化工程的流水号
	 */
	@PostConstruct
	public void initEngNo(){
		Map<String,Object> map = engineeringService.getMap(new QueryWrapper<EngineeringEntity>().select("max(eng_no) maxEngNo"));
		if(map == null || map.size() == 0){
			redisUtils.set(CURRENT_MAX_ENG_NO,"E20190517000000",RedisUtils.NOT_EXPIRE);
		}else{
			redisUtils.set(CURRENT_MAX_ENG_NO,map.get("maxEngNo").toString(),RedisUtils.NOT_EXPIRE);
		}
	}

	/**
	 * @Description 采用synchronized+redis分布式锁 形式共同完成
	 */
	@Override
	public synchronized String getProjectNo() {
		String newMaxValue = null;
		if(redisUtils.lock(CURRENT_MAX_PROJECT_NO)){
			// 1.获取当前最大项目编号
			String currentMaxValue = redisUtils.get(CURRENT_MAX_PROJECT_NO);
			if(StringUtils.isEmpty(currentMaxValue)){
				// 重新初始化
				initProjectNo();
				currentMaxValue = redisUtils.get(CURRENT_MAX_PROJECT_NO);
			}

			// 2.最大值加1,格式 P+yyyyMMdd+6位数字
			int currentMaxNum = Integer.parseInt(currentMaxValue.substring(9));
			newMaxValue = "P"+ DateUtils.format(new Date(),"yyyyMMdd") + String.format("%06d",currentMaxNum + 1);

			// 3.新的最大值保存到redis缓存
			redisUtils.set(CURRENT_MAX_PROJECT_NO,newMaxValue,RedisUtils.NOT_EXPIRE);

			// 4.释放锁
			redisUtils.unlock(CURRENT_MAX_PROJECT_NO);

		}
		if(newMaxValue == null){
			throw new RRException("获取项目编号失败", R.STATUS_FAIL);
		}
		return newMaxValue;
	}

	@Override
	public synchronized String getEngNo() {
		String newMaxValue = null;
		if(redisUtils.lock(CURRENT_MAX_ENG_NO)){
			// 1.获取当前最大工程编号
			String currentMaxValue = redisUtils.get(CURRENT_MAX_ENG_NO);
			if(StringUtils.isEmpty(currentMaxValue)){
				// 重新初始化
				initEngNo();
				currentMaxValue = redisUtils.get(CURRENT_MAX_ENG_NO);
			}

			// 2.最大值加1,格式E+yyyyMMdd+6位数字
			int currentMaxNum = Integer.parseInt(currentMaxValue.substring(9));
			newMaxValue = "E"+ DateUtils.format(new Date(),"yyyyMMdd") + String.format("%06d",currentMaxNum + 1);

			// 3.新的最大值保存到redis缓存
			redisUtils.set(CURRENT_MAX_ENG_NO,newMaxValue,RedisUtils.NOT_EXPIRE);

			// 4.释放锁
			redisUtils.unlock(CURRENT_MAX_ENG_NO);
		}
		if(newMaxValue == null){
			throw new RRException("获取工程编号失败", R.STATUS_FAIL);
		}
		return newMaxValue;
	}
}

@Bean注解中指定初始化方法

这种方式与@PostConstruct类似,同样是指定一个方法在Bean初始化完成之后调用

public class SimpleExampleBean {
  public void init(){
    log.debug("Bean初始化完成,调用。。");
  }
}

然后在配置类中通过@Bean实例化这个Bean,不过@Bean中的initMethod这个属性需要指定初始之后需要执行的方法,如下:

@Bean(initMethod = "init")
public SimpleExampleBean simpleExampleBean(){
  return new SimpleExampleBean();
}

InitializingBean接口

InitializingBean的用法基本与@PostConstruct一致,只不过相应的Bean需要实现afterPropertiesSet方法

@Component
public class SimpleExampleBean implements InitializingBean {
  @Override
  public void afterPropertiesSet(){
    log.debug("Bean初始化完成,调用。。。。");
  }
}

5、@Transcactional本地事务

Spring 使用 @Transcactional注解管理事务,我们一起来看看它的源码:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
  @AliasFor("transactionManager")
  String value() default "";

  @AliasFor("value")
  String transactionManager() default "";

  // 事务传播
  Propagation propagation() default Propagation.REQUIRED;

  // 隔离
  Isolation isolation() default Isolation.DEFAULT;

  // 事务的超时时间,设置事务在强制回滚之前可以占用的时间,默认为-1,不超时,单位为s(测试为单位s)
  int timeout() default -1;

  /* 
  true: 只读,只能对数据库进行读取操作,不能有修改的操作,如果需要确保当前事务只有读取操作,就有必要设置为只读,可以帮助数据库,	引擎优化事务;
  false: 非只读,不仅会读取数据还会有修改操作*/
  boolean readOnly() default false;

  // 剩下的四个属性:事务的回滚与不回滚   默认情况下, Spring会对所有的运行时异常进行事务回滚,指定异常的类名,或者类型
  Class<? extends Throwable>[] rollbackFor() default {};

  String[] rollbackForClassName() default {};

  Class<? extends Throwable>[] noRollbackFor() default {};

  String[] noRollbackForClassName() default {};
}

1、propagation 属性如何处理事务,点进Propagation的源码

public enum Propagation {
  /**支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择,也是 Spring 默认的事务的传播。*/
  REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),
  
  /**支持当前事务,如果当前没有事务,就以非事务方式执行*/
  SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),
  
  /**支持当前事务,如果当前没有事务,就抛出异常*/
  MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),
  
  /**新建事务,如果当前存在事务,把当前事务挂起*/
  REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),
  
  /**-以非事务方式执行操作,如果当前存在事务,就把当前事务挂起*/
  NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),
  
  /**以非事务方式执行,如果当前存在事务,则抛出异常*/
  NEVER(TransactionDefinition.PROPAGATION_NEVER),
  
  /**如果一个活动的事务存在,则运行在一个嵌套的事务中。如果没有活动事务,则按REQUIRED属性执行。
     * 它使用了一个单独的事务,这个事务拥有多个可以回滚的保存点。
     * 内部事务的回滚不会对外部事务造成影响。
     * 它只对DataSourceTransactionManager事务管理器起效
    */
  NESTED(TransactionDefinition.PROPAGATION_NESTED);

  private final int value;

  Propagation(int value) {
    this.value = value;
  }

  public int value() {
    return this.value;
  }
}

2、isolation 属性,事务的隔离级别,点进Isolation的源码

public enum Isolation {
 
	/**数据库的默认级别*/
	DEFAULT(TransactionDefinition.ISOLATION_DEFAULT),
 
	/**读未提交      脏读*/
	READ_UNCOMMITTED(TransactionDefinition.ISOLATION_READ_UNCOMMITTED),
 
	/**读已提交  不可重复读(update)*/
	READ_COMMITTED(TransactionDefinition.ISOLATION_READ_COMMITTED),
 
	/**可重复读      幻读(插入操作)*/
	REPEATABLE_READ(TransactionDefinition.ISOLATION_REPEATABLE_READ),
 
	/** 串行化         效率低*/
	SERIALIZABLE(TransactionDefinition.ISOLATION_SERIALIZABLE);
 
	private final int value;
 
	Isolation(int value) { this.value = value; }
 
	public int value() { return this.value; }

}

Spring Boot 使用注解 @EnableTransactionManagement 开启事务支持后,然后在访问数据库的Service方法上添加注解 @Transactional 便可, @Transactional放在Service类上,代表每一方法都是一个事务。例如使用mybatis,在mybatis的配置类上添加@EnableTransactionManagement注解

事务失效

1、只在public方法上生效

2、数据库引擎本身不支持事务,比如说MySQL数据库中的myisam,本身就不支持事务

3、Spring只会对unchecked异常进行事务回滚;如果是checked异常则不回滚

  • unchecked异常:派生于Error或者RuntimeException的异常
  • checked异常:所有其他的异常

编程式事务:/springboot/2021/02/24/spring-skill-002.html

@EnableTransactionManagement 源码分析

@Transactional是如何选择事务控制器的,还有编程式事务TransactionTemplate是如何获取事务控制器,为什么多数据源的情况下需要指定事务控制器,否则spring事务失效

6、@Autowired与@Resource的区别

如UserService、UserService2是UserService的两个实现类

@Service("userService1")
public class UserService  implements UserService {}

@Service("userService2")
public class UserService2 implements UserService {}

spring的@Service注解默认会将类名的第一个字母转换成小写,作为bean的名称,如上面的userService,当然我们也可以自定义bean名称,就像上面的写法

那么在方法中使用接口UserService,使用@Autowired来标注时,需要加上@Qualifier区分注入具体的实现类。

@Autowired
@Qualifier("userService2")
private IuserService userService;

@Resource(name="loginService") 
private LoginService loginService;

@Autowired(required=false)@Qualifier("loginService") 
private LoginService loginService;
  1. @Autowired 与@Resource都可以用来装配bean,写在字段上或者setter方法上;

  2. @Autowired 默认按类型装配

    ,就是bytype方式

    默认情况下依赖对象必须存在,如果要允许null值,可以设置它的required属性为false,就不会自动装配了

    @Autowired(required=false)
    
  3. @Resource(这个注解属于J2EE的)默认按名称装配

    通过name属性指定,如果没有指定name属性,就字段名装配;如果注解写在setter方法上,默认按属性名进行装配,当找不到匹配的bean时才按照类型进行装配。注意:如果name属性一旦指定,就只会按照名称进行装配。

下面展开@Autowired深入说明

@Qualifier

public class TestService1 {
    public void test1() {
    }
}

@Service
public class TestService2 {
    @Autowired
    private TestService1 testService1;

    public void test2() {
    }
}

@Configuration
public class TestConfig {
    @Bean("test1")
    public TestService1 test1() {
        return new TestService1();
    }

    @Bean("test2")
    public TestService1 test2() {
        return new TestService1();
    }
}

启动会报错

提示testService1是单例的,根据类型匹配找到两个对象,需要加上@Qualifier区分注入具体的实现类,相当于byName的方式,Qualifier意思是合格者,一般跟Autowired配合使用,需要指定一个bean的名称,通过bean名称就能找到需要装配的bean。

@Autowired
@Qualifier("test1")
private TestService1 testService1;

@Primary

public interface IUser {
    void say();
}

@Service
public class User1 implements IUser{
    @Override
    public void say() {
    }
}

@Service
public class User2 implements IUser{
    @Override
    public void say() {
    }
}

@Service
public class UserService {

    @Autowired
    private IUser user;
}

启动同样因为找到两个同类型的bean对象报错,可以加@Primary注解解决

@Primary
@Service
public class User1 implements IUser{
    @Override
    public void say() {
    }
}

当我们使用自动配置的方式装配Bean时,如果这个Bean有多个候选者,假如其中一个候选者具有@Primary注解修饰,该候选者会被选中,作为自动配置的值。

@Autowired的使用范围

看源码

看元注解@Target我们知道autowired的作用范围

下面逐一说明

  • 成员变量

    这种方式我们平时用的最多

    @Service
    public class UserService {
        @Autowired
        private IUser user;
    }
    
  • 构造器

    在构造器上使用Autowired注解:

    @Service
    public class UserService {
        private IUser user;
      
        @Autowired
        public UserService(IUser user) {
            this.user = user;
            System.out.println("user:" + user);
        }
    }
    

    其实是自动装配了IUser类的bean对象

  • 方法

    在普通方法上加Autowired注解:

    @Service
    public class UserService {
        @Autowired
        public void test(IUser user) {
           user.say();
        }
    }
    

    spring会在项目启动的过程中,自动调用一次加了@Autowired注解的方法,我们可以在该方法做一些初始化的工作。

  • 参数

    可以在构造器的入参上加Autowired注解

    @Service
    public class UserService {
        private IUser user;
      
        public UserService(@Autowired IUser user) {
            this.user = user;
            System.out.println("user:" + user);
        }
    }
      
    @Service
    public class UserService {
        public void test(@Autowired IUser user) {
           user.say();
        }
    }
    

@Autowired的高端玩法

将UserService方法调整一下,用一个List集合接收IUser类型的参数:

@Service
public class UserService {
    @Autowired
    private List<IUser> userList;

    @Autowired
    private Set<IUser> userSet;

    @Autowired
    private Map<String, IUser> userMap;

    public void test() {
        System.out.println("userList:" + userList);
        System.out.println("userSet:" + userSet);
        System.out.println("userMap:" + userMap);
    }
}

增加一个controller

@RequestMapping("/u")
@RestController
public class UController {
    @Autowired
    private UserService userService;

    @RequestMapping("/test")
    public String test() {
        userService.test();
        return "success";
    }
}

从上图中看出:userList、userSet和userMap都打印出了两个元素,说明@Autowired会自动把相同类型的IUser对象收集到集合中。

@Autowired自动装配失败

  • 没有加@Service注解

    在类上面忘了加@Controller、@Service、@Component、@Repository等注解,spring就无法完成自动装配的功能,例如

    public class UserService {
        @Autowired
        private IUser user;
      
        public void test() {
            user.say();
        }
    }
    
  • 注入Filter或Listener

    web应用启动的顺序是:listener->filter->servlet

    接下来,看看这个案例

    public class UserFilter implements Filter {
        @Autowired
        private IUser user;
      
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
            user.say();
        }
      
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
      
        }
      
        @Override
        public void destroy() {
        }
    }
    
    @Configuration
    public class FilterConfig {
        @Bean
        public FilterRegistrationBean filterRegistrationBean() {
            FilterRegistrationBean bean = new FilterRegistrationBean();
            bean.setFilter(new UserFilter());
            bean.addUrlPatterns("/*");
            return bean;
        }
    }
    

    程序启动会报错:

    tomcat无法正常启动。

    什么原因呢?

    众所周知,springmvc的启动是在DisptachServlet里面做的,而它是在listener和filter之后执行。如果我们想在listener和filter里面@Autowired某个bean,肯定是不行的,因为filter初始化的时候,此时bean还没有初始化,无法自动装配。

    如果工作当中真的需要这样做,我们该如何解决这个问题呢?

    public class UserFilter  implements Filter {
        private IUser user;
      
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
            ApplicationContext applicationContext = WebApplicationContextUtils.getWebApplicationContext(filterConfig.getServletContext());
            this.user = ((IUser)(applicationContext.getBean("user1")));
            user.say();
        }
      
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
      
        }
      
        @Override
        public void destroy() {
        }
    }
    

    答案是使用WebApplicationContextUtils.getWebApplicationContext获取当前的ApplicationContext,再通过它获取到bean实例。

  • 注解未被@ComponentScan扫描

    通常情况下,@Controller、@Service、@Component、@Repository、@Configuration等注解,是需要通过@ComponentScan注解扫描,收集元数据的。

    但是,如果没有加@ComponentScan注解,或者@ComponentScan注解扫描的路径不对,或者路径范围太小,会导致有些注解无法收集,到后面无法使用@Autowired完成自动装配的功能。

    有个好消息是,在springboot项目中,如果使用了@SpringBootApplication注解,它里面内置了@ComponentScan注解的功能,启动类会默认扫描同package及子package的路径,自动装配的功能

  • 循环依赖问题

    spring的bean默认是单例的,如果单例bean使用@Autowired装配,大多数情况,能解决循环依赖问题。

@Autowired和@Resource的区别

@Autowired功能虽说非常强大,但是也有些不足之处。比如:比如它跟spring强耦合了,如果换成了JFinal等其他框架,功能就会失效。而@Resource是JSR-250提供的,它是Java标准,绝大部分框架都支持。有些场景使用@Autowired无法满足的要求,改成@Resource却能解决问题。

  • @Autowired默认按byType自动装配,而@Resource默认byName自动装配。
  • @Autowired只包含一个参数:required,表示是否开启自动准入,默认是true。而@Resource包含七个参数,其中最重要的两个参数是:name 和 type。
  • @Autowired如果要使用byName,需要使用@Qualifier一起配合。而@Resource如果指定了name,则用byName自动装配,如果指定了type,则用byType自动装配。
  • @Autowired能够用在:构造器、方法、参数、成员变量和注解上,而@Resource能用在:类、成员变量和方法上。
  • @Autowired是spring定义的注解,而@Resource是JSR-250定义的注解。

他们的装配顺序不同

@Autowired的装配顺序如下

@Resource的装配顺序如下

  1. 如果同时指定了name和type:

  2. 如果指定了name:

  3. 如果指定了type:

  4. 如果既没有指定name,也没有指定type

@AutowiredSpring提供的,它是特定IoC提供的特定注解,这就导致了应用与框架的强绑定,一旦换用了其他的IoC框架,是不能够支持注入的。

@ResourceJSR-250提供的,它是Java标准,我们使用的IoC容器应当去兼容它,这样即使更换容器,也可以正常工作。

基于这个原因,我们推荐使用@Resource注解,不使用@Autowired注解

7、@Slf4j

这个是lombok的扩展注解,import lombok.extern.slf4j.Slf4j;

如果每次不想写上

private  final Logger logger = LoggerFactory.getLogger(当前类名.class);

可以用注解@Slf4j 来打印日志。

1、首先idea需要安装lombok插件

2、你的springboot项目引入依赖

<dependency>
  <groupId>org.projectlombok</groupId>
  <artifactId>lombok</artifactId>
  <optional>true</optional>  可选依赖,如果间接依赖要显示声明该依赖
</dependency>

3、在任意类上添加注解@Slf4j,就可以在本类中任意方法内打印日志了

@Slf4j
@RestController(value = "/test")
public class TestController {
    @RequestMapping(value = "/testPrint",method = RequestMethod.GET)
    public String testPrint(){
        log.debug("可以直接调用log打印日志了");
        return "testPrint";
    }   
}

8、@RequestMapping的4大请求方法

@RequestMapping注解的主要用途是将Web请求与请求处理类中的方法进行映射,有以下的六个配置属性

  • value:映射的请求URL或者其别名
  • method:兼容HTTP的方法名
  • params:根据HTTP参数的存在、缺省或值对请求进行过滤
  • header:根据HTTP Header的存在、缺省或值对请求进行过滤
  • consume:设定在HTTP请求正文中允许使用的媒体类型
  • product:在HTTP响应体中允许使用的媒体类型

提示:在使用@RequestMapping之前,请求处理类还需要使用@Controller或@RestController进行标记

@RequestBody

@RequestBody在处理请求方法的参数列表中使用,它可以将请求主体中的参数绑定到一个对象中,请求主体参数是通过HttpMessageConverter传递的,根据请求主体中的参数名与对象的属性名进行匹配并绑定值。此外,还可以通过@Valid注解对请求主体中的参数进行校验。下面是一个使用@RequestBody的示例

相当于其它4大请求注解:

  • @GetMapping = @RequestMapping(method = RequestMethod.GET)
  • @PostMapping = @RequestMapping(method = RequestMethod.POST)
  • @PutMapping = @RequestMapping(method = RequestMethod.PUT)
  • @DeleteMapping = @RequestMapping(method = RequestMethod.DELETE)

区别

  1. 语义上的不同,Get 是获取数据,把参数放在url中,Post 是提交数据,把参数放在request body中,所以Get就会暴露参数,相对不安全,而且url 传送参数长度是有限制的;

  2. Get 在浏览器回退时是无害的,Post 会再次提交请求,会造成重复提交;

  3. Get 的url地址可以被浏览器历史记录记住,Post 不会;

  4. Get 请求会被浏览器主动cache,而Post 不会,除非手动设置;

  5. Put 更新单个对象数据

    @ApiOperation(value = "根据id更新章节")
    @PutMapping("{id}")
    public R updateById(@ApiParam(name = "id",value = "章节id",required = true) @PathVariable String id,
                        @ApiParam(name = "chapter",value = "章节对象",required = true) @RequestBody Chapter chapter){
      chapterService.updateById(chapter);
      return R.ok();
    }
    

    Put与Post 都是向服务端发送数据,区别在于Post主要作用一个集合资源上(url),而Put主要作用在一个具体资源上(url/xxx),如果URL可以在客户端确定,那么可使用PUT,否则用POST

  6. Delete 请求顾名思义,就是用来删除某一个资源的,该请求就像数据库的delete操作。

    @ApiOperation(value = "根据id删除章节")
    @DeleteMapping("{id}")
    public R removeById(@ApiParam(name = "id",value = "章节id",required = true) @PathVariable String id){
      chapterService.removeChapterById(id);
      return R.ok();
    }
    

@PatchMapping

@PatchMapping注解用于处理HTTP PATCH请求,并将请求映射到对应的处理方法中。@PatchMapping相当于是@RequestMapping(method=HttpMethod.PATCH)的快捷方式。下面是一个简单的示例:

总结

POST    /url      创建  
DELETE  /url/{id}  删除  
PUT     /url/{id}  更新
GET     /url/{courseId}  查看
GET     /url/{page}/{limit}  查看

说到请求,就提一下请求时的contentType的值(提交数据的方式)

  • application/x-www-form-urlencoded

    原生浏览器的form表单提交方式,默认的方式,键值对,action为get时会把参数拼接到url上,action为post时会把参数放入request body中,

  • multipart/form-data

    使用表单上传文件时,必须设置form元素的enctyped 属性为该值。

    前端接口上传文件的时候,通常会将请求header的content-type设置为:multipart/form-data, 或者form表单提交的时候将enctype设置为”multipart/form-data” 。

  • application/json

    主流的json字符串提交方式,数据对象放入request body中,jquery时代的ajax和vue时代的axios异步一般都会使用该方式提交数据

9、@Aspect 切面编程

spring简化开发的4大核心思想:

  1. Spring Bean,生命周期由spring 容器管理的ava对象
  2. IOC,控制反转的思想,所有的对象都去Spring容器getbean
  3. AOP,切面编程降低侵入。
  4. xxxTemplate模版技术,如RestTemplate,RedisTemplate

AOP是Spring框架面向切面的编程思想,它将涉及多业务流程的通用功能抽取并单独封装,形成独立的切面,在合适的时机将这些切面横向切入到业务流程指定的位置中,从而让业务层只关注业务本身,降低程序的耦合度,是函数式编程的一种衍生。

通用功能:非业务逻辑功能,如日志记录(renren-fast有实现),权限验证(renren-fast有实现),事务处理,异常处理等

具体看我的另外一篇文章:Spring AOP 切面编程

10、@JsonFormat 日期格式化

常用的做法在po类的日期类型字段上贴 @JsonFormat方式来设置格式

@JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd") // 返回给前端json字符串格式
@DateTimeFormat(pattern="yyyy-MM-dd")//前端传参,接受格式,不是这个格式的就会报错
private Date birthday;

全局配置,可以在application.properties中配置

# 接受和返回的日期格式
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8 # 北京时间

上面是SpringBoot默认使用的jackson进行时间格式设置,spring-boot-starter-web默认引入jackson依赖

11、@RedisOpener

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Import({RedisSelecter.class})
public @interface RedisOpener {
  // 开启模板类
  boolean enableTemplate() default true;
// 开启缓存
  boolean enableCache() default false;
// 开启分布式锁
  boolean enableLock() default false;
}

可以看到会引入RedisSelecter选择器,点击源码

enableLock=true会开启RedissonConfig的配置,就是配置redisson分布式锁

@ControllerAdvice

@ControllerAdvice是@Component注解的一个延伸注解,Spring会自动扫描并检测被@ControllerAdvice所标注的类。@ControllerAdvice需要和@ExceptionHandler、@InitBinder以及@ModelAttribute注解搭配使用,主要是用来处理控制器所抛出的异常信息。首先,我们需要定义一个被@ControllerAdvice所标注的类,在该类中,定义一个用于处理具体异常的方法,并使用@ExceptionHandler注解进行标记。此外,在有必要的时候,可以使用@InitBinder在类中进行全局的配置,还可以使用@ModelAttribute配置与视图相关的参数。使用@ControllerAdvice注解,就可以快速的创建统一的,自定义的异常处理类。

下面是一个使用@ControllerAdvice的示例代码:

@ExceptionHandler

@ExceptionHander注解用于标注处理特定类型异常类所抛出异常的方法。当控制器中的方法抛出异常时,Spring会自动捕获异常,并将捕获的异常信息传递给被@ExceptionHandler标注的方法。

下面是使用该注解的一个示例:

@ResponseBody

@ResponseBody会自动将控制器中方法的返回值写入到HTTP响应中。特别的,@ResponseBody注解只能用在被@Controller注解标记的类中。如果在被@RestController标记的类中,则方法不需要使用@ResponseBody注解进行标注。@RestController相当于是@Controller和@ResponseBody的组合注解。

下面是使用该注解的一个示例

@ResponseStatus

@ResponseStatus注解可以标注请求处理方法。使用此注解,可以指定响应所需要的HTTP STATUS。特别地,我们可以使用HttpStauts类对该注解的value属性进行赋值。

下面是使用@ResponseStatus注解的一个示例:

@PathVariable

@PathVariable注解是将方法中的参数绑定到请求URI中的模板变量上。可以通过@RequestMapping注解来指定URI的模板变量,然后使用@PathVariable注解将方法中的参数绑定到模板变量上。特别地,@PathVariable注解允许我们使用value或name属性来给参数取一个别名。

下面是使用此注解的一个示例:

模板变量名需要使用“{ }”进行包裹,如果方法的参数名与URI模板变量名一致,则在@PathVariable中就可以省略别名的定义。下面是一个简写的示例:

提示:如果参数是一个非必须的,可选的项,则可以在@PathVariable中设置require = false

@RequestParam

@RequestParam注解用于将方法的参数与Web请求的传递的参数进行绑定。使用@RequestParam可以轻松的访问HTTP请求参数的值。下面是使用该注解的代码示例:

该注解的其他属性配置与@PathVariable的配置相同,特别的,如果传递的参数为空,还可以通过defaultValue设置一个默认值。示例代码如下:

@Controller

@Controller是@Component注解的一个延伸,Spring会自动扫描并配置被该注解标注的类。此注解用于标注Spring MVC的控制器

@RestController

@RestController是在Spring 4.0开始引入的,这是一个特定的控制器注解。此注解相当于@Controller和@ResponseBody的快捷方式。当使用此注解时,不需要再在方法上使用@ResponseBody注解。

@ModelAttribute

通过此注解,可以通过模型索引名称来访问已经存在于控制器中的model。下面是使用此注解的一个简单示例:

与@PathVariable和@RequestParam注解一样,如果参数名与模型具有相同的名字,则不必指定索引名称,简写示例如下:

特别地,如果使用@ModelAttribute对方法进行标注,Spring会将方法的返回值绑定到具体的Model上。示例如下

@InitBinder

@InitBinder注解用于标注初始化WebDataBinider的方法,该方法用于对Http请求传递的表单数据进行处理,如时间格式化、字符串处理等。下面是使用此注解的示例:

12、Spring Bean 注解

主要列举与Spring Bean相关的4个注解以及它们的使用方式。

@ComponentScan

@ComponentScan注解用于配置Spring需要扫描的被组件注解注释的类所在的包。可以通过配置其basePackages属性或者value属性来配置需要扫描的包路径。value属性是basePackages的别名。此注解的用法如下

@Component

@Component注解用于标注一个普通的组件类,它没有明确的业务范围,只是通知Spring被此注解的类需要被纳入到Spring Bean容器中并进行管理。此注解的使用示例如下:

@Service

@Service注解是@Component的一个延伸(特例),它用于标注业务逻辑类。与@Component注解一样,被此注解标注的类,会自动被Spring所管理。下面是使用@Service注解的示例:

@Repository

@Repository注解也是@Component注解的延伸,与@Component注解一样,被此注解标注的类会被Spring自动管理起来,@Repository注解用于标注DAO层的数据持久化类。此注解的用法如下:

13、Spring Dependency 注入

@DependsOn

@DependsOn注解可以配置Spring IoC容器在初始化一个Bean之前,先初始化其他的Bean对象。下面是此注解使用示例代码:

@Bean

@Bean注解主要的作用是告知Spring,被此注解所标注的类将需要纳入到Bean管理工厂中。@Bean注解的用法很简单,在这里,着重介绍@Bean注解中initMethod和destroyMethod的用法。示例如下

@Scope

@Scope注解可以用来定义@Component标注的类的作用范围以及@Bean所标记的类的作用范围。@Scope所限定的作用范围有:singleton、prototype、request、session、globalSession或者其他的自定义范围。这里以prototype为例子进行讲解。当一个Spring Bean被声明为prototype(原型模式)时,在每次需要使用到该类的时候,Spring IoC容器都会初始化一个新的改类的实例。在定义一个Bean时,可以设置Bean的scope属性为prototype:scope=“prototype”,也可以使用@Scope注解设置,如下:

@Scope(value=ConfigurableBeanFactory.SCOPE_PROPTOTYPE)

@Scope 单例模式

@Scope(value=ConfigurableBeanFactory.SCOPE_SINGLETON)

当@Scope的作用范围设置成Singleton时,被此注解所标注的类只会被Spring IoC容器初始化一次。在默认情况下,Spring IoC容器所初始化的类实例都为singleton。同样的原理,此情形也有两种配置方式,示例代码如下:

14、容器配置注解

@Autowired

@Autowired注解用于标记Spring将要解析和注入的依赖项。此注解可以作用在构造函数、字段和setter方法上。

作用于构造函数

作用于setter方法

作用于字段

@Autowired注解标注字段是最简单的,只需要在对应的字段上加入此注解即可,示例代码如下:

@Primary

当系统中需要配置多个具有相同类型的bean时,@Primary可以定义这些Bean的优先级。下面将给出一个实例代码来说明这一特性:

结果

this is send DingDing method message.

@Qualifier

当系统中存在同一类型的多个Bean时,@Autowired在进行依赖注入的时候就不知道该选择哪一个实现类进行注入。此时,我们可以使用@Qualifier注解来微调,帮助@Autowired选择正确的依赖项。下面是一个关于此注解的代码示例:

15、条件注解

@SpringBootApplication

@SpringBootApplication注解是一个快捷的配置注解,在被它标注的类中,可以定义一个或多个Bean,并自动触发自动配置Bean和自动扫描组件。此注解相当于@Configuration、@EnableAutoConfiguration和@ComponentScan的组合。在Spring Boot应用程序的主类中,就使用了此注解。示例代码如下

@SpringBootApplication
public class Application{
 public static void main(String [] args){
   SpringApplication.run(Application.class,args);
 }
}

@EnableAutoConfiguration

@EnableAutoConfiguration注解用于通知Spring,根据当前类路径下引入的依赖包,自动配置与这些依赖包相关的配置项。

@ConditionalOnClass与@ConditionalOnMissingClass

这两个注解属于类条件注解,它们根据是否存在某个类作为判断依据来决定是否要执行某些配置。下面是一个简单的示例代码:

@Configuration
@ConditionalOnClass(DataSource.class)
class MySQLAutoConfiguration {
 //...
}

@ConditionalOnBean与@ConditionalOnMissingBean

这两个注解属于对象条件注解,根据是否存在某个对象作为依据来决定是否要执行某些配置方法。示例代码如下:


@Bean
@ConditionalOnBean(name="dataSource")
LocalContainerEntityManagerFactoryBean entityManagerFactory(){
 //...
}
@Bean
@ConditionalOnMissingBean
public MyBean myBean(){
 //...
}

@ConditionalOnProperty

@ConditionalOnProperty注解会根据Spring配置文件中的配置项是否满足配置要求,从而决定是否要执行被其标注的方法。示例代码如下

@Bean
@ConditionalOnProperty(name="alipay",havingValue="on")
Alipay alipay(){
 return new Alipay();
}

@ConditionalOnResource

此注解用于检测当某个配置文件存在,则触发被其标注的方法,下面是使用此注解的代码示例:

@ConditionalOnResource(resources = "classpath:website.properties")
Properties addWebsiteProperties(){
 //...
}

@ConditionalOnWebApplication与@ConditionalOnNotWebApplication

这两个注解用于判断当前的应用程序是否是Web应用程序。如果当前应用是Web应用程序,则使用Spring WebApplicationContext,并定义其会话的生命周期。下面是一个简单的示例:

@ConditionalOnWebApplication
HealthCheckController healthCheckController(){
 //...
}

@ConditionalExpression

此注解可以让我们控制更细粒度的基于表达式的配置条件限制。当表达式满足某个条件或者表达式为真的时候,将会执行被此注解标注的方法

@Bean
@ConditionalException("${localstore} && ${local == 'true'}")
LocalFileStore store(){
 //...
}

@Conditional

@Conditional注解可以控制更为复杂的配置条件。在Spring内置的条件控制注解不满足应用需求的时候,可以使用此注解定义自定义的控制条件,以达到自定义的要求。下面是使用该注解的简单示例:

@Conditioanl(CustomConditioanl.class)
CustomProperties addCustomProperties(){
 //...
}

16、热刷新Bean的配置属性值

@RefreshScope

经过@RefreshScope注解修饰的bean,将被RefreshScope进行代理,用来实现配置、实例热加载,即当配置变更时可以在不重启应用的前提下刷新bean中相关的属性值,咱们先搞懂@Scope注解,因为它是基于Scope的实现

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Scope("refresh")
@Documented
public @interface RefreshScope {
    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
}

@Scope是spring ioc容器的作用域,当bean超出作用域就会被ioc销毁,有下面几种:

  • singleton: 单例模式(默认),spring ioc容器中只有一个实例
  • prototype: 原型模式,每次通过getBean获取该bean就会新产生一个实例
  • request:每次http请求都会产生一个新的bean,同时仅在该http请求的spring 上下文中有效
  • session:在一个http session中,每个bean定义对应
  • global session:

RefreshScope刷新

当配置中心刷新配置之后,有两种方式可以动态刷新Bean的配置变量值

  • 向上下文发布一个RefreshEvent事件
  • Http访问/refresh这个EndPoint

两种方式,最终都会调用RefreshScope#refreshAll

public void refreshAll{
    super.destroy();
    this.context.publishEvent(new RefreshScopeRefreshedEvent());
}

Post Directory