一、参数校验:程序员的“防杠精神器”
假如你的api像个热情的饭店服务员,用户说“随便来点吃的”,你就真给他上了盘空气——这可不妙!参数校验就像是那个会耐心问“要辣的还是不辣的?要牛肉还是鸡肉?”的细心服务员,确保不闹出“我要咖啡你却给我上了杯洗脚水”的尴尬。
springboot的注解校验就像给你的方法参数请了个私人保镖,专门拦截那些不靠谱的输入。没有它?用户传个null过来,你的程序可能就会表演“当场崩溃”的绝活。
二、详细步骤:给代码戴上“紧箍咒”
第1步:先来点“开胃菜”——添加依赖
<!-- pom.xml里加入这个,就像泡面加卤蛋,标配! -->
<dependency>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-starter-validation</artifactid>
</dependency>
第2步:创建个“相亲简历”dto类
import javax.validation.constraints.*;
import java.util.date;
import java.util.list;
/**
* 用户注册dto - 比相亲网站的个人资料要求还严格
*/
public class userregisterdto {
@notblank(message = "用户名不能为空,难道您是无名氏?")
@size(min = 2, max = 20, message = "用户名长度在2-20之间,太短没存在感,太长记不住")
private string username;
@email(message = "邮箱格式不对,这可不是在写情书,随便写写就行")
private string email;
@pattern(regexp = "^(?=.*[a-za-z])(?=.*\\d)[a-za-z\\d]{8,}$",
message = "密码至少8位,包含字母和数字,别再用123456了!")
private string password;
@min(value = 18, message = "未满18岁?小朋友先去写作业")
@max(value = 120, message = "超过120岁?您是老神仙吧")
private integer age;
@notnull(message = "手机号必须填,不然外卖到了找谁?")
private string phone;
@asserttrue(message = "必须接受协议,虽然可能没人看")
private boolean acceptedagreement;
@future(message = "预约时间必须是未来,时光机还没发明呢")
private date appointmenttime;
@size(min = 1, max = 3, message = "最多选3个爱好,您是想成为全能超人吗?")
private list<string> hobbies;
// 此处省略getter和setter,但它们确实存在,我发誓!
// 用lombok的@data也行,但今天咱们保持纯洁的java关系
// 自定义校验注解示例
@validgender
private string gender;
}
第3步:自定义校验注解——打造专属“安检仪”
import javax.validation.constraint;
import javax.validation.payload;
import java.lang.annotation.*;
/**
* 性别校验注解 - 咱们思想很开放,但数据要规范
*/
@target({elementtype.field})
@retention(retentionpolicy.runtime)
@constraint(validatedby = gendervalidator.class)
public @interface validgender {
string message() default "性别必须是男、女或保密,您这是来自火星吗?";
class<?>[] groups() default {};
class<? extends payload>[] payload() default {};
}
/**
* 性别校验器 - 严肃的判官
*/
public class gendervalidator implements constraintvalidator<validgender, string> {
private static final set<string> valid_genders =
new hashset<>(arrays.aslist("男", "女", "保密"));
@override
public boolean isvalid(string value, constraintvalidatorcontext context) {
if (value == null) {
return true; // 用@notnull管非空,咱们只管格式
}
return valid_genders.contains(value);
}
}
第4步:控制器里使用——给api装上“安检门”
import org.springframework.validation.annotation.validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.valid;
import javax.validation.constraints.notblank;
@restcontroller
@requestmapping("/api/users")
@validated // 这个注解让方法参数校验生效,就像给方法吃了“严格丸”
public class usercontroller {
/**
* 注册用户 - 参数校验比丈母娘挑女婿还严格
*/
@postmapping("/register")
public result register(@requestbody @valid userregisterdto userdto) {
// 如果参数校验失败,根本走不到这里
// 就像考试不及格,进不了下一轮面试
return result.success("注册成功,恭喜通过严格审查!");
}
/**
* 方法参数校验 - 连路径变量都不放过
*/
@getmapping("/{id}")
public result getuser(
@pathvariable @min(value = 1, message = "id必须大于0,您这是要找空气用户吗?") long id,
@requestparam @notblank(message = "令牌不能为空,您这是想蒙混过关?") string token) {
return result.success("找到了用户id: " + id);
}
/**
* 分组校验 - 根据不同场景使用不同规则
* 就像上班穿正装,在家穿睡衣,场合要分清
*/
@postmapping("/update")
public result updateuser(@requestbody @validated(userupdategroup.class) userupdatedto dto) {
return result.success("更新成功");
}
}
// 分组接口定义
interface userupdategroup {}
interface usercreategroup {}
第5步:全局异常处理——优雅的“救火队员”
import org.springframework.web.bind.methodargumentnotvalidexception;
import org.springframework.web.bind.annotation.exceptionhandler;
import org.springframework.web.bind.annotation.restcontrolleradvice;
import org.springframework.validation.fielderror;
/**
* 全局异常处理器 - 专业收拾校验失败的烂摊子
*/
@restcontrolleradvice
public class globalexceptionhandler {
/**
* 处理参数校验异常 - 把技术语言翻译成人话
*/
@exceptionhandler(methodargumentnotvalidexception.class)
public result handlevalidationexception(methodargumentnotvalidexception ex) {
// 收集所有错误信息,就像收集考试错题
map<string, string> errors = new hashmap<>();
ex.getbindingresult().getallerrors().foreach(error -> {
string fieldname = ((fielderror) error).getfield();
string errormessage = error.getdefaultmessage();
errors.put(fieldname, errormessage);
});
return result.error(400, "参数校验失败", errors)
.setmessage("您提交的数据有点小问题,请检查后再试哦~");
}
/**
* 处理constraintviolationexception - 方法参数校验失败
*/
@exceptionhandler(constraintviolationexception.class)
public result handleconstraintviolationexception(constraintviolationexception ex) {
list<string> errors = ex.getconstraintviolations().stream()
.map(violation -> violation.getmessage())
.collect(collectors.tolist());
return result.error(400, "参数不合法", errors);
}
}
/**
* 统一返回结果 - 给前端一个标准的“成绩单”
*/
@data
@allargsconstructor
@noargsconstructor
public class result<t> {
private integer code;
private string message;
private t data;
private long timestamp = system.currenttimemillis();
public static <t> result<t> success(t data) {
return new result<>(200, "成功", data);
}
public static <t> result<t> error(integer code, string message, t data) {
return new result<>(code, message, data);
}
public result<t> setmessage(string message) {
this.message = message;
return this;
}
}
第6步:进阶玩法——嵌套校验和集合校验
/**
* 订单dto - 俄罗斯套娃式的校验
*/
public class orderdto {
@notnull(message = "订单信息不能为空")
@valid // 这个注解让嵌套校验生效,就像班主任检查每个学生的作业
private userdto user;
@valid // 集合也要逐个校验,一个都别想逃
private list<@valid orderitemdto> items;
@valid
private addressdto address;
}
/**
* 地址dto - 精确到门牌号
*/
public class addressdto {
@notblank(message = "省份不能空,您这是要寄到外太空?")
private string province;
@notblank(message = "城市不能空")
private string city;
@size(min = 5, max = 100, message = "详细地址5-100字,说清楚点,快递员会感谢您")
private string detail;
}
三、测试一下:看看“保镖”工作认不认真
// 测试controller - 专门捣乱看系统反应
@springboottest
@autoconfiguremockmvc
class usercontrollertest {
@autowired
private mockmvc mockmvc;
@test
void testregisterwithinvaliddata() throws exception {
string invaliduserjson = """
{
"username": "a", // 太短了!
"email": "not-an-email", // 这不是邮箱
"password": "123", // 太弱了
"age": 10, // 未成年!
"phone": null, // 空值
"acceptedagreement": false, // 不同意协议
"appointmenttime": "2020-01-01", // 过去的时间
"hobbies": ["吃饭", "睡觉", "打豆豆", "刷手机", "发呆"] // 爱好太多
}
""";
mockmvc.perform(mockmvcrequestbuilders.post("/api/users/register")
.contenttype(mediatype.application_json)
.content(invaliduserjson))
.andexpect(status().isbadrequest()) // 应该返回400
.andexpect(jsonpath("$.code").value(400))
.andexpect(jsonpath("$.data").exists()) // 错误详情
.anddo(print()); // 打印响应,看看“保镖”怎么怼你
}
}
四、性能优化小贴士
/**
* 校验配置 - 让校验既严格又高效
*/
@configuration
public class validationconfig {
@bean
public validator validator() {
validatorfactory factory = validation.builddefaultvalidatorfactory();
validator validator = factory.getvalidator();
// 可以在这里配置一些自定义设置
// 比如缓存校验器,避免重复创建
return validator;
}
/**
* 快速失败模式 - 发现一个错误就立即返回
* 就像考试发现第一题错了就交卷(不建议真人尝试)
*/
@bean
public validator fastfailvalidator() {
return validation.bydefaultprovider()
.configure()
.addproperty("hibernate.validator.fail_fast", "true")
.buildvalidatorfactory()
.getvalidator();
}
}
总结:参数校验的“人生哲理”
为什么需要参数校验?
- 防止gigo(垃圾进,垃圾出)——输入决定输出质量
- 安全第一:很多安全漏洞都源于不可信的输入
- 用户体验:早发现错误,早提示用户,别让用户猜谜
注解校验的优点:
- 声明式:像贴标签一样简单,告别一堆if-else
- 集中管理:规则在实体类上一目了然
- 易于维护:改注解就能改规则,不用翻业务代码
- 丰富内置:spring提供了几十种注解,总有一款适合你
最佳实践建议:
- 在dto层做校验,保持业务层纯洁
- 错误消息要友好,说人话,别甩技术术语
- 区分必填和非必填字段,别要求用户填宇宙
- 复杂逻辑用自定义校验器,别硬塞到一个注解里
- 记得处理异常,给前端统一的错误格式
总结: 参数校验就像给你的代码请了个:
- 门卫大爷:不合格的一律不让进
- 语文老师:检查格式对不对,内容全不全
- 健身教练:严格要求,不容马虎
- 相声演员:出错时还能用幽默的方式告诉你
严谨的程序员对待输入就像猫奴对待猫主子,既要有爱,也要有规矩! 你的api会因为良好的参数校验而变得更加健壮、安全、用户友好。
以上就是springboot中注解参数校验的实战指南的详细内容,更多关于springboot注解参数校验的资料请关注代码网其它相关文章!
发表评论