@validated注解实现

用法


public interface UserService {
    public  UserModel get2( Integer uuid) ;
}
@Validated      //① 告诉MethodValidationPostProcessor此Bean需要开启方法级别验证支持
@Component
public class UserServiceImpl implement UserService  {
    public @NotNull UserModel get2(@NotNull @Min(value = 1) Integer uuid) { //②声明前置条件/后置条件
        //获取 User Model
        UserModel user = new UserModel(); //此处应该从数据库获取
        if(uuid > 100) {//方便后置添加的判断(此处假设传入的uuid>100 则返回null)
            return null;
        }
        return user;
    }
}

<!--注册方法验证的后处理器-->
<bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor"/>

@validated和@valid不同点

在spring项目中,@validated和@valid功能很类似,都可以在controller层开启数据校验功能。
但是@validated和@valid又不尽相同。有以下不同点:

  1. 分组
  2. 注解地方,@Valid可以注解在成员属性(字段)上,但是@Validated不行
  3. 由于第2点的不同,将导致@Validated不能做嵌套校验
  4. @valid只能用在controller。@Validated可以用在其他被spring管理的类上。
    对于第4点的不同,体现了@validated注解其实又更实用的功能。那就是@validated可以用在普通bean的方法校验上。

@validated的使用注意点

1 @validated和@valid都可以用在controller层的参数前面,但这只能在controller层生效。
2 @validated如果要开启方法验证。注解应该打在类上,而不是方法参数上。
3 方法验证模式下,被jsr303标准的注解修饰的可以是方法参数也可以是返回值,类似如下
public @NotNull Object myValidMethod(@NotNull String arg1, @Max(10) int arg2)
4 @validated不支持嵌套验证。所以jsr303标准的注解修饰的对象只能基本类型和包装类型。其他类型只能做到检测是否为空,
对于对象里面的jsr303标准的注解修饰的属性,不支持验证。

如何实现

看MethodValidationPostProcessor类继承图谱


clipboard.png

实现了InitializingBean和BeanPostProcessor。所以我们重点看生命周期的2个节点方法。
BeanPostProcessor.postProcessAfterInitialization
InitializingBean.afterPropertiesSet
根据生命周期的顺序,先执行afterPropertiesSet方法。

MethodValidationPostProcessor.afterPropertiesSet
    ->Pointcut pointcut = new AnnotationMatchingPointcut(this.validatedAnnotationType, true);//创建了切入点
      this.advisor = new DefaultPointcutAdvisor(pointcut, createMethodValidationAdvice(this.validator));//创建了切面。

然后再看postProcessAfterInitialization。实现是在MethodValidationPostProcessor的祖父类AbstractAdvisingBeanPostProcessor上

AbstractAdvisingBeanPostProcessor.postProcessAfterInitialization
    ->if (bean instanceof Advised)//如果被拦截的bean已经是代理类了。
        if (this.beforeExistingAdvisors)//并且beforeExistingAdvisors=true的时候,
            advised.addAdvisor(0, this.advisor);//把当前切面放到代理类切面的第一位。可以想到beforeExistingAdvisors参数的作用是为了保证验证切面优先于其他的切面。
                                                //并且这里我们也能得知,代理类的切面可能不止1个,相当于代理类里面还存在拦截器链一样。//后文会去看这块的源码
        ->if (isEligible(bean, beanName))//如果被拦截的bean不是一个代理类。先校验一下这个类是否有资格添加上 validated的切面。原理就是扫描这个类上面是否有@validated注解
            ProxyFactory proxyFactory = prepareProxyFactory(bean, beanName);
            proxyFactory.addAdvisor(this.advisor);
            return proxyFactory.getProxy(getProxyClassLoader());//创建代理。
                ->createAopProxy().getProxy(classLoader);
                    ->ProxyCreatorSupport.createAopProxy
                        ->getAopProxyFactory().createAopProxy(this)
                            ->DefaultAopProxyFactory.createAopProxy
                                ->if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {//如果该类有接口或者是代理类了,则直接使用jdk动态代理
                                       return new JdkDynamicAopProxy(config);}
                                   return new ObjenesisCglibAopProxy(config);//否则使用cglib动态代理
                    ->JdkDynamicAopProxy.getProxy
                        ->Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);//这一步找到目标类的所有接口。
                                                //另外根据配置决定是否把Advised接口也加进去。加进去就意味着代理类也是Advised的实现类。那这一步显示是加上了Advised接口
                        ->Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);//看到调用jdk动态代理的api了。

