造个轮子,我学到了什么

14次阅读

共计 3486 个字符,预计需要花费 9 分钟才能阅读完成。

阅读原文:造个轮子, 我学到了什么
听说的最多的是不是“不要重复的造轮子”?不要被这句话蒙骗了,这句话应该还没说完整,在什么情况下不要造轮子? 实际项目中由于工期和质量原因,肯定不希望你造轮子,你造轮子花费时间且质量不如现有的轮子。
但是!不造轮子怎么去装 X!不造轮子怎么去了解其中原理!不造轮子怎么成长!
那在造参数校验器轮子的过程中我学到了什么呢?

注解的定义与使用
反射的应用
Spring AOP 的使用
异常的抛出与处理

造之前的规划
雄心勃勃的规划,开干!我一定比 hibernate validator 做的好!
我要支持:

属性验证
方法参数验证
方法验证
方法内主动验证

注解
你初见注解时,是不是有种疑惑?为什么在某个类或方法属性上添加一个注解,它就能拥有某种功能呢?那么我将为你慢慢解开这个迷惑。
注解就相当于一个标签,它本身并没有任何功能性,只是打个标签说明一下这是什么。那它怎么实现的某些功能呢?这就要说说反射了,只有注解和反射双剑合璧,才能发挥它的功效。我们先说注解,后说反射。
如何定义一个注解
格式
自定义注解的格式为:
public @interface 注解名{注解体}

@interface 用来声明一个注解,并自动继承 java.lang.annotation.Annotation 接口。
注解体中的类似方法定义的,我们称为注解中的元素。

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface NotNull{

String value() default “”;
}
注解元素
格式:权限修饰符 数据类型 元素名() default 默认值
权限修饰符:只能 public 和 default(默认)
返回值类型:8 种基本数据类型和 String,Class,enum,Annotaion 及他们的数组
默认值限制:编译器对元素的默认值有些挑剔,元素的值不能为 null 并且不能有不确定的默认值(即要么有默认值,要么使用时提供值),所以一般我们在定义注解时便加上默认值。
元注解
定义注解一定要使用到 Java 给我们提供的四种元注解,用于注解其他注解的。
@Target
@Retention
@Documented
@Inherited

我们重点关注 @Target 和 @Retention.
@Target
说明定义的注解所作用的范围(可以用于修饰什么)。
取值(ElementType)有:


意义

CONSTRUCTOR
构造器声明

FIELD
属性声明(包括 enum 实例)

LOCAL_VARIABLE
局部变量声明

METHOD
方法声明

PACKAGE
包声明

PARAMETER
参数声明

TYPE
用于描述类、接口(包括注解类型) 或 enum 声明

@Retention
表示需要在什么级别保存该注释信息,用于描述注解的生命周期(被描述的注解在什么范围内有效)
取值 (RetentionPoicy) 有:


意义

SOURCE
在源文件中有效(即源文件保留)

CLASS
在 class 文件中有效(即 class 保留)

RUNTIME
在运行时有效(即运行时保留)

自定义的注解
参数校验定义的常用注解:

注解
意义

NotNull
参数不能为空

On
数值的范围

OnMax
最大值不能超过

OnMin
最小值不能低于

Email
邮箱格式

反射
反射中牵涉的类有 Class,Method,Parameter,Annotation,Field


获取方式

Class
Class.forName(“”); clazz.getClass(); Type.class;

Method
clazz.getMethods();

Parameter
method.getParameters(); constructor.getParameters();

Annotation
clazz.getAnnotations();method.getAnnotations();field.getAnnotations()

Field
clazz.getFields();

用好反射的关键在于了解反射的 API,之后我会单独一篇讲下我们常用的反射 API。
Spring AOP 的使用
我将借助 Spring AOP 来实现找到这些注解的功能。我这里只讲讲浅显一点的,因为很多人对于 Spring AOP 的使用还不了解。这个轮子是基于 Spring Boot 构建,所以我只讲声明式编程,就是注解实现的。
使用比较简单,只需三步走:

定义切面类
指定切入点
定义通知类型

@Component // 声明这是一个组件
@Aspect // 声明这是一个切面
public class ServiceAspect {

// 定义切入点,没有方法体
@Pointcut(“@annotation(定义的注解)”)
public void pointcut(){}

/*
* 前置通知, 使用 pointcut()上注册的切入点
*
* @param joinPoint 接受 JoinPoint 切入点对象, 可以没有该参数
*/
@Before(“pointcut()”)
public void before(JoinPoint joinPoint){
}

// 后置通知
@After(“pointcut()”)
public void after(JoinPoint joinPoint){
}

// 环绕通知
@Around(“pointcut()”)
public void around(JoinPoint joinPoint){
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
// 反射就此打开序幕
}

// 后置返回通知
@AfterReturning(“pointcut()”)
public void afterReturn(JoinPoint joinPoint){

}

// 抛出异常后通知
@AfterThrowing(pointcut=”pointcut()”, throwing=”ex”)
public void afterThrow(JoinPoint joinPoint, Exception ex){
}

}
AOP 底层原理是使用动态代理,动态代理有 JDK 动态代理和 cglib 动态代理,这里暂不细说了。
异常
自定义异常类
public class FastValidatorException extends RuntimeException {
public FastValidatorException(String message) {
super(message);
}
}
设计时有两种失败模式:快速失败和安全失败
当参数校验,不符合要求时,快速失败将直接抛出此异常,安全失败将收集所有失败返回。
如:
private void emptyResult(String fieldName) {
if (isFailFast) {
throw new FastValidatorException(fieldName + “ 不能为空 ”);
} else {
formatResult(fieldName + “ 不能为空 ”);
}
}

private void formatResult(String msg) {
if (!msg.isEmpty()) {
result.getErrors().add(msg);
}
}

处理异常
如果使用快速失败模式,那么使用者将要对异常做全局统一处理。
将参数异常类信息,封装成比较友好的信息给前端。
@RestControllerAdvice
public class ErrorHandler {

@ExceptionHandler(FastValidatorException.class)
public JSONResult handle(FastValidatorException ex) {
return new JSONResult(ex.getMessage(), ex.getStatus());
}
}
造之后的感想
还是 hibernate validator 做的好!(捂脸哭) 我服!
but
造轮子能迫使我去了解更多的知识点,能迫使我去了解轮子的原理,也能加深我对知识的理解,顺便还能吹吹!做的过程中你会思考如何优化它,一遍遍的推倒重来,会想到用怎么来解耦?用什么提高扩展性,灵活性?
造轮子的意义在于能让你不断的思考和学习。无论造的好坏,行动就好。
轮子地址:https://github.com/flyhero/fa… 忘不吝指教!
发现这个面试视频不错,分享给大家,公众号回复:面试视频

更多精彩技术文章尽在微信公众号:码上实战

正文完
 0