「时光不负,创作不停,本文正在参加2022年中总结征文大赛」
👨🎓作者:Java学术趴
💌公众号:Java学术趴
🚫特别声明:原创不易,未经授权不得转载或抄袭,如需转载可联系小编授权。
🙏版权声明:文章里的部分文字或者图片来自于互联网以及百度百科,如有侵权请尽快联系小编。微信搜索公众号Java学术趴联系小编。
☠️每日毒鸡汤:放心,闭上眼,睡一觉,反正明天也不一定比今天好。
👋大家好!我是你们的老朋友Java学术趴。今天的文章是小编经过了长达两个星期的创作,毫不出牛逼的说,这篇文章完全可以作为一个字典查询。里面几乎包含了你目前你可以用到的所有SpringBoot注解,只要你轻轻的来一下全局搜索,你想要的答案马上就会出现,特别适合在蹲坑、无赖的时候看看哦。这还不赶紧收藏一波,当然,如果感觉对你有帮助的话,希望可以点赞加关注哦,小编会一直陪伴着大家,给大家一直分享技术干货i,在这来先谢谢大家啦,嘻嘻🤭。话不多说,上正文。
先简单介绍一下使用注解开发的重要性:
使用 Spring 开发时,进行配置主要有两种方式,一是 xml 的方式,二是 java config 的方式。Spring 技术自身也在不断的发展和改变,从当前 Springboot 的火热程度来看,java config 的应用是越来越广泛了,在使用 java config 的过程当中,我们不可避免的会有各种各样的注解打交道,所有,注解在实际开发中的地位很高。
1. SpringBootApplication启动类中的注解
1.1 @SpringBootApplication
@SpringBootApplication :它是SpringBoot的启动类注解,其中由三个注解组成。
- SpringBootConfiguration : 加载配置文件的注解。
- EnableAutoConfiguration : 开启自动配置的功能。
- ComponentScan : @ComponentScan 的功能其实就是自动扫描并加载符合条件的组件或 bean 定义,最终将这些 bean 定义加载到容器中。这个注解是可有可无的,因为在开发中我们一般都当个的进行@Bean声明,并不一定要使用这里的批量加载的方式。
1.2 @MapperScan("mapper接口包路径信息")
@MapperScan("mapper接口包路径信息") :扫描指定包下所有的接口类,然后所有接口在编译之后都会生成相应的实现类,也就是针对Mapper进行一个声明。加上这个注解之后,就不用在每个Mapper接口上使用@Mapper注解。
@MapperScan注解多个包。
@SpringBootApplication
@MapperScan("cn.gyyx.mapper")
// 这个注解可以扫描 cn.gyyx.mapper 这个包下面的所有接口类,可以把这个接口类全部的进行动态代理。
public class WardenApplication {
public static void main(String[] args) {
SpringApplication.run(WardenApplication.class,args);
}
}
在application.yml的MyBatis-plus配置:
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
map-underscore-to-camel-case: true
mapper-locations: classpath:cn/gyyx/mapper/xml/*.xml
这里 mapper-locations 配置的是:dao层 xml 文件的路径。
1.3 @EnableOpenApi
@EnableOpenApi : Swagger 3.0 的启用注解,添加上这个注解之后就可以使用Swagger3.0的Api文档。
1.4 @EnableTransactionManagement
@EnableTransactionManagement :开启声明式事务的支持。添加该注解后,Spring容器会自动扫描被 @Transactional注解的方法和类。
所有的数据访问技术都有事务处理机制,这些技术提供了API用来开启事务、提交事务以完成数据操纵,或者在发生错误的时候回滚数据。Spring支持声明式事务,这是基于AOP实现的。
1.5 @EnableAsync
@EnableAsync : 表示项目支持异步方法调用。此添加该注解之后,Spring容器会自动扫描被 @Async注解的方法或者类,对该方法进行异步操作。即该方法和调用者不在一个线程中进行。
1.6 @EnableScheduling
@EnableScheduling : 开启项目对定时任务的支持,此添加该注解之后,Spring容器会自动扫描被 @Scheduled注解的方法,被 @Scheduled注解声明的方法为定时任务,在指定的时间进行自动的执行。
1.7 @EnableCaching
@EnableCaching : 开启基于注解的缓存,声明之后SpringBoot就可以识别@Cacheable注解声明的缓存方法。
2. SpringBoot实体类中的注解
2.1 lombok中的注解
2.1.1 @Getter/@Setter
@Getter/@Setter : 注解在类上, 为类提供读写属性。
2.1.2 @ToString
@ToString : 注解在类上, 为类提供 toString() 方法。
2.1.3 @EqualsAndHashCode
@EqualsAndHashCode : 注解在类上, 为类提供 equals() 和 hashCode() 方法。
在类是继承父类的情况下,EqualsAndHashCode实则就是在比较两个对象的属性;
- 当 @EqualsAndHashCode(callSuper = false) 时不会比较其继承的父类的属性可能会导致错误判断;
- 当 @EqualsAndHashCode(callSuper = true) 时会比较其继承的父类的属性;
2.1.4 @NoArgsConstructor
@NoArgsConstructor :注解在实体类上, 为类提供无参构造方法。
2.1.5 @RequiredArgsConstructor
@RequiredArgsConstructor :注解在实体类上,提供有指定参数的构造方法。
2.1.6 @AllArgsConstructor
@AllArgsConstructor : 注解在实体类上,提供全参构造方法。
2.1.7 @NonNull
@NonNull : 注解在参数上, 如果获取到的这个参数为 null , 就会报出异常, throw new NullPointException(参数名)。 注意 :这个注解是注释在实体类的属性上,而不是实体类上。
2.1.8 @Data
@Data :@Data相当于@Getter @Setter @RequiredArgsConstructor @ToString @EqualsAndHashCode这5个注解的合集。写这一个注解就不用写其中包含的这5个注解了。
2.1.9 @Builder
@Builder : 注解在实体类上, 为类提供一个内部的 Builder。
2.1.10 @Synchronized
@Synchronized : 注解在方法上, 为方法提供同步锁。 注意:这个是注解在方法上,而不是类上。
2.1.11 @Log4j
@Log4j : 注解在各个层,提供一个属性名为 log 的 log4j 的日志对象。之后就可以在任何地方直接使用log.info()打印日志。
以前使用打印日志,必须先创建一个打印日志的Logger对象,调用Logger对象中的info()方法进行打印。
private final Logger logger = LoggerFactory.getLogger(当前类名.class);
logger.info("方法被触发了");
加入了@Log4j注解之后,直接使用 log.info() 方法打印日志信息
log.info("方法被触发了");
2.1.12 @Slf4j
@Slf4j : 注解在各个层,提供一个属性名为 log 的 Slf4j 的日志对象。他的用法和上面的@Log4一样。
2.1.13 @Accessors
@Accessors : Accessor的中文含义是存取器。这个注解是针对实体类中的 getter与setter方法而言的。
包含的属性:
fluent属性:参数值为true/false
- 默认为false,当设置为true时,表示生成的setter和getter方法没有前缀set和get,且setter方法返回的是当前对象。
@Data
@Accessors(fluent = true)
public class Person {
private int id;
private String name;
// 生成的getter和setter方法如下,方法体略
// getter省略了get
public int id() {}
// setter省略了set,并且返回值是:Person对象
public Person id(int id) {}
public String name() {}
public Person name(String name) {}
}
- getter方法不是getId(){},而是id(){};
- setter方法不是void setId(){},而是Person id(int id){}
chain属性:参数值为true/false
- 默认为false (注:但是当fluent为true时,其默认为true) ,当为false的时候,生成的setter方法是void类型;如果设置为true生成的setter方法返回this(当前对象)。
@Data
@Accessors(chain = true)
public class Person {
private int id;
private String name;
// 生成的setter方法如下,方法体略
public Person setId(int id) {}
public Person setName(String name) {}
}
这样写的好处:
-
以前实体类的setter()方法返回值是:void。当给实体类中的多个参数赋值的时候必须分开写,很麻烦。
Person.setName("张三"); Person.setAge(20);
-
使用 @Accessors(chain = true) 之后,setter()方法的返回值是该实例对象,所以此时可以连续调用setter()
Person.setName("张三").setAge(20);
prefix属性:参数为String类型
- 可以指定前缀,生成getter和setter方法时会去掉指定的前缀(遵守驼峰命名)
@Data
@Accessors(prefix = "zqr")
class Person {
private int zqrId;
private String zqrName;
// 生成的getter和setter方法如下,方法体略
public int getId() {}
public void setId(int id) {}
public String getName() {}
public void setName(String name) {}
}
相当于字符串截取功能,在生成getter和setter方法的时候,会自动截取去除指定前缀,然后加上get与set;
如上代码,生成的不是getZqrId(),而是getId()。遵守驼峰命名规则。
2.2 swagger中的注解
2.2.1 @ApiModel
@ApiModel :在实体类上边使用,标记类时swagger的解析类。这样生成的Swagger的Api文档就会存在对该类的详细介绍。
记住两个常用的参数:value以及description
@ApiModel(value="Assets对象", description="关于资产的实体类")
2.2.2 @ApiModelProperty
@ApiModelProperty : 使用在被 @ApiModel 注解的模型类的属性上。这样生成的Swagger的Api文档就会存在对这个属性的详细介绍。
记住几个常用的参数:value、name、dataType、required、example、hidden、readOnly、allowEmptyValue
@ApiModelProperty(value = "光宇编号", example = "TM1234");
2.3 jackson中的注解
2.3.1 @JsonFormat
@JsonFormat :它是一个时间格式化注解,比如我们存储在mysql中的数据是date类型的,当我们读取出来封装在实体类中的时候,就会变成英文时间格式,而不是yyyy-MM-dd HH:mm:ss这样的中文时间,因此我们需要用到JsonFormat注解来格式化我们的时间。
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8");
当不使用这个注解仍然想让时间不解析为英文时间格式,那么就需要在 application.yml 中对jackson进行配置。
配置完之后就可以省略 @JsonFormat 注解了。
当然也可以使用 @JsonFormat(pattern = "yyyy-MM-dd ",timezone = "GMT+8") 来进行格式的覆盖。
2.3.2 @JsonIgnore
@JsonIgnore :此注解是类注解,作用是json序列化时将java bean中的一些属性忽略掉,序列化和反序列化都受影响。也就是在前后端进行数据交互的时候会忽略掉这个注解,不传递关于这个注解的任何参数值。
@JsonIgnore
private Integer mainId;
2.3.3 @JsonSerialize
@JsonSerialize :该注解的作用是解决前后端传递的时候数据转换的,该注解用在属性的 getter() 方法上。
该注解不仅可以转换数据的类型还可以转变前后端传递的数据
2.3.3.1 改变实体类的数据类型
改变前端后传递的数据类型,实体类中的数据类型是Data类型,但是传递到前端的数据类型需要时间戳
// 实体类中的某个属性
/** 订单创建时间 */
@JsonSerialize(using = DateToLongSerializer.class)
private Date createTiem;
在使用 @JsonSerialize 注解的时候,需要一个进行数据转换的工具类
// 这个工具类必须继承 JsonSerializer<T> ,其中这个泛型 T 指的是实体类中该属性的类型
public class DateToLongSerializer extends JsonSerializer<Date> {
// 必须覆盖重写这个方法
@Override
public void serialize(Date date, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
// 这快就是将date日期转换为时间戳进行返回
jsonGenerator.writeNumber(date.getTime() / 1000);
}
}
2.3.3.2 改变实体类传递的数据
数据库中存储的:以1、0来记录性别,其中1是男,0是女。要求返回前端的是男或者女,而不是0或者1
// 存储性别的实体类
@Table(name = "sys_user")
@Data
public class User {
@JsonSerialize(using = GenderSerializer.class)
private String gender;
}
编写进行数据转换的工具类
// 实现数据转换的工具类
/**
性别注解(1:男 0:女)
*/
public class GenderSerializer extends JsonSerializer {
@Override
public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException {
if ("1".equals(value)) {
gen.writeString("男");
} else {
gen.writeString("女");
}
}
}
这个后端返回到前端的就是男或者女,而不是0或者1。当然,如果前端传递到后端是男或女,后端会通过该注解自动解析为0或1,存储到数据库中。
2.3.4 @Excel
@Excel(name = "无效时间", width = 15, format = "yyyy-MM-dd HH:mm:ss")
2.3.5 @DateTimeFormat
@DateTimeFormat(pattren = "yyyy-MM-dd HH:mm:ss")
- @DateTimeFormat :有的时候由前台jsp页面填写一个日期,提交到后台spring mvc的时候,我们希望直接转换成一个Date类型,而不是由一个string 类型接收,然后再通过simpleDateFormat来进行转格式,这样太麻烦了,代码会显的很乱,spring为我们提供了类型转化器,写起来也是很麻烦,我们的需求很简单就是由框架帮我们去自动的转换类型而不是手动的转换,在这样的背景下,我们可以使用@DateTimeFormat注解。
- @JsonFormat : 我们从数据库里面查询到了日期,然后我们想把这个日期自动的变成string类型,这时我们可以使用@JsonFormat注解。就是把Date类型的数据转换为String类型传递给前端。
@DateTimeFormat @JsonFormat 使用注意的地方:
- 注意这两个注解的注解位置
- 注意被注解的字段应该是Date类型
- 一般会用在VO中
- @JsonFormat不要忘了加GMT+8
@DateTimeFormat(pattern="yyyy-MM-dd")
private Date alertDate;
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm")
public Date getAlertDate() {
return alertDate;
}
public void setAlertDate(Date alertDate) {
this.alertDate = alertDate;
}
2.4 mybatisplus中的注解
2.4.1 @TableName
@TableName : 在实体类上指定,指定实体类和数据库表的映射关系。重点:当实体类的类名在转成小写后和数据库表名相同时,可以不指定该注解。
@TableName("alarm_prehandle"); // 参数为表名,该注解在实体类上
2.4.2 @Tableld
@Tableld : 用于指定实体类的某个属性为对应的数据表中的主键。这个注解在实体类中只存在一个。
// value:指定对应表中的字段名,当声明的属性名和表中的字段名一致的时候可以省略不写。
// type:是主键的类型,AUTO代表的主键自增
@TableId(value = "id", type = IdType.AUTO);
private Integer id;
2.4.3 @TableField
@TableField : 解决实体类中属性名和表中的字段名不一致(非驼峰),还有对象中的某个属性不在表中进行忽略。
在开发中一般在 application.yml 对MyBatis进行实体类的驼峰与表中_对应的配置。此时会对实体类中的大写字母转为小写,并且在单词之间拼接 _ ,让其与数据库字段对应。
通过以下代码说明 @TableField 的几种用法
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("tb_user")
// 注意:此时在MyBatis中配置了实体类中的属性驼峰与表中_相对应的配置,所以不一定都要指定@TableField
public class User{
private Integer id;
// @TableField(value = "user_name") 可以省略,因为配置了MyBatis
private String userName;
//不希望password出现在查询结果中,默认为true,还可以指定update、detete、insert
@TableField(select = false)
private String passWord;
// 解决字段名不一致,此时不满足MyBatis配置的驼峰和_对应
// 其实这个有个小技巧,就是你可以自定义一个属性名,然后通过@TableField指定它对应数据库表中的某个字段的值,那么此时这个自定义的属性就存在了数据表中某个字段的值,然后可以传递给前端进行响应操作。
@TableField(value = "email")
private String mail;
// 当address属性在数据库表中不存在,但是我们必须使用这个属性的时候,就可以使用 exist = false 进行忽略,这样在MyBatis-plus解析实体类的时候就会忽略这个属性。否则会报错。
// 如果不使用 exist 进行忽略,那么必须使用value让它对应表中的某个字段才可以。
@TableField(exist = false)//指明该属性在数据库表的字段中不存在
private String address;
}
@TableField完成字段自动填充的操作
实现方式:这个自动填充是添加到实体类中的某个属性之上,是对该属性在进行指定操作的时候完成自动填充
- 在进行插入操作时 :对添加了注解@tablefield(fill = fieldfill.insert) 的字段进行自动填充。
- 对添加了注解 @tablefield(fill = fieldfill.insert_update) 的字段在进行插入和更新时进行自动填充。
/**
* 创建人
*/
// 该属性对应的数据表中的字段(creator_id)会在执行 insert 语句的时候会自动进行填充
@TableField(fill = FieldFill.INSERT)
private long creatorId;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private date gmtCreat;
/**
* 修改人
*/
// 该属性对应的数据表中的字段值会在插入和更新的时候自动填充
@TableField(fill = FieldFill.INSERT_UPDATE)
private long modifierId;
/**
* 修改时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private date gmtModified;
/**
* 是否可用
*/
// 该属性对应的数据表中的字段会在修改的时候自动填充
@TableField(fill = FieldFill.UPDATE)
private boolean availableFlag;
这样我们在具体业务中对实体类进行赋值就可以不用对这些公共字段进行赋值,在执行插入或者更新时就能自动赋值并插入数据库。
重点:那么这些自动填充的数据从何而来????
- 在项目的config包下新建自动填充处理类使其实现接口metaobjecthandler,并且重写其中的方法(这个配置类用于给定自动填充的数据),以下代码可以直接复制粘贴在项目中使用。
package cn.gyyx.config;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.util.Date;
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
// 覆盖重写 insertFill()、updateFill() 方法,代表插入和更新
@Override
public void insertFill(MetaObject metaObject) {
// this此时代表的就是:MetaObject实例对象,metaObject
// 方法的第一个参数是:实体类中的属性名。第二个参数是:自动赋值的值。第三个参数this
this.setFieldValByName("updateDate", new Date(), metaObject);
this.setFieldValByName("createTime", new Date(), metaObject);
this.setFieldValByName("deleteFlag", 0, metaObject);
this.setFieldValByName("operUser", "username", metaObject);
}
@Override
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("updateDate", new Date(), metaObject);
}
}
2.5 实体类参数校验
2.5.1 验证注解介绍
- SpringBoot中提供了可以给实体类上的参数加入校验,对于前端请求的数据进行校验。比如数据的长度、格式、类型、是否为空等等,如果没有通过校验直接报错,大大的减少了在代码中使用if...else进行判断以及防止脏数据对数据库的影响。
- 只要当验证的返回值为false,就直接报错,不会在往下执行。
常用的验证注解
注意:这来要也别注意一下 @NotNull、@NotNull、@NotBlank以及@NotEmpty注解的区别
- @NotNull :传递的所有对象都不能是null,其他的不保证。
- @NotNull : CharSequence, Collection, Map 和 Array 对象不能是 null, 但可以是空集(size = 0)
- @NotEmpty : CharSequence, Collection, Map 和 Array 对象不能是 null 并且相关对象的 size 大于 0
- @NotBlank : 这个注解针对的 String 类型,String 不是 null 且去除两端空白字符后的长度大于 0。
空检查
@Null 验证对象是否为null
@NotNull 验证对象是否不为null, 无法查检长度为0的字符串
@NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.
@NotEmpty 检查约束元素是否为NULL或者是EMPTY.
Booelan检查
@AssertTrue 验证 Boolean 对象是否为 true
@AssertFalse 验证 Boolean 对象是否为 false
长度检查
@Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内
@Length(min=, max=) 验证注解的元素值长度在min和max区间内
日期检查
@Past 验证 Date 和 Calendar 对象是否在当前时间之前
@Future 验证 Date 和 Calendar 对象是否在当前时间之后
@Pattern 验证 String 对象是否符合正则表达式的规则
数值检查,建议使用在Stirng,Integer类型,不建议使用在int类型上,因为表单值为“”时无法转换为int,但可以转换为Stirng为"",Integer为null
@Min 验证 Number 和 String 对象是否大等于指定的值
@Max 验证 Number 和 String 对象是否小等于指定的值
@DecimalMax 被标注的值必须不大于约束中指定的最大值. 这个约束的参数是一个通过BigDecimal定义的最大值的字符串表示.小数存在精度
@DecimalMin 被标注的值必须不小于约束中指定的最小值. 这个约束的参数是一个通过BigDecimal定义的最小值的字符串表示.小数存在精度
@Digits 验证 Number 和 String 的构成是否合法
@Digits(integer=,fraction=) 验证字符串是否是符合指定格式的数字,interger指定整数精度,fraction指定小数精度。
@Range(min=, max=) 验证注解的元素值在最小值和最大值之间
@Range(min=10000,max=50000,message="range.bean.wage")
private BigDecimal wage;
@Valid 递归的对关联对象进行校验, 如果关联对象是个集合或者数组,那么对其中的元素进行递归校验,如果是一个map,则对其中的值部分进行校验.(是否进行递归验证)
@CreditCardNumber信用卡验证
@Email 验证是否是邮件地址,如果为null,不进行验证,算通过验证。
@ScriptAssert(lang= ,script=, alias=)
@URL(protocol=,host=, port=,regexp=, flags=)
@Constraint : 指定自定义注解逻辑类,使用的是反射机制 Class.class
使用这个验证直接的时候,需要在 pom.xml 中加入依赖
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
</dependency>
2.5.2 @@Validated和@Valid的区别
为什么要使用这两个注解,
- 因为在前端传递过来数据可能是大量的数据或者是一个对象,这样如果一个一个的手写注解验证非常的麻烦,此时就需要使用到这两个注解,这两个注解会递归的将对象中的每个实体类属性进行校验,当所有验证成功的时候才会向下执行。
这两个注解的区别:
-
所属的包不同:
- @Valid属于javax.validation包下,是jdk给提供的。
- @Validated是org.springframework.validation.annotation包下的,是spring提供的。
-
@Validated要比@Valid更加强大
-
- @Validated在@Valid之上提供了分组功能和验证排序功能
使用方式:
@RestController
@Slf4j
public class VerifyController {
@PostMapping(value = "/valid")
public void verifyValid(@Validated @RequestBody Person person) {
// ...
}
}
2.5.3 基本使用方式
第一步:创建一个实体类
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.Range;
import javax.validation.constraints.*;
import java.io.Serializable;
import java.util.List;
/**
* @Description TODO PersonBean
* @Author Java学术趴
* @可以自己在每个实体类上随意的添加校验注解
*/
@Data
public class PersonBean implements Serializable {
private static final long serialVersionUID = -8374325179529529802L;
/**
* 年龄
*/
@Range(min = 1, max = 99, message = "年龄必须在1~99之间")
private Integer personAge;
/**
* 姓名
*/
@Length(min = 5, max = 10, message = "用户名长度必须在5~10之间")
private String personName;
/**
* 密码
*/
@Length(min = 5, max = 10, message = "密码长度必须在5~10之间")
@NotBlank(message = "密码不能为空")
private String password;
/**
* 手机号
*/
@Pattern(regexp = "^[1]([3][0-9]{1}|59|58|88|89)[0-9]{8}$", message = "手机号格式有误")
@Length(min = 11, max = 11, message = "手机号必须为11位")
private String personPhone;
/**
* 邮箱
*/
@Email(message = "邮箱格式有误")
private String personEmail;
/**
* 资产
*/
@Pattern(regexp = "^(([1-9]{1}\d*)|([0]{1}))(\.(\d){0,2})?$", message = "金额有误!必须是数字且最多保留两位小数")
private String personMoney;
/**
* 照片
*/
@Size(min = 1, max = 3, message = "集合长度的范围为1~3")
@NotEmpty(message = "集合不能为空")
private List<String> photoList;
}
第二步:Controller
@RestController
@RequestMapping("/person")
@Validated
public class PersonController {
@GetMapping("/get")
public DataResult get(@Range(max = 10, message = "age最大值为10") @RequestParam("age") Integer age,@NotBlank(message = "name不可以为空") @Length(min = 3, message = "name长度最少是3") @RequestParam("name") String name) {
return DataResult.success();
}
@PostMapping("/post")
public DataResult post(@Validated @RequestBody PersonBean person) {
return DataResult.success();
}
}
解释:
- 分别校验 : 如果是get请求的单个参数校验,那么在Controller类上加上 @Validated注解,之后直接在参数前加上相应的注解校验即可。也就是其中的 get 方法。
- 批量校验 :如果是 post请求的一个对象,那么此时我们需要使用 @Validated注解 进行批量校验,因为在实体类中已经给属性加入了相应的验证注解,所以他会使用递归的方式进行逐一的校验。
2.5.3 修改参数校验模式
- SpringBoot默认的是对所有的实体类属性进行验证,之后才会抛出异常,这样效率就会变低,但是其实只要有一个验证失败,那么就代表这个请求失败,直接拒绝这个请求,所以我们创建一个配置类,完成一种新的校验模式:
这个配置文件可以直接复制粘贴到代码中使用。
import org.hibernate.validator.HibernateValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
/**
* @Description TODO 配置Springboot校验模式
* @Author Java学术趴
*/
@Configuration
public class ValidatorConfig {
/**
* validation默认会校验完所有字段,然后返回所有的验证失败信息。
* 可以通过一些简单的配置,开启Fail Fast模式,只要有一个验证失败就立即返回
*/
@Bean
public Validator validator() {
ValidatorFactory validatorFactory = Validation
.byProvider(HibernateValidator.class)
.configure()
.failFast(true)
.buildValidatorFactory();
Validator validator = validatorFactory.getValidator();
return validator;
}
}
2.5.4 定义全局异常拦截,将异常中重要信息返回给前端。
- 如果注解校验失败后端直接抛出异常的,并不会给前端返回任何的数据,我们如果想要返回自定义的验证失败信息就需要再配置下全局异常监听。
/**
* @Description TODO 全局异常管理
* @Author admin
* @Date 2020/12/11
*/
import com.lgy.demo.util.DataResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.*;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public DataResult validateException(MethodArgumentNotValidException e) {
List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
List<String> list = new ArrayList<>();
for (FieldError error : fieldErrors) {
list.add(error.getField() + error.getDefaultMessage());
}
return DataResult.custom(500, "参数有误!", list.get(0));
}
@ExceptionHandler(value = ConstraintViolationException.class)
public DataResult validateException(ConstraintViolationException e) {
Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
List<String> list = new ArrayList<>();
for (ConstraintViolation<?> item : violations) {
list.add(item.getMessage());
}
return DataResult.custom(500, "参数有误!", list.get(0));
}
}
因为上面我们配置了校验模式:只要有一个验证失败就立即返回信息,所以这里返回的都是list.get(0);
接下来我们发送请求验证一下:
get请求
post请求:
测试通过
2.5.5 @Validated 分组验证
- 想要用一个实体类去接收多个controller的参数,但是不同controller所需要的参数校验又有些许不同,而你又不想为这点不同去建个新的类接收参数,这个时候我们需要------参数校验分组。
实现方式:
1. 定义两个分组接口 : 定义两个interface,实现javax.validation.groups.Default接口:
public interface Add extends Default {
}
public interface Update extends Default {
}
2. 在实体类中给属性添加验证注解的时候指定验证的分组
@Data
public class Person {
@NotEmpty(groups = Update.class, message = "更新时候id不能为空")
private Long id;
@NotEmpty(groups = {Add.class,Update.class}, message = "姓名不能为空")
private String name;
}
3. 使用分组进行参数的验证
@RestController
@Slf4j
public class VerifyController {
@PostMapping(value = "/validated/add")
public void add(@Validated(value = Add.class) @RequestBody Person person) {
...
}
@PostMapping(value = "/validated/update")
public void update(@Validated(value = Update.class) @RequestBody Person person) {
...
}
}
分组的原理:
- controller中的@Validated未指定分组,则只会校验实体类中属性未指定分组的值,而注解指定分组的值不会校验。
- controller中的@Validated指定了我们自己定义Add分组,则只会校验实体类中属性指定Add分组的值和未指定任何分组的值,而注解指定Update的值不会校验。
- controller中的@Validated指定了我们自己定义Update分组,可以看到这个分组在两个实体类的属性上都有,那么都会进行验证。
2.5.6 自定义校验注解
即使springboot内置了各种注解去帮助我们校验参数,但是当面对复杂参数校验时,还是不能满足我们的要求,这时候我们就需要自定义校验注解。
现在我们来定义一个校验身份证号的注解,实现方式:
第一步:先定义一个注解
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
/**
* @Description TODO 身份证号校验注解
* @Author Java学术趴
* @Date 2020/12/11
*/
@Documented
@Target({ElementType.PARAMETER, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = IdCardValidate.class)
public @interface IdCard {
String message() default "身份证号码不合法";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
注解类里面前三个参数是固定必须的,直接复制即可,@Constraint(validatedBy = IdCardValidate.class)指定的是下面我们自己创建的校验逻辑实现类。
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
/**
* @Description TODO 身份证校验逻辑
* @Author admin
* @Date 2020/12/14
*/
public class IdCardValidate implements ConstraintValidator<IdCard, String> {
@Override
public void initialize(IdCard constraintAnnotation) {
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
//这里只是举例,此处输入你的校验逻辑,成功返回true,否则返回false
String id = "123456789";
if (value.equals(id)) {
return true;
}
return false;
}
}
使用自定义注解
@IdCard
@NotBlank(message = "身份证为必填项")
private String idCard;
3. SpringBoot中Controller层的注解
3.1 @Controller注解
@Controller : 加在类上面的注解,使得类里面的每个方法都返回一个视图页面。
但是在实际开发中,我们一般只是让后端的方法返回给前端是查询的数据,而不是一个新的视图页面。如果使用@Controller注解必须结合@ResponseBody,让这个方法返回给前端的不是一个视图,而只是给前端传递查询到的数据。
可以把@ResponseBody注解加到Controller类上或者是Controller层的方法上。
- @ResponseBody添加到类上:代表这个类中国所有的方法都返回的数据,而不是视图。
- @ResponseBody添加到方法上:代表只有这个方法返回的是数据,其他没有声明的返回的还是视图。
@Controller
public class HelloController {
@GetMapping(value="/hello")
@ResponseBody
public String say(){//返回json 数据
return "gril";
}
@GetMapping(value="/hello1")
public String say1(){//返回视图
return "sys/index1";
}
为了解决这个麻烦的操作,SpringBoot中提供了@RestController注解解决这个问题,如下:
3.2 @RestController
@RestController :从Spring 4.0以后产生的,用来将json/xml数据发送到前台页面,而不是返回视图页面。它相当于@Controller和@ResponseBody。
@RestController加在类上面的注解,使得类里面的每个方法都将json/xml返回数据加返回到前台页面中。梭所以在实际开发中,我们一般都使用这个注解。
3.3 @RequestMapping("路径信息")
@RequestMapping("路径信息") :@RequestMapping 来映射请求,也就是通过它来指定控制器可以处理哪些URL请求。这个注解可以使用在Controller层的类或者方法上。
@RequestMapping中的参数:
- path : 指定路径,和value没有区别,只是path不可以省略,value可以省略。
3.3.1 注解在Controller类上
- 将 @RequestMapping 注解在 Controller 类上,这时类的注解是相对于 Web 根目录,而方法上的是相对于类上的路径。
- 注意: @RequestMapping("/index") 等同于 @RequestMapping(value = "/index")
@RestController
@RequestMapping("/user")
// @RequestMapping(value = "/user")
public class UserController {
@RequestMapping("/login")
public String login() {
return "success";
}
}
// 此时请求的实际路径是:/user/login
// 在类上的@RequestMapping相当于声明一个根路径,在请求的时候他会把类和方上的路径进行拼接
3.3.2 注解在Controller类的方法上
method属性:
通过method属性来指定请求的类型:有GET(查)、POST(增)、PUT(改)、DELETE(删),由于浏览器表单无法发送 DELETE 和 PUT 请求,如果使用的话需要进行处理,所以我们在开发中一般使用 CET和POST请求方式完成请求任务。
- 通过 @RequestMapping(value="/login",method=RequestMethod.GET) 来指定 login()方法 仅处理通过 GET 方式发来的请求
@RestController
@RequestMapping(path = "/user")
public class UserController {
// 通过 method 属性来指定请求的类型,此时只能使用GET请求访问,使用POST会报错。
@RequestMapping(path = "/login", method=RequestMethod.GET)
public String login() {
return "success";
}
}
- 通过 @RequestMapping(value="/login",method=RequestMethod.POST) 来指定 login()方法 仅处理通过 POST 方式发来的请求
@RestController
@RequestMapping(path = "/user")
public class UserController {
// 通过 method 属性来指定请求的类型,此时只能使用POST请求访问,使用GET会报错。
@RequestMapping(path = "/login", method=RequestMethod.POST)
public String login() {
return "success";
}
}
- 由于在 RequestMapping 注解类中 method() 方法返回的是 RequestMethod 数组,所以可以给 method 同时指定多个请求方式,例如
@RestController
@RequestMapping(path = "/user")
public class UserController {
// 该方法将同时接收通过GET和POST方式发来的请求
@RequestMapping(path = "/login", method={RequestMethod.POST,RequestMethod.GET})
public String login() {
return "success";
}
}
params属性:
- @RequestMapping 的 params 属性,该属性表示请求参数,也就是追加在URL上的键值对,多个请求参数以&隔开,例如:
http://localhost/SpringMVC/user/login?username=kolbe&password=123456
- 则这个请求的参数为username=kolbe以及password=123456,@RequestMapping 中可以使用 params 来限制请求参数,来实现进一步的过滤请求,举个例子:
@Controller
@RequestMapping(path = "/user")
public class UserController {
// 该方法将接收 /user/login 发来的请求,且请求参数必须为 username=kolbe&password=123456
@RequestMapping(path = "/login", params={"username=kolbe","password=123456"})
public String login() {
return "success";
}
}
- 该例中则表示 UserController 中的 login() 方法仅处理 /user/login 发来的请求,且必须带有 username=kolbe&password=123456 的请求参数,否则浏览器将返回HTTP 404的错误。
headers 属性:
- @RequestMapping 的 headers 属性,该属性表示请求头。
- 通过 @RequestMapping 中的 headers 属性,可以限制客户端发来的请求。
@Controller
@RequestMapping(path = "/user")
public class UserController {
// 表示只接收 localhost:8080 发来的请求,不会处理其他请求
@RequestMapping(path = "/login", headers="Host=localhost:8080")
public String login() {
return "success";
}
}
带有占位符的URL
- 带占位符的URL是Spring 3.0 新增的功能,可以通过 @PathVariable 将 URL 中的占位符绑定到控制器的处理方法的参数中,占位符使用{}括起来。
@Controller
@RequestMapping(path = "/user")
public class UserController {
// 当只存在一个参数的时候,可以省略@PathVariable("id")注解,但是后边的参数名必须和{}中的占位符名字一致,否则找不到会报错。
// 当给定 @PathVariable("id") 的时候括号中的参数名字必须和{}中占位符的名字一致。此时后边的参数可以随便定义其他的名字比如:@PathVariable("id") Integer param
@RequestMapping(value="/{id}", method=RequestMethod.GET)
public String show(@PathVariable("id") Integer id) {
return "success";
}
}
在这个控制器中 show() 方法将可以接收 user/1、user/2、user/3等等的路径请求,请求的方法必须为GET,使用 @PathVariable 为应用实现 REST 规范提供了具大的便利条件。
3.4 @PostMapping("路径信息")
- 用于将Http Post 请求映射到特定处理程序方法的注释。具体来讲就是:@PostMapping是一个做为快捷方式的组合注释@RequestMapping(method = RequestMethod.POST)。
- 这个注解相当于: @RequestMapping(value="/{id}", method=RequestMethod.POST)
3.5 @GetMapping("路径信息")
- 用于将Http Get 请求映射到特定处理程序方法的注释。具体来讲就是:@GetMapping是一个做为快捷方式的组合注释 @RequestMapping(method = RequestMethod.GET)。
- 这个注解相当于: @RequestMapping(value="/{id}", method=RequestMethod.GET)
相似组合注解还有:@PutMapping、@DeleteMapping、@PatchMapping分别对用method的PUT、Delete以及Patch
3.6 @Api(tags = "针对这个Controller类的描述")
@Api :这个注解是Swagger中的一个注解,专门用于在Controller类上,针对这个Controller接口类生成一个文档的描述,在之后生成的Swagger的Api文档中会对这个Controller进行介绍。
参数信息:
- tags: 生成的api文档会根据tags分类,直白的说就是这个controller中的所有接口生成的接口文档都会在tags这个list下;tags如果有多个值,会生成多个list,每个list都显示所有接口。
@Api(tags = "列表1")
@Api(tags = {"列表1","列表2"})
- value : 它的作用和 tags 是一样的,只是不可以生成多个list,只能生成一个。
// 用于 Controller 类上
@Api(tags = "客户端通信服务端接口")
3.7 @ApiOperation("针对Controller类中某个方法的描述")
@ApiOperation : 这个也是Swagger中的一个注解,作用在Controller类中的方法上,针对这个接口进行描述,会在Swagger的Api文档中进行记录。
参数信息:
- value: 对这个方法进行描述,value可以省略。
- notes : 用于提示内容。
// 用于 Controller 类中的接口上
@ApiOperation(value = "客户端下载对应脚本", notes = "下载脚本")
这两个Swagger注解用于 Controller 类上,还有 @ApiModel()以及@ApiModelProperty()用于实体类上,注意区分
3.8 @Autowired
@Autowired : spring可以自动帮你把bean里面引用的对象的setter/getter方法省略,它会自动帮你set/get。他会帮你完成对该Bean对象的自动装配。
@Autowired这个注解是属于SpringBoot的
@Autowired自动创建和装配的原理: 默认加在IOC容器中的组件,容器会调用无参构造器创建对象,在进行初始化赋值等操作,如果只有有参构造,Spring会调用有参构造,有参构造函数会自动注入。
@Autowired使用的地方:
- 构造器 : 如果组件只有一个有参构造器,这个有参构造器的@Autowrite可以省略,参数位置组件还是可以自动从容器中获取。
- 方法:@Bean或者方法参数,参数从容器中获取;默认不写@Autowrite效果是一样的,都能自动装配。
- 参数 :可以自动注入。
- 属性 :可以自动注入。
注意:使用 @Autowrite声明的对象一般声明为 private
package com.sue.cache.service;
import org.springframework.stereotype.Service;
@Service
public class TestService1 {
public void test1() {
}
}
package com.sue.cache.service;
import org.springframework.stereotype.Service;
@Service
public class TestService2 {
//自动装配一个 Service 层的实例对象
@Autowired
private TestService1 testService1;
public void test2() {
}
}
@Autowride的使用原理,他是在返回数据的时候使用DI技术实现的。DAO层不用使用@Autowited,因为这层不需要实体类
注意:
-
spring是按照类型装配的,也就是我们所说的
byType
方式。 -
如果要修改为根据
byName
进行自动装配,需要和 @Qualifier() 注解 配套使用@Autowired() @Qualifier("baseDao") private BaseDao baseDao;
byName : 会搜索整个配置文件中的bean,如果有相同名称的bean则自动装配,否则显示异常。(类名) byType : 会搜索整个配置文件中的bean,如果有相同类型的bean则自动装配,否则显示异常。(类的类别)
-
@Autowired注解的
required
参数默认是true,表示开启自动装配,有些时候我们不想使用自动装配功能,可以将该参数设置成false。required = true :开启自动装配(默认) required = false : 关闭自动装配(需手动开启)
在 IDEA 中 @Autowired 存在下划线,但是可以正常使用。这是为什么呢???
- 因为 @Autowired 是基于构造函数的,正确的使用方式应该是使用构造函数的饿方式声明一个类对象。才是@Autowired注解的正确用法。
private final CategoryMapper categoryMapper;
public CategoryServiceImpl(CategoryMapper categoryMapper) {
this.categoryMapper = categoryMapper;
}
但是在实际开发中,我们都直接使用 @Autowired 来简化开发的难度。
@Autowired
private CategoryMapper categoryMapper;
所以他会存在一个提醒的下画波浪线,但是不影响使用。
3.9 @Resource
@Resource :@Resource注解和@Autowired注解一样,都是为了装配Bean,但是两者在使用上又有少许区别。@Resource默认按照名字装配Bean,即会按照name属性的值来找到具有相同id的Bean Definition 并注入。如果@Resource没有指定name属性,则会根据这个将要被注入的属性的名字来进行Bean装配。
- 我们先来定义一个Bean(通过注解的方式定义)
@Component(value = "renlei")
public class Person {
String name;
Integer age;
}
- 然后我们再来定义一个类House,并引用上面的Person,使容器对它自动装配
使用 @Resource(name = "renlei") 中name属性值去对应@Component(value = "renlei") 中的value值
@Component
public class House {
//@Resource的name属性的值与Person类的@Component的value值相对应【都是 renlei】,所以可以进行装配
@Resource(name = "renlei")
private Person p1;
//@Resource的name属性的值与Person类的@Component的value值不对应,一个是relei,一个是person,所以装配失败
@Resource(name = "person")
private Person p1;
//@Reource 没有指定name的值,所以容器会拿这个p1变量的名字去Bean池子中查找id为renlei的bean并装配到这个renlei变量中。装配成功
@Reource
private Person renlei;
// @Reource 没有指定name的值,所以容器会拿这个p1变量的名字去Bean池子中查找id为p1的bean并装配到这个 p1变量中。如果找不到,就按照类型来进行装配,都是Person类,所以装配成功
// 注意:这里存在一个先更具 byName方式匹配之后根据 byType方式匹配,都不成功的时候才会报错。
@Reource
private Person p1;
}
@Resource这个注解是属于 J2EE的。用于属性或者方法上,一般用于属性上。
@Resource注解使用的过程:
-
当启动spring容器的时候,spring容器加载了配置文件
-
在spring配置文件中,只要遇到bean的配置,就会为该bean创建对象
-
在纳入spring容器的范围内查找所有的bean,看哪些bean的属性或者方法上加有@Resource
-
找到@Resource注解以后,判断该注解name的属性是否为""(name没有写)
- 如果没有写name属性,则会让属性的名称的值和spring中ID的值做匹配,如果匹配成功则赋值如果匹配不成功,则会按照类型进行匹配,如果匹配不成功,则报错。
- 如果有name属性,则会按照name属性的值和spring的bean中ID进行匹配,匹配成功,则赋值,不成功则报错。
注意:@Resource先使用 byName 进行匹配,匹配不成功给则使用 byType 方式,如果还是匹配失败,则报错。在实际开发中,我们一般都给 @Service、@Controller以及@Repository 的value属性,所以在项目中使用的 @Resource 注解都是使用的 byType 类型匹配方式。
3.10 @Value
3.10.1 @Value("${...}") -- 配置参数值注入
3.10.1.1 @Value("1")
- 给变量赋一个值。
@Value("张三")
private String name;
// 以上用法相当于
private String name = "张三";
3.10.1.2 @Value("${...}") -- 配置参数值注入
@Value :项目启动时,用于读取配置文件给静态文件赋值。读取SpringBoot的主配置文件中的属性值。
使用方式:
- application.yml配置文件
student:
name:张三
age:20
room:java
- 在代码层获取这个变量值
@Value("${student.name}")
private String name;
// 此时的name等于张三
如果配置参数 student.name 在配置文件中未定义则注入失败,抛出异常IllegalArgumentException
java.lang.IllegalArgumentException:
Could not resolve placeholder 'server.error.path' in value "${server.error.path}"
@Value("{...:{...}}") 嵌套使用
@Value("${student.name:${studnet.age:/student.room}}")
private String info;
- 注入配置参数 student.name 的值。
- 当 student.name 值未定义,注入配置参数 studnet.age 的值。
- 如果 student.name 和 studnet.age 都没有定义,则使用 student.room 的值。
- 如果这三个值都未定义,则报以上的错误。
3.10.2 @Value("#{...}") -- SPEL表达式求值注入
3.10.2.1 @Value("#{1}")
@Value("#{"张三"}")
private String name;
// 此时的name等于张三
- 注入 bean student 属性name的值
- 如果 student 不存在或者其属性 name 不存在,则抛出异常 SpelEvaluationException
org.springframework.expression.spel.SpelEvaluationException:
EL1008E: Property or field 'student' cannot be found on object of type
'org.springframework.beans.factory.config.BeanExpressionContext'
- maybe not public or not valid?
3.10.2.2 @Value("#{...}")
@Value("#{student.name ?: '张三'}")
- 注入 bean student 的属性name值
- 如果student存在并且有属性name,但属性值为null,则注入值“张三”
这个 @Value 注解可以使用在任何层
3.11 @PathVariable
@PathVariable : 映射URL绑定的占位符。
带占位符的URL是 Spring3.0 新增的功能,URL中的 {xxx} 占位符可以通过 @PathVariable("xxx") 绑定到操作方法的入参中。
@RequestMapping("/user/{id}")
public String testPathVariable(@PathVariable("id") String id){
System.out.println("路径上的占位符的值="+id);
return "success";
}
- 当URL中只存在一个一个占位符的时候,可以省略这个@PathVariable注解,此时后面参数名必须和占位符的名字一致。
@RequestMapping("/user/{id}")
// 这个参数名必须是id,和占位符一致才可以识别到。
public String testPathVariable(String id){
System.out.println("路径上的占位符的值="+id);
return "success";
}
- 当给定@PathVariable注解的时候,这个注解中的值必须和占位符名一致,此时后面的参数名可以自定义。
@RequestMapping("/user/{id}")
// @PathVariable("id") 中的参数名必须和占位符一致,此时后面的参数可以任意给定。
public String testPathVariable(@PathVariable("id") String myId){
System.out.println("路径上的占位符的值="+myId);
return "success";
}
- 当存在多个占位符的时候,此时不可以省略 @PathVariable 注解,并且要把其中的参数和占位符相对应。
@RequestMapping("/user/{id}/{name}")
public String testPathVariable(@PathVariable("id") String myId, @PathVariable("name") String myName,){
System.out.println("路径上的占位符的值="+id);
return "success";
}
3.12 @RequestParam
@RequestParam : 这个注解是用于后端接收数据的。接收的参数是来自requestHeader中,即请求头。通常用于GET请求,像POST、DELETE等其它类型的请求也可以使用。比如常见的url:http://localhost:8081/spring-boot-study/novel/findByAuthorAndType?author=唐家三少&type=已完结
@RequestMapping(value = "add",method = RequestMethod.GRT)
public void addPeople(@RequestParam(value = "name", required = fasle,default = "Java学术趴") String name,@RequestParam(value = "age") Integer age){
.....
}
3.13 @RequestBody
@RequestBody :这个注解也是用于后端接收数据的。接收的参数是来自requestBody中,即请求体。通常用于接收POST、DELETE等类型的请求数据,GET类型也可以适用。一般用于处理非 Content-Type: application/x-www-form-urlencoded编码格式的数据,比如:application/json、application/xml等类型的数据。
application/json
类型的数据而言,使用注解@RequestBody可以将body里面所有的json数据传到后端,后端再进行解析。
@RequestMapping(value = "add",method = RequestMethod.GRT)
public void addPeople(@RequestBody People people){
.....
}
在后端的同一个接收方法里,@RequestBody与@RequestParam()可以同时使用,@RequestBody最多只能有一个,而@RequestParam()可以有多个。
3.14 @ExceptionHandler
3.14.1 基本用法
- Spring的@ExceptionHandler可以用来统一处理方法抛出的异常,比如这样:
@ExceptionHandler()
public String handleExeption2(Exception ex) {
System.out.println("抛异常了:" + ex);
ex.printStackTrace();
String resultStr = "异常:默认";
return resultStr;
}
比如上面的handleExeption2()方法,给这个方法加上@ExceptionHandler注解,这个方法就会处理类中其他方法(被@RequestMapping注解)抛出的异常。
3.1.4.2 注解的参数
@ExceptionHandler注解中可以添加参数,参数是某个异常类的class,代表这个方法专门处理该类异常,比如这样:
@ExceptionHandler(NumberFormatException.class)
public String handleExeption(Exception ex) {
System.out.println("抛异常了:" + ex);
ex.printStackTrace();
String resultStr = "异常:NumberFormatException";
return resultStr;
}
此时注解的参数是NumberFormatException.class,表示只有方法抛出NumberFormatException时,才会调用该方法。如果抛出其他的异常的时候,这个方法就不可以接收到这个异常。
3.1.4.3 就近原则
当异常发生时,Spring会选择最接近抛出异常的处理方法。
比如之前提到的NumberFormatException,这个异常有父类RuntimeException,RuntimeException还有父类Exception,如果我们分别定义异常处理方法,@ExceptionHandler分别使用这三个异常作为参数,比如这样:
@ExceptionHandler(NumberFormatException.class)
public String handleExeption(Exception ex) {
System.out.println("抛异常了:" + ex);
ex.printStackTrace();
String resultStr = "异常:NumberFormatException";
return resultStr;
}
@ExceptionHandler()
public String handleExeption2(Exception ex) {
System.out.println("抛异常了:" + ex);
ex.printStackTrace();
String resultStr = "异常:默认";
return resultStr;
}
@ExceptionHandler(RuntimeException.class)
public String handleExeption3(Exception ex) {
System.out.println("抛异常了:" + ex);
ex.printStackTrace();
String resultStr = "异常:RuntimeException";
return resultStr;
}
那么,当代码抛出NumberFormatException时,调用的方法将是注解参数NumberFormatException.class的方法,也就是handleExeption(),而当代码抛出IndexOutOfBoundsException时,调用的方法将是注解参数RuntimeException的方法,也就是handleExeption3()。
3.1.1.4 注解方法的返回值
标识了@ExceptionHandler注解的方法,返回值类型和标识了@RequestMapping的方法是统一的,可参见@RequestMapping的说明,比如默认返回Spring的ModelAndView对象,也可以返回String,这时的String是ModelAndView的路径,而不是字符串本身。
有些情况下我们会给标识了@RequestMapping的方法添加 @ResponseBody,比如使用Ajax的场景,直接返回字符串,异常处理类也可以如此操作,添加@ResponseBody注解后,可以直接返回字符串,比如这样:
@ExceptionHandler(NumberFormatException.class)
@ResponseBody
public String handleExeption(Exception ex) {
System.out.println("抛异常了:" + ex);
ex.printStackTrace();
String resultStr = "异常:NumberFormatException";
return resultStr;
}
这样的操作可以在执行完方法后直接返回字符串本身。
3.1.1.5 错误的操作
使用@ExceptionHandler时尽量不要使用相同的注解参数。
如果我们定义两个处理相同异常的处理方法:
@ExceptionHandler(NumberFormatException.class)
@ResponseBody
public String handleExeption(Exception ex) {
System.out.println("抛异常了:" + ex);
ex.printStackTrace();
String resultStr = "异常:NumberFormatException";
return resultStr;
}
@ExceptionHandler(NumberFormatException.class)
@ResponseBody
public String handleExeption2(Exception ex) {
System.out.println("抛异常了:" + ex);
ex.printStackTrace();
String resultStr = "异常:默认";
return resultStr;
}
两个方法都处理NumberFormatException,这种定义方式编译可以通过,而当NumberFormatException真正被抛出时,Spring会给我们报错:
java.lang.IllegalStateException: Ambiguous @ExceptionHandler method mapped for [class java.lang.NumberFormatException]: {public java.lang.String TestController.handleExeption(java.lang.Exception), public java.lang.String TestController.handleExeption2(java.lang.Exception)}
at org.springframework.web.method.annotation.ExceptionHandlerMethodResolver.addExceptionMapping(ExceptionHandlerMethodResolver.java:102) ~[spring-web-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.web.method.annotation.ExceptionHandlerMethodResolver.<init>(ExceptionHandlerMethodResolver.java:66) ~[spring-web-5.0.5.RELEASE.jar:5.0.5.RELEASE]
3.18 @ModelAttribute
-
@ModelAttribute注解常用在Controller层的方法上,被@ModelAttribute声明的方法在Controller层每个方法执行之前都会执行,因此对于一个Controller层包含多个URL的时候,要谨慎使用。在开发中,我们一般都是把被@ModelAttribute声明的方法单独的提取出来。
3.15 @ControllerAdvice
- @ControllerAdvice是@Controller注解的一个增强,这个注解是Spring里面的东西,可以处理全局异常。当然在Spring Boot中也可以使用,但是Spring Boot中有它全自动化配置的异常处理,因为是全自动化的,因此也可以自己定制,比如定制它的异常页面,异常信息提示,异常视图。需要配合@ModelAttribute一起使用。
@ControllerAdvice
public class MyAdviceException {
//MaxUploadSizeExceededException,这个是异常类,这里可以枚举多个异常
@ExceptionHandler(MaxUploadSizeExceededException.class)
public void myexcept(MaxUploadSizeExceededException e, HttpServletResponse response){
response.setContentType("text/html;charset=utf-8");
PrintWriter writer = null;
try {
writer = response.getWriter();
} catch (IOException ex) {
ex.printStackTrace();
}
writer.write("文件太大,请重新选择");
writer.flush();
writer.close();
}
}
3.16 @ResponseStatus
-
@ResponseStatus的作用就是为了改变HTTP响应的状态码。
3.17 @CrossOrigin
3.17.1 跨域介绍
- @CrossOrigin注解:出于安全原因,浏览器禁止Ajax调用驻留在当前源点之外的资源。比如:当你在一个网页中查看你的银行账户,此时你在另一个页面中访问这个银行账户的时候,此时就禁止这个页面向当这个银行账户发送请求。这样就保证了账户的安全。
- 跨源资源共享(CORS)是由大多数浏览器实现的W3C规范,允许您灵活地指定什么样的跨域请求被授权,而不是使用一些不太安全和不太强大的策略,如IFPAME或JSONP。
3.17.2 使用方式
1. 给controller中的某个方法配置CORS
controller方法的CORS配置,您可以向@RequestMapping注解处理程序方法添加一个@CrossOrigin注解,以便启用CORS(默认情况下,@CrossOrigin允许在@RequestMapping注解中指定的所有源和HTTP方法):
为单独的方法配置跨域请求
@RestController
@RequestMapping("/account") public class AccountController {
@CrossOrigin
@GetMapping("/{id}")
public Account retrieve(@PathVariable Long id) { // ...
}
@DeleteMapping("/{id}")
public void remove(@PathVariable Long id) { // ...
}
}
@CrossOrigin注解中的参数
- origins : 允许可访问的域列表
- maxAge:准备响应前的缓存持续的最大时间(以秒为单位)。
2.为整个controller启用@CrossOrigin
在这个例子中,对于retrieve()和remove()处理方法都启用了跨域支持,还可以看到如何使用@CrossOrigin属性定制CORS配置。
@CrossOrigin(origins = "http://domain2.com", maxAge = 3600)
@RestController
@RequestMapping("/account") public class AccountController {
@GetMapping("/{id}") public Account retrieve(@PathVariable Long id) { // ...
}
@DeleteMapping("/{id}") public void remove(@PathVariable Long id) { // ...
}
}
3. 同时使用controller和方法级别的CORS配置,Spring将合并两个注释属性以创建合并的CORS配置。
@CrossOrigin(maxAge = 3600)
@RestController
@RequestMapping("/account") public class AccountController {
@CrossOrigin(origins = "http://domain2.com")
@GetMapping("/{id}")
public Account retrieve(@PathVariable Long id) {
// ...
}
@DeleteMapping("/{id}")
public void remove(@PathVariable Long id) {
// ...
}
}
3.18 @InitBinder
@InitBinder : 在实际操作中经常会碰到表单中的日期 字符串和Javabean中的日期类型的属性自动转换, 而springMVC默认不支持这个格式的转换,所以必须要手动配置, 自定义数据类型的绑定才能实现这个功能。
@InitBinder用于在@Controller中标注于方法,表示为当前控制器注册一个属性编辑器或者其他,只对当前的Controller有效。
@RequestMapping("test")
@Controller
public class TestController {
@InitBinder
public void InitBinder(WebDataBinder binder){
DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
CustomDateEditor dateEditor = new CustomDateEditor(df, true);
binder.registerCustomEditor(Date.class,dateEditor);
}
@RequestMapping(value="/param",method=RequestMethod.GET)
@ResponseBody
public Map<String,Object> getFormatData(Date date) throws ParseException{
Map<String,Object> map = new HashMap<String, Object>();
map.put("name", "zhangsan");
map.put("age", 22);
map.put("date",date);
return map;
}
}
4. Service层注解
@Service注解一般写在业务层的接口实现类上,而不是接口上。
4.1 @Service
@Service : @Service注解用于类上,标记当前类是一个service类,加上该注解会将当前类自动注入到spring容中。
@Service
public class CourseDAOImpl extends HibernateDaoSupport implements CourseDAO{
...
}
@Service("courseDAOImpl") :这个表示给类命名一个别名,方便注入到其他类中。不加这个参数值的话,默认别名就是当前类名,但是首字母小写。
在实际开发中,我们一般都直接使用 @Sevice
4.2 @Scope
@Scope :Spring IOC 容器中的一个作用域,在 Spring IOC容器中,他用来配置Bean实例的作用域对象。
@Scope的几种作用域:
- singleton 单实例的(单例)(默认) ----全局有且仅有一个实例。
- prototype 多实例的(多例) ---- 每次获取Bean的时候会有一个新的实例。
- reqeust 同一次请求 ----request:每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP request内有效。
- session 同一个会话级别 ---- session:每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP session内有效。
默认的作用域是:singleton(单实例)
5. Mapper层注解
5.1 @Reponsitory
@Reponsitory :@Reponsitory的作用与@Controller,@Service的作用都是把对象交给Spring管理。@Reponsitory是标注在Dao层接口上,作用是将接口的一个实现类交给Spring管理。
注意:
- 使用这个注解的前提是必须在启动类上添加 @MapperScan("Mapper接口层路径") 注解。
这个 @Repository 完全可以省略不写,也完全可以实现自动注入,但是在IDEA中会存在一个红色的波浪线。原因如下:
- Spring配置文件中配置了MapperScannerConfiguer这个Bean,它会扫描持久层接口创建实现类并交给Spring管理。
- SpringBoot的启动类上标注了@MapperScanner,它的作用和上面的MapperScannerConfiguer作用一样。
5.2 @Mapper
@Mapper : 这个注解一般使用在Dao层接口上,相当于一个mapper.xml文件,它的作用就是将接口生成一个动态代理类。加入了@Mapper注解,目的就是为了不再写mapper映射文件。这个注解就是用来映射mapper.xml文件的。
@Mapper使用的原理:
-
Mybatis 会有一个拦截器,会自动的把 @Mapper 注解的接口生成动态代理类。前提是,映射器(Mapper.xml)必须满足动态代理的条件:
- namespace声明的命名空间必须和Dao层接口的全限定类型一致。否者Mapper.xml这个映射器映射不到Dao层的接口类。
- Mapper.xml文件中sql语句的id名必须和接口的方法名一致。否则该sql语句对应不上指定的接口。
注意:
- 在Dao层不要存在相同名字的接口,也就是在Dao不要写重载。因为mapper文件是通过id与接口进行对应的,如果写了两个同名的接口,就会导致mapper文件映射出错。
5.2.1 @Mapper注解和@MapperScan注解的区别
-
@Mapper注解写在每个Dao接口层的接口类上,@MapperScan注解写在SpringBoot的启动类上。
-
当我们的一个项目中存在多个Dao层接口的时候,此时我们需要对每个接口类都写上@Mapper注解,非常的麻烦,此时可以使用@MapperScan注解来解决这个问题。让这个接口进行一次性的注入,不需要在写@Mapper注解
@SpringBootApplication @MapperScan("cn.gyyx.mapper") // 这个注解可以扫描 cn.gyyx.mapper 这个包下面的所有接口类,可以把这个接口类全部的进行动态代理。 public class WardenApplication { public static void main(String[] args) { SpringApplication.run(WardenApplication.class,args); } }
-
@Mapper注解相当于是@Reponsitory注解和@MapperScan注解的和,会自动的进行配置加载。
-
@MapperScan注解多个包,@Mapper只能把当前接口类进行动态代理。
在实际开发中,如何使用@Mapper、@MapperSacn、@Reponsitory注解???
- 在SpringBoot的启动类上给定@MapperSacn注解。此时Dao层可以省略@Mapper注解,当让@Reponsitory注解可写可不写,最好还是写上。
- 当使用@Mapper注解的时候,可以省略@MapperSacn以及@Reponsitory。
建议:
- 以后在使用的时候,在启动类上给定@MapperScan("Dao层接口所在的包路径")。在Dao层上不写@Mapper注解,写上@Reponsitory即可。
5.3 @Select、@Insert、@Delete、@Update
5.3.1 基本用法
@Select :该注解的目的是为了取代mapper.xml中的select标签,只作用于方法上面。此时就不要在写mapper.xml文件了。
@Select注解的源码
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Select
{
String[] value();
}
从上述可以看到两点信息:
- @Select注解只能修饰方法。
- @Select注解的值是字符数组。
所以,@Select注解的用法是这样的:
@Select({ "select * from xxx", "select * from yyy" })
Person selectPersonById(Integer id);
虽然@Select注解的值是字符数组,但是真正生效的应该是最后那条SQL语句。这一点请大家要留意一下。
5.3.2 @Select注解动态SQL拼写(重点)
普通的字符串值,只能实现变量的替换功能,实现简单的SQL语句,如下所示,
@Select("select * from t_person where id = #{id}")
Person selectPersonById(Integer id);
如果要想实现复杂的逻辑判断,则需要使用标签 ,如下所示:
@Select("<script> select * from t_person where id = #{id}
<when test='address !=null'> and address = #{address}
</when> </script>")
Person selectPersonById(Integer id);
其实,标签 注解专用的,其他的注解,例如@Insert、@Update、@Delete等等,都可以使用的。
5.4 @Param
@Param : 动态代理接口向映射文件中传递参数的时候,会使用@Param注解,并且如果动态代理接口使用了@Param这个注解,那么映射文件中的标签中可以不用写parameterType属性,可以直接接收@Param中传递来的参数值。
先来看一下动态代理接口中是怎样写的,如下图:
再来看一下与动态代理接口对应的映射文件中是怎样接收参数的,如下图:
5.4.1 @Param注解基本类型的参数
mapper中的方法:
public User selectUser(@Param("userName") String name,@Param("password") String pwd);
映射到xml中的标签 <select id="selectUser" resultMap="User"> select * from user where user_name = #{userName} and user_password=#{password} </select> 其中where user_name = #{userName} and user_password = #{password}中的userName和password都是从注解@Param()里面取出来的,取出来的值就是方法中形式参数 String name 和 String pwd的值。 重点:当只存在一个参数的时候,此时可以省略这个 @@Param注解,但是两个参数必须使用这个注解。 5.4.2 @Param注解JavaBean对象 SQL语句通过@Param注解中的别名把对象中的属性取出来然后复制 mapper中的方法: public List<User> getAllUser(@Param("user") User u); 映射到xml中的标签
<select id="getAllUser" parameterType="com.vo.User" resultMap="userMapper">
select
from user t where 1=1
and t.user_name = #{user.userName}
and t.user_age = #{user.userAge}
</select>
注意点:
- 当使用了@Param注解来声明参数的时候,SQL语句取值使用#{},${}取值都可以。
- 当不使用@Param注解声明参数的时候,必须使用的是#{}来取参数。使用${}方式取值会报错。
- 不使用@Param注解时,参数只能有一个,可以是一个基本的数据也可以是一个JavaBean。如果是JavaBean最好还是声明一个@Param注解。
6. 其他注解
6.1 @Configuration
@Configuration :用于定义配置类,可替换xml配置文件,被注解的类内部包含有一个或多个被@Bean注解的方法,这些方法将会被AnnotationConfigApplicationContext或AnnotationConfigWebApplicationContext类进行扫描,并用于构建bean定义,初始化Spring容器。
我们相当于把xml配置文件转换成了一个配置类。这个配置类就相当于xml配置文件。
注意:
- @Configuration不可以是final类型;
- @Configuration不可以是匿名类;
- 嵌套的configuration必须是静态类。
@Configuration配置spring并启动spring容器
以前我们需要使用 xml 文件来进行spring的配置:
@Configuration标注在类上,相当于把该类作为spring的xml配置文件中的<beans>
,作用为:配置spring容器(应用上下文)
@Bean注解就是声明其中的标签
@Configuation等价于
@Bean等价于
举例说明:将指定的组件添加到SpringBoot的IOC容器中。
- 主程序类
- 实体类
-
在spring中,我们如果想把这两个类交给Spring容器进行管理,那么必须写xml配置文件。
- 需要在resources目录下创建一个xml配置文件
- 创建bean标签
使用SpringBoot解决这个xml配置文件
现在的Spring Boot已经不需要向以前一样了,有一个注解@Configuration(翻译:配置)可以供使用
- 创建一个类。
- 在这个类的上面加上@Configuration,告诉Spring Boot这是一个配置类。
- 此时这个配置类就相当于xml这个配置文件。其中的@Configuration就相当于xml配置文件中的标签
这个时候在类里边不能写bean标签了需要使用@bean注解,想要构建出user和pet对象需要自己将它创造出来。 @bean:给容器中添加组件,以方法名作为组件的id。返回类型为组件类型,返回的值,就是组件在容器中的实例对象。
注意:@Bean这个注解需要声明在方法名上面,使用@Configuration、@Bean就可以完全代替xml配置文件
这个配置文件会在SpringBoot启动的时候进行注册加载,就会创建出User以及Pet实例对象。
正常的实例名称就是方法的名称,当然可以使用修改这个实例对象的名字。
重点:给容器中注册的组件默认是单实例的,就是说无论我们从容器中获取多少次,这些实例对象都是同一个实例对象。当然被 @Configuration 注解的配置类也是一个组件。
同时,在Spring Boot 5.2之后的@Configuration注解多了一个属性proxyBeanMethods,默认为true(翻译:代理bean的方法)
6.2 @Bean
@Bean:这个注解用于告诉方法,产生一个Bean对象,然后这个Bean对象交给Spring管理。产生这个Bean对象的方法Spring只会调用一次,随后这个Spring将会将这个Bean对象放在自己的IOC容器中;
6.2.1 Bean是什么??
- Java面向对象,对象有方法和属性,那么就需要对象实例来调用方法和属性(即实例化);
- 凡是有方法或属性的类都需要实例化,这样才能具象化去使用这些方法和属性;
- 规律:凡是子类及带有方法或属性的类都要加上注册Bean到Spring IoC的注解;
- 把Bean理解为类的代理或代言人(实际上确实是通过反射、代理来实现的),这样它就能代表类拥有该拥有的东西了
6.2.2 注解分为两类
- 一类是使用Bean,即是把已经在xml文件中配置好的Bean拿来用,完成属性、方法的组装; 比如@Autowired , @Resource,可以通过byTYPE(@Autowired)、byNAME(@Resource)的方式获取Bean;
- 一类是注册Bean,@Component , @Repository , @ Controller , @Service , @Configration这些注解都是把你要实例化的对象转化成一个Bean,放在IoC容器中,等你要用的时候,它会和上面的@Autowired , @Resource配合到一起,把对象、属性、方法完美组装。
6.2.3 @Bean是什么??
- @Bean:Spring的@Bean注解用于告诉方法,产生一个Bean对象,然后这个Bean对象交给Spring管理。 产生这个Bean对象的方法Spring只会调用一次,随后这个Spring将会将这个Bean对象放在自己的IOC容器中;
- SpringIOC 容器管理一个或者多个bean,这些bean都需要在@Configuration注解下进行创建,在一个方法上使用@Bean注解就表明这个方法需要交给Spring进行管理;
- @Bean是一个方法级别上的注解,主要用在 @Configuration注解的类里,也可以用在 @Component注解的类里。添加的bean的id为方法名,当然也可以使用 @Bean(value)中的value属性改变其中的id值。
- 使用Bean时,即是把已经在xml文件中配置好的Bean拿来用,完成属性、方法的组装; 比如@Autowired , @Resource,可以通过byTYPE(@Autowired)、byNAME(@Resource)的方式获取Bean;
- 注册Bean时,@Component , @Repository , @ Controller , @Service , @Configration这些注解都是把你要实例化的对象转化成一个Bean,放在IoC容器中,等你要用的时候,它会和上面的@Autowired , @Resource配合到一起,把对象、属性、方法完美组装;
- @Configuration与@Bean结合使用:@Configuration可理解为用spring的时候xml配置文件中的标签,@Bean可理解为用spring的时候xml配置文件里面的标签;
@Configuration
public class AppConfig {
// 使用@Bean 注解表明myBean需要交给Spring进行管理
// 未指定bean 的名称,默认采用的是 "方法名" + "首字母小写"的配置方式
@Bean
public MyBean myBean(){
return new MyBean();
}
}
public class MyBean {
public MyBean(){
System.out.println("MyBean Initializing");
}
}
6.2.4 @Bean的属性
- value: bean别名和name是相互依赖关联的,value,name如果都使用的话值必须要一致;
- name:bean名称,如果不写会默认为注解的方法名称;
- autowire: 自定装配默认是不开启的,建议尽量不要开启,因为自动装配不能装配基本数据类型、字符串、数组等,这是自动装配设计的局限性,并且自动装配不如依赖注入精确;
- initMethod: bean的初始化之前的执行方法,该参数一般不怎么用,因为完全可以在代码中实现;
- destroyMethod:默认使用javaConfig配置的bean,如果存在close或者shutdown方法,则在bean销毁时会自动执行该方法,如果你不想执行该方法,则添加@Bean(destroyMethod="")来防止出发销毁方法;
如果发现销毁方法没有执行,原因是bean销魂之前程序已经结束了,可以手动close下如下:
AnnotationConfigApplicationContext applicationContext2 = new AnnotationConfigApplicationContext(MainConfig.class);
User bean2 = applicationContext2.getBean(User.class);
System.out.println(bean2);
//手动执行close方法
applicationContext2.close();
6.3 @Component
6.3.1 @Component注解
@component :把普通pojo实例化到spring容器中,相当于xml配置文件中的 。泛指各种组件,就是说当我们的类不属于各种归类的时候(不属于@Controller、@Services等的时候),我们就可以使用@Component来标注这个类。把当前类对象存入到Spring容器中。
- 在没有这个注解之前,我们需要使用xml配置文件来把这个类注册到IOC容器中,代码如下:
<context:component-scan base-package=”com.mmnc”>
# base-package表示为需要扫描的所有子包。
- 此时我们完全可以使用 @Component 注解来完成以上的这个操作,只需要在配置类上加上@Component注解即可。在Spring启动的时候他会自动扫描这个注解将这个配置类交给Spring的IOC容器进行管理。
这个注解和@controller 、@service、@repository注解一样,都是把声明的类交给Spring进行管理。
6.3.2 @Component注解的属性
- @Component注解属性主要指的是value属性。 value属性用于指定bean的id。当我们不写时,它的默认值是当前类名首字母改小写。当然一般情况下是不写的。
6.3.3@Component 和 @Bean 的区别是什么?
-
@Component 注解作用于类,而@Bean注解作用于方法。
-
@Component 注解是通过类路径扫描来自动装配到IOC容器中的,而@Bean注解是将方法返回值作为bean装载到IOC容器中。
-
@Bean注解比@Component注解的自定义性更强,而且很多地方我们只能通过@Bean注解来注册bean。比如当我们引用第三方库中的类需要装配到Spring容器时,则只能通过@Bean来实现而不能通过@Component注解来实现(其实很好理解:你总不能在第三方类上加@Component注解吧)。
6.4 @Scheduled(cron = "0/30 * * * * ?")
6.4.1 基础概念
@Scheduled : 这个注解是spring boot提供的用于定时任务控制的注解,主要用于控制任务在某个指定时间执行,或者每隔一段时间执行.使用这个注解的前提是需要在SpirngBoot的启动类上加入 @EnableScheduling注解。
@Scheduled使用在定时任务的方法上,@EnableScheduling使用在SpringBoot启动类上。
6.4.2 @Scheduled属性
配置@Scheduled中执行的时间有三种方式:cron表达式、fixedRate、fixedDelay。
其中还存在一个针对首次执行定时任务进行设置的属性:initialDelay
6.4.2.1 corn属性
corn属性: 使用corn表达式可以指定定时任务执行的指定时间。
cron是@Scheduled的一个参数,是一个字符串,以5个空格隔开,只容许6个域(注意不是7个,7个直接会报错),分别表示秒、分、时、日、月、周。
cron通配符
cron使用的普通示例
@Scheduled(cron = "0 * * * 1 SAT") //每一年的1月的全部周六的全部0秒时间执行
@Scheduled(cron = "0 0 0 1 Jan ?") //每一年的1月的1日的0时0分0秒执行
cron支持占位符的使用
如果在SpringBoot的配置文件中存在常量 : cron = 2 2 2 2 2 ?
可以使用占位符的方式获取到这个常量,直接使用即可,相当于@Value注解。
@Scheduled(cron = "${cron}") // 表示每一年的二月二号的两时两分两秒执行
6.4.2.2 fixedRate属性
- fixedRate表示自上一次执行时间以后多长时间执行,以ms为单位。参数值是一个int类型的数据。
@Scheduled(fixedRate = 1000 * 5) // 自上次执行以后5秒再执行
6.4.2.3 fixedRateString属性
- 这个属性和fixedRate属性是一样的。只是这个属性的值是字符串类型,而不是int类型。并且这个属性支持占位符。
@Scheduled(fixedRateString = "1000") // 自上次执行1秒再执行
若在配置文件中有相应的属性,能够用占位符获取属性,如在application.yml中有 interval=2000
@Scheduled(fixedRateStirng="${interval}") // 自上次执行2秒后在执行
6.4.2.4 fixedRate属性
fixedDelay与fixedRate有点相似,不过fixedRate是上一次开始以后计时,fixedDelay是上一次结束以后计时,也就是说,fixedDelay表示上一次执行完毕以后多长时间执行,单位也是ms,他是int类型
@Scheduled(fixedDelay = 1000 * 3600 * 12) //上一次执行完毕后半天后再次执行
6.4.2.4 fixedDelayString属性
与fixedDelay相似,属性值是字符串类型的数据,也是支持占位符。
@Scheduled(fixedDelayString = "${fixedDelay}")
6.4.2.4 initialDelay属性
initialDelay表示首次延迟多长时间后执行,单位ms,以后按照cron/fixedRate/fixedRateString/fixedDelay/fixedDelayString指定的规则执行,须要指定其中一个规则。属性值是int类型。
注意:这个属性只针对第一次执行这个定时任务生效。
@Scheduled(initialDelay=1000,fixedRate=1000) //首次运行延迟1s
6.4.2.4 initialDelayString属性
与initialDelay相似,不过属性值是字符串,支持占位符。
@Scheduled(initialDelayString = "${initialDelay}",cron = "0 0 0 14 4 ?")
//按照配置文件initialDelay指定的时间首次延迟,并于每一年4月14日0时0分0秒执行
重点:initialDelay、initialDelayString都不能和cron一起使用
6.6 @EnableCaching
@EnableCaching : 这个注解是spring framework中的注解驱动的缓存管理功能。自spring版本3.1起加入了该注解。如果你使用了这个注解,那么你就不需要在XML文件中配置cache manager了。
当你在配置类@Configuration上使用@EnableCaching注解时,会触发一个post processor,这会扫描每一个spring bean,查看是否已经存在注解对应的缓存。如果找到了,就会自动创建一个代理拦截方法调用,使用缓存的bean执行处理。
@EnableCaching //开启缓存
@Configuration //配置类
public class RedisConfig extends CachingConfigurerSupport {
...
}
6.5 @Cacheable(cacheNames = "detectionDetail",key = "#ip")
6.5.1 基础用法
@Cacheable :该标注在方法上,表示该方法的结果需要被缓存起来,缓存的键由 keyGenerator 的 策略决定,缓存的值的形式则由serialize 序列化策略决定 ( 序列化还是 json 格式 ) ;标注上该注解之 后,在缓存时效内再次调用该方法时将不会调用方法本身而是直接从缓存获取结果。
@Cacheable作用:把方法的返回值添加到Ehcache缓存中。以后查询相同的数据,直接从缓存中取,不需要调用方法
@Override
@Cacheable(cacheNames = "CategoryLisxxxtByParentId", key = "#parxxxentId")
public List<Category> listByPaxxrentId(Long parentId) {
return categoryMapper.listBxxyParentId(parentId,0l);
}
// 此时会把返回的List集合数据存储到缓存中,之后在查询相同数据的时候,直接从缓存中获取而不执行这个方法。
重点:
- 其中value表示该方法返回的参数的缓存存在那个Cache里(即为缓存块的名字)
- 缓存结果以一个键值对存放在缓存中,注解中key即为键值对的key;value即为方法返回的结果
- key用双引号,里面#加上方法的参数:获取方法的参数, ''表示在参数之间添加分隔符。
- 缓存中存储数据的结构是:key--value的形式
6.5.2 属性:
- cacheNames/value :用来指定缓存组件的名字,将方法的返回结果放在哪个缓存中,可以是数组的方式,支持指定多个缓存。
// 指定单个缓存的名字
@Cacheable(cacheNames = "streamUrl", key = "#eventId", unless = "#result == null")
// 指定多个缓存的名字,以数组的形式指定
@Cacheable(cacheNames = {"streamUrl", "test"}, key = "#eventId", unless = "#result == null")
- key :缓存数据时使用的 key。默认使用的是方法参数的值。可以使用 spEL 表达式去编写。
// 这里必须使用spEL表达式指定
@Cacheable(cacheNames = "detectionDetail",key = "#ip")
public List<AssetsDetectionScriptVo> detectionDetail(String ip) {
...
}
- keyGenerator: key 的生成器,可以自己指定 key 的生成器,通过这个生成器来生成 key。
// 使用自定义的key生成器自动生成key值
@Cacheable(value = "ip", keyGenerator = "cacheKeyGenerator")
public IPResponse query(String ip) {
return ipAPI.query(ip);
}
@Slf4j
@Component
// 以下是自定义key生成器的规则
public class CacheKeyGenerator implements KeyGenerator {
@Override
public Object generate(Object target, Method method, Object... params) {
//...
return key.toString();
}
}
这样放入缓存中的 key 的生成规则就按照你自定义的 keyGenerator 来生成。
注意:
key 和 keyGenerator属性在使用的时候一般二选一。
- condition:符合条件的情况下才缓存。方法返回的数据要不要缓存,可以做一个动态判断。
// 表示 id 大于 1 才进行缓存,把返回的结果存储到 stramUrl 的缓存中。小于1的不会进行缓存。
@Cacheable(cacheNames = "streamUrl", condition = "#id > 1")
- unless:否定缓存。当 unless 指定的条件为 true ,方法的返回值就不会被缓存。当然你也可以获取到结果进行判断。(通过 #result 获取方法结果)
// #result可以接收到这个方法的返回值,对这个返回值进行判断。当返回的数据为nnull的时候不做缓存。
@Cacheable(cacheNames = "streamUrl", key = "#eventId", unless = "#result == null")
- sync :是否使用异步模式。默认是方法执行完,以同步的方式将方法返回的结果存在缓存中。
6.5.3 spEL 编写 key
前面说过,缓存的 key 支持使用 spEL 表达式去编写,下面总结一下使用 spEL 去编写 key 可以用的一些元数据:
6.7 @CacheEvict
6.7.1 基础用法
@CacheEvict : 该注解是用来标注在需要清除缓存元素的方法或类上的。当标记在一个类上时,表示其中所有的方法的执行都会触发缓存的清除操作。当标记在一个方法上的时候,只有该方法会触发清除缓存操作,不会影响其他的方法。
6.7.2 属性
@CacheEvict可以指定的属性有value、key、condition、allEntries和beforeInvocation。其中value、key和condition的语义与@Cacheable对应的属性类似。
- value表示清除操作是发生在哪些Cache上的(对应Cache的名称)。
- key表示需要清除的是哪个key,如未指定则会使用默认策略生成的key。
- condition表示清除操作发生的条件。
下面我们介绍这个注解特有的两个属性:
- allEntries属性 :allEntries是boolean类型,表示是否需要清除缓存中的所有元素。默认为false,表示不需要。当指定了allEntries为true时,清除缓存中的所有元素,Spring Cache将忽略指定的key(也就是当指定allEntries属性为true的时候就不需要给定key属性,没有必要)
大多数情况下我们都是直接删除缓存中的全部元素,而不是使用key一个个的删除缓存中的数据。
- beforeInvocation属性 : 清除操作默认是在对应方法成功执行之后触发的,即方法如果因为抛出异常而未能成功返回时也不会触发清除操作。使用beforeInvocation可以改变触发清除操作的时间,当我们指定该属性值为true时,Spring会在调用该方法之前清除缓存中的指定元素。
6.8 @PostConstruct 和 @PreDestroy
6.8.1 基础用法
- javaEE5引入了@PostConstruct和@PreDestroy两个作用于Servlet生命周期的注解,实现Bean初始化之前和销毁之前的自定义操作。
6.8.2 使用场景
- 在项目中主要是在Servlet初始化之前加载一些缓存数据以及测试定时任务等
6.8.3 使用该注解的特点
- 只有一个非静态方法能使用此注解
- 被注解的方法不得有任何参数
- 被注解的方法返回值必须为void
- 被注解方法不得抛出已检查异常
- 此方法只会被执行一次
@PostConstruct
public void heartbeatAlarm() {
....
}
6.8.4 注意
使用此注解时会影响服务启动时间。服务启动时会扫描WEB-INF/classes的所有文件和WEB-INF/lib下的所有jar包。
6.8.5 @PostConstruct注解的用法
@PostConstruct是java5的时候引入的注解,指的是在项目启动的时候执行这个方法,也可以理解为在spring容器启动的时候执行,可作为一些数据的常规化加载,比如数据字典之类的。
数据字典:也就是在项目启动的时候直接返回给前端的数据,不需要前端发送请求获取。直接在启动项目的时候就会把这些数据返回给前端,然后前端进行显示。
被@PostConstruct修饰的方法会在服务器加载Servle的时候运行,并且只会被服务器执行一次。PostConstruct在构造函数之后执行。
项目启动时的加载顺序:
服务器加载Servlet -> servlet 构造函数的加载 -> postConstruct ->init(init是在service 中的初始化方法. 创建service 时发生的事件.) ->Service->destory->predestory->服务器卸载serlvet
spring中Constructor(构造函数)、@Autowired、@PostConstruct的顺序:
Constructor >> @Autowired >> @PostConstruct