小结:

  • 1 什么时候创建的切面
    在afterPropertiesSet方法中创建切面。
    这里我就有一个疑问了,是不是实现InitializingBean的类都优先被加载呢?不然怎么提前把切面创建出来呢?

  • 2 怎么扫描注解的
    实现BeanPostProcessor的方法,可以拦截到所有bean

  • 3 什么时候给目标类做代理的
    也是在BeanPostProcessor.postProcessAfterInitialization方法中做的,拦截了bean之后,就检测是否类似打上了@validated注解,
    如果有就创建代理,

  • 4 如果目标类已经代理了,怎么办
    如果已经是代理了,就直接添加切面。如果想优先使用验证切面,则需要设置优先级为0.那么怎么设置优先级呢?
    直接把beforeExistingAdvisors属性设置为true即可。

  • 5 代理类的拦截器链是怎么实现的呢?
    根据jdk动态代理的知识,会动态的给UserService 类创建一个实现类(代理类)并实现了接口中的所有方法,当调用接口中的方法其实先经过代理类。
    代理类在把请求传递给InvocationHandler实例。估计InvocationHandler实现里面有维护着一个拦截器链,那么InvocationHandler是怎么设置的呢?
    接着看源码

->JdkDynamicAopProxy.getProxy
    ->Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);//看到调用jdk动态代理的api了。
                                                                //通过this可以看到InvocationHandler实例其实就是JdkDynamicAopProxy类。
JdkDynamicAopProxy.invoke(Object proxy, Method method, Object[] args)
    ->List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);//原来InvocationHandler
        //保存着切面配置信息AdvisedSupport advised。advised里面存储了切面集合,下标代表了优先级。这个是什么时候传递进来的呢?
        //是在创建proxyFactory的时候,把配置信息传递给proxyFactory的 proxyFactory.addAdvisor(this.advisor)。这个时候验证切面就进到了proxyFactory中去了。
        //创建代理InvocationHandler,又把切面配置信息传递过去的。
        ->AdvisedSupport.getInterceptorsAndDynamicInterceptionAdvice
            ->List<Object> chain = this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(this, method, targetClass);//把advised对象向下传递
                ->DefaultAdvisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice( Advised config, Method method, @Nullable Class<?> targetClass)//这一步主要是遍历AdvisedSupport
                                                                                                    //对象的切面集合,即AdvisedSupport.advisors。把所有可以拦截这个方法的切面都装到一个集合中去。
           ->MethodInvocation invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);//组装成一个带有链的执行器
               ->retVal = invocation.proceed();//方法内部拦截器数组下标为0的拦截器调用invoke。
               |    ->Object interceptorOrInterceptionAdvice = this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);//currentInterceptorIndex初始默认是-1,下标+1
               |      InterceptorAndDynamicMethodMatcher dm = (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
               ^      return dm.interceptor.invoke(this);//如果验证拦截器优先调用的话,则这一步一定会进到MethodValidationInterceptor
               |          ->MethodValidationInterceptor.invoke(MethodInvocation invocation)
               |              ->invocation.proceed()---|
               |---------<--------<-----------<--------|
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容

  • 注意LifecycleProcessor接口继承了Lifcycle接口。同时,增加了2个方法,用于处理容器的ref...
    google666s阅读 1,079评论 0 51
  • IOC和DI是什么? Spring IOC 的理解,其初始化过程? BeanFactory 和 FactoryBe...
    justlpf阅读 3,374评论 1 21
  • 本文主要涉及下面10个【SSM】问题 1、说一下Session的工作原理?如果客户端禁用cookie,sessio...
    一只小星_阅读 947评论 0 0
  • 概述 Spring是什么? Spring是一个开源框架,为了解决企业应用开发的复杂性而创建的,但是现在已经不止于企...
    琅筑阅读 1,110评论 2 8
  • 摄影:无尘 无尘:坚持快走锻炼,练习正念冥想,爱好手机摄影,学习时间管理,崇尚简法人生。若把人生旅途比作一本书,我...
    心若了无尘阅读 676评论 3 16