" />

欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳解Spring注解@Validated 失效分析

 更新時(shí)間:2023年04月23日 08:40:46   作者:Jerry_Chan  
這篇文章主要介紹了Spring注解@Validated失效分析,文中有詳細(xì)代碼示例講解了@Validated為何失效,需要的小伙伴可以參考一下

Controller 中使用 @Validated

@Validated 注解的作用這里就不多做介紹了,具體用法在網(wǎng)上應(yīng)該有不少。

在之前使用 MVC 架構(gòu)編碼時(shí),通常是將 @Validated 注解或者 @Valid 配置在 Controller 的方法中,如下代碼所示:

@PostMapping("common/set")
public Response<?> setCommonSetting(@RequestBody @Validated SetCommonSettingReqVO reqVO) {
    //doSomeThings
    return Response.success();
}

所以在配置應(yīng)用層校驗(yàn)時(shí),就想當(dāng)然的按照類似的寫法:

public void addClueTrack(@Validated AddClueTrackCommand command) {
    //doSomeThings
}

結(jié)果可想而知,@Validated 注解并不生效。

@Validated 是怎么生效的?

竟然不生效,那么就開始分析原因。

首先可以很容易想到,竟然能在方法執(zhí)行前就攔截進(jìn)行校驗(yàn),那么大概率是使用動(dòng)態(tài)代理。就和 @Transactional 事務(wù)注解一樣,底層都是基于 AOP 實(shí)現(xiàn)動(dòng)態(tài)代理。

接下來為了印證這個(gè)想法,就是需要深入看看 Spring 實(shí)現(xiàn)的。通過 IDE 可以很方便看到有哪些地方引用了 @Validated 注解:

其中一個(gè)類名一下就引起了我的注意 MethodValidationPostProcessor,熟悉 Spring 的小伙伴應(yīng)該知道,Spring 中有很多 BeanPostProcessor 用于擴(kuò)展 Bean,Aop 便是基于此實(shí)現(xiàn)動(dòng)態(tài)代理的。點(diǎn)進(jìn)去一看,果不其然:

public class MethodValidationPostProcessor extends AbstractBeanFactoryAwareAdvisingPostProcessor
        implements InitializingBean {

    private Class<? extends Annotation> validatedAnnotationType = Validated.class;

    @Nullable
    private Validator validator;

    //...

    @Override
    public void afterPropertiesSet() {
        //創(chuàng)建切點(diǎn)
        Pointcut pointcut = new AnnotationMatchingPointcut(this.validatedAnnotationType, true);
        this.advisor = new DefaultPointcutAdvisor(pointcut, createMethodValidationAdvice(this.validator));
    }

    protected Advice createMethodValidationAdvice(@Nullable Validator validator) {
        //創(chuàng)建攔截器
        return (validator != null ? new MethodValidationInterceptor(validator) : new MethodValidationInterceptor());
    }
}

public class AnnotationMatchingPointcut implements Pointcut {

    private final ClassFilter classFilter;
    private final MethodMatcher methodMatcher;
    
    public AnnotationMatchingPointcut(Class<? extends Annotation> classAnnotationType, boolean checkInherited) {
        //切點(diǎn)只針對(duì)類級(jí)別
        this.classFilter = new AnnotationClassFilter(classAnnotationType, checkInherited);
        this.methodMatcher = MethodMatcher.TRUE;
    }

    //...
}

MethodValidationPostProcessor 中創(chuàng)建了一個(gè)切點(diǎn),過濾類上添加了 @Validated 的 Bean,只要滿足此條件,就會(huì)根據(jù) MethodValidationInterceptor 生成對(duì)應(yīng)的代理類。嗯,和 @Transactional 的實(shí)現(xiàn)原理差不多。

ok,看到這里我就在應(yīng)用服務(wù)實(shí)現(xiàn)上添加了 @Validated 注解,那么此時(shí)注解生效了嗎?哈哈,進(jìn)度條還沒過半呢??

理論上類上加上 @Validated 注解,應(yīng)該會(huì)生成動(dòng)態(tài)代理類的,竟然沒成功進(jìn)行參數(shù)校驗(yàn),我能想到的原因有二:

1. MethodValidationPostProcessor 沒注入到 BeanFactory 中,所以沒生成對(duì)應(yīng)的代理類 2. MethodValidationInterceptor 對(duì)還有其他需要滿足的條件,而目前還未滿足

這里先劇透一下,答案是 2 ??

MethodValidationInterceptor需要滿足什么條件

竟然答案是2,那這里就先講一下 MethodValidationInterceptor,MethodValidationPostProcessor 是怎么注冊(cè)到容器的咱們后面再來講。

ExecutableValidatorpublic class MethodValidationInterceptor implements MethodInterceptor {

    private final Validator validator;

    @Override
    @Nullable
    public Object invoke(MethodInvocation invocation) throws Throwable {

        // Standard Bean Validation 1.1 API
        ExecutableValidator execVal = this.validator.forExecutables();
        Method methodToValidate = invocation.getMethod();
        Set<ConstraintViolation<Object>> result;

        //獲取類本身的實(shí)例(非代理類),請(qǐng)記住這里,這里就是和 Controller 最大的區(qū)別
        Object target = invocation.getThis();
        Assert.state(target != null, "Target must not be null");

        try {
            //執(zhí)行參數(shù)校驗(yàn),校驗(yàn)的是當(dāng)前類,也就是說校驗(yàn)的是 Bean 對(duì)應(yīng)的類
            result = execVal.validateParameters(target, methodToValidate, invocation.getArguments(), groups);
        }
        catch (IllegalArgumentException ex) {
            //doSomeThings
        }
        if (!result.isEmpty()) {
            throw new ConstraintViolationException(result);
        }
        //執(zhí)行方法
        Object returnValue = invocation.proceed();
        //校驗(yàn)返回值
        result = execVal.validateReturnValue(target, methodToValidate, returnValue, groups);
        if (!result.isEmpty()) {
            throw new ConstraintViolationException(result);
        }

        return returnValue;
    }

}

接下來就要看看 ExecutableValidator.validateParameters 這個(gè)方法是如何實(shí)現(xiàn)的,為了方便閱讀,這里我只保留了部分核心代碼。根據(jù)包名我們大概也能猜到 ExecutableValidator.validateParameters 是 hibernate-validator 包提供的方法,而 @Validated 注解是由 Spring 提供的,所以不生效也就正常了。接下來我們繼續(xù)往下走,我這里只貼部分核心的代碼,中間的棧路徑可以根據(jù)以下這個(gè)路徑往下走:

/**
 *  --> org.hibernate.validator.internal.engine.ValidatorImpl#validateParameters  
 *  --> org.hibernate.validator.internal.metadata.BeanMetaDataManager#getBeanMetaData
 *  --> org.hibernate.validator.internal.metadata.BeanMetaDataManagerImpl#createBeanMetaData
 *  --> org.hibernate.validator.internal.metadata.BeanMetaDataManagerImpl#getBeanConfigurationForHierarchy
 *  --> org.hibernate.validator.internal.metadata.provider.MetaDataProvider#getBeanConfiguration
 *  --> org.hibernate.validator.internal.metadata.provider.AnnotationMetaDataProvider#retrieveBeanConfiguration
 *  --> org.hibernate.validator.internal.metadata.provider.AnnotationMetaDataProvider#getFieldMetaData
 *  --> org.hibernate.validator.internal.metadata.provider.AnnotationMetaDataProvider#findPropertyMetaData
 *  --> org.hibernate.validator.internal.metadata.provider.AnnotationMetaDataProvider#findConstraints
 *  --> org.hibernate.validator.internal.metadata.provider.AnnotationMetaDataProvider#findCascadingMetaData
 *  <-- ...
 *  --> org.hibernate.validator.internal.metadata.provider.AnnotationMetaDataProvider#getMethodMetaData
 *  --> org.hibernate.validator.internal.metadata.provider.AnnotationMetaDataProvider#getConstructorMetaData
 *  --> org.hibernate.validator.internal.metadata.provider.AnnotationMetaDataProvider#getClassLevelConstraints
 *  <-- ...
 *  --> org.hibernate.validator.internal.metadata.aggregated.BeanMetaData#hasConstraints
 *  --> org.hibernate.validator.internal.engine.ValidatorImpl#validateParametersInContext
 * 
 */
public class ValidatorImpl implements Validator, ExecutableValidator {
    @Override
    public final <T> Set<ConstraintViolation<T>> validateValue(Class<T> beanType, String propertyName, Object value, Class<?>... groups) {
        Contracts.assertNotNull( beanType, MESSAGES.beanTypeCannotBeNull() );
        sanityCheckPropertyPath( propertyName );
        sanityCheckGroups( groups );

        //獲取 bean 及其父類、超類的
        BeanMetaData<T> rootBeanMetaData = beanMetaDataManager.getBeanMetaData( beanType );

        //判斷該 bean 是否有約束
        if ( !rootBeanMetaData.hasConstraints() ) {
            return Collections.emptySet();
        }

        PathImpl propertyPath = PathImpl.createPathFromString( propertyName );
        BaseBeanValidationContext<T> validationContext = getValidationContextBuilder().forValidateValue( beanType, rootBeanMetaData, propertyPath );

        ValidationOrder validationOrder = determineGroupValidationOrder( groups );

        //校驗(yàn)參數(shù)
        return validateValueInContext(validationContext, value, propertyPath, validationOrder);
    }
    //...
}

當(dāng)我調(diào)試到 rootBeanMetaData.hasConstraints() 時(shí),判斷沒有約束,然后就直接返回了沒有進(jìn)行參數(shù)校驗(yàn)。我就想說看看是如何判斷 Bean 是否有約束的,于是就返回上層進(jìn)入 beanMetaDataManager.getBeanMetaData 中看,結(jié)果發(fā)現(xiàn)里面的代碼有夠復(fù)雜的??

public class AnnotationMetaDataProvider implements MetaDataProvider {
  
    //獲取類上所有的約束條件
    private <T> BeanConfiguration<T> retrieveBeanConfiguration(Class<T> beanClass) {
        //獲取字段上的約束條件
        Set<ConstrainedElement> constrainedElements = getFieldMetaData( beanClass );
        //獲取方法上的約束條件(包括參數(shù)、返回值)
        constrainedElements.addAll( getMethodMetaData( beanClass ) );
        //獲取構(gòu)造函數(shù)
        constrainedElements.addAll( getConstructorMetaData( beanClass ) );
        //獲取類上的約束條件
        Set<MetaConstraint<?>> classLevelConstraints = getClassLevelConstraints( beanClass );
        if ( !classLevelConstraints.isEmpty() ) {
            ConstrainedType classLevelMetaData =
                new ConstrainedType(ConfigurationSource.ANNOTATION, beanClass, classLevelConstraints);
            constrainedElements.add( classLevelMetaData );
        }

        return new BeanConfiguration<>(ConfigurationSource.ANNOTATION, beanClass, constrainedElements, getDefaultGroupSequence( beanClass ), getDefaultGroupSequenceProvider( beanClass ));
    }
	
  //查找約束注解
    protected <A extends Annotation> List<ConstraintDescriptorImpl<?>> findConstraintAnnotations(Constrainable constrainable, A annotation, ConstraintLocationKind type) {

      //如果包含 "jdk.internal" and "java" 下的注解,則直接不進(jìn)行校驗(yàn)
          if ( constraintCreationContext.getConstraintHelper().isJdkAnnotation( annotation.annotationType() ) ) {
          return Collections.emptyList();
      }
      
      List<Annotation> constraints = newArrayList();
      Class<? extends Annotation> annotationType = annotation.annotationType();
      //判斷是否有約束條件,也就我們經(jīng)常配置的 @NotNull,@Min 這類注解
      if ( constraintCreationContext.getConstraintHelper().isConstraintAnnotation( annotationType ) ) {
          constraints.add( annotation );
      }
      //這個(gè)沒用過,暫時(shí)跳過
      else if ( constraintCreationContext.getConstraintHelper().isMultiValueConstraint( annotationType ) ) {
          constraints.addAll( constraintCreationContext.getConstraintHelper().getConstraintsFromMultiValueConstraint( annotation ) );
      }

      return constraints.stream()
          .map( c -> buildConstraintDescriptor( constrainable, c, type ) )
          .collect( Collectors.toList() );
    }
	
    //構(gòu)建級(jí)聯(lián)元數(shù)據(jù)構(gòu)造器,也就是我們常用的 @Valid,在 Bean 中如果我們要對(duì)對(duì)象屬性進(jìn)行校驗(yàn),
    //需要在該屬性上添加 @Valid,此處便是如此
    private CascadingMetaDataBuilder getCascadingMetaData(JavaBeanAnnotatedElement annotatedElement, Map<TypeVariable<?>, CascadingMetaDataBuilder> containerElementTypesCascadingMetaData) {
        return CascadingMetaDataBuilder.annotatedObject( annotatedElement.getType(), annotatedElement.isAnnotationPresent( Valid.class ),
            containerElementTypesCascadingMetaData, getGroupConversions( annotatedElement.getAnnotatedType() ) );
    }
}

順著上面的棧路徑一直往下走,最終發(fā)現(xiàn)最核心的幾個(gè)方法是 getFieldMetaDatagetMethodMetaData、getConstructorMetaData、getClassLevelConstraints,這個(gè)幾方法都是用于獲取約束和級(jí)聯(lián)元數(shù)據(jù)。那么里面到底是怎么獲取約束元數(shù)據(jù)的呢,咱繼續(xù)往里鉆,可以看到最終調(diào)用了 findConstraintAnnotations 獲取約束元數(shù)據(jù),也就是我們平時(shí)用到的 @NotNull,@Min 等注解,通過 getCascadingMetaData 獲取級(jí)聯(lián)元數(shù)據(jù),也就是 @Valid 注解。看到這,是不是很容易就能想到,知道我加上 @Valid 就能成功校驗(yàn)了呢?

于是我嘗試了一波,果然沒問題。嗯~ 長見識(shí)了??。由于時(shí)間有限,ValidatorImpl.validateParametersInContext() 方法我就沒有深入進(jìn)去看了。感興趣的小伙伴可以自行去看看??!??

那么 Controller 為啥直接添加@Validated 或者 @Valid 就可以呢?

明白了在應(yīng)用服務(wù)實(shí)現(xiàn),準(zhǔn)確的說應(yīng)該是普通 Bean 中應(yīng)該怎么配置之 @Validated 和 @Valid 使其生效之后,我就很好奇為啥 Controller 只需要單獨(dú)在方法上配置 @Validated 或者 @Valid 就能成功校驗(yàn)?zāi)兀?/p>

還記得上面通過 IDE 查看應(yīng)用 @Validated 注解的類時(shí),我們發(fā)現(xiàn)了 MethodValidationPostProcessor,還有另外幾個(gè)類一看就很像 Controller 參數(shù)解析相關(guān)的類:

我在這幾個(gè)類上各打了一個(gè)斷點(diǎn),最終進(jìn)入的是 AbstractMessageConverterMethodArgumentResolver。

ok,那就看看他是怎么實(shí)現(xiàn)的,這里只貼了很參數(shù)校驗(yàn)相關(guān)的方法:

public abstract class AbstractMessageConverterMethodArgumentResolver implements HandlerMethodArgumentResolver {

    protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {
        Annotation[] annotations = parameter.getParameterAnnotations();
        for (Annotation ann : annotations) {
            //獲取分組信息
            Object[] validationHints = ValidationAnnotationUtils.determineValidationHints(ann);
            if (validationHints != null) {
                //進(jìn)行校驗(yàn)
                binder.validate(validationHints);
                break;
            }
        }
    }
}

public abstract class ValidationAnnotationUtils {
    @Nullable
    public static Object[] determineValidationHints(Annotation ann) {
        Class<? extends Annotation> annotationType = ann.annotationType();
        String annotationName = annotationType.getName();
        //如果是 @valid 注解直接返回一個(gè)空數(shù)組
        if ("javax.validation.Valid".equals(annotationName)) {
            return EMPTY_OBJECT_ARRAY;
        }
        //如果是 @validated 則返回其分組信息
        Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
        if (validatedAnn != null) {
            Object hints = validatedAnn.value();
            return convertValidationHints(hints);
        }
        if (annotationType.getSimpleName().startsWith("Valid")) {
            Object hints = AnnotationUtils.getValue(ann);
            return convertValidationHints(hints);
        }
        return null;
    }
}

public class DataBinder implements PropertyEditorRegistry, TypeConverter {
    public void validate(Object... validationHints) {
        //此處是關(guān)鍵所在,這里獲取的是參數(shù)?。?!和普通的 Bean 獲取到的卻是 Bean 本身
        Object target = getTarget();
        Assert.state(target != null, "No target to validate");
        BindingResult bindingResult = getBindingResult();
        // Call each validator with the same binding result
        for (Validator validator : getValidators()) {
            if (!ObjectUtils.isEmpty(validationHints) && validator instanceof SmartValidator) {
                ((SmartValidator) validator).validate(target, bindingResult, validationHints);
            }
            else if (validator != null) {
                validator.validate(target, bindingResult);
            }
        }
    }
}

可以看到,對(duì)于 Controller 不論是直接在參數(shù)上加上 @Validated 或者 @Valid 注解,都會(huì)進(jìn)入到校驗(yàn)方法,而且校驗(yàn)的就是參數(shù)!??!而 Bean 校驗(yàn)的卻是 Bean 本身!??!

MethodValidationPostProcessor 和 AbstractMessageConverterMethodArgumentResolver 是怎么被注冊(cè)到 BeanFactory 的?

明白了 @Validated 的攔截實(shí)現(xiàn)的原理后,那么就只剩最后一個(gè)問題了,MethodValidationPostProcessor 和 AbstractMessageConverterMethodArgumentResolver 是怎么被注冊(cè)到 BeanFactory 的。

其實(shí)不用看源碼大概有也能猜到是 Spring Boot 自動(dòng)裝配的。為了印證一下,我還是貼一下源碼:

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ExecutableValidator.class)
@ConditionalOnResource(resources = "classpath:META-INF/services/javax.validation.spi.ValidationProvider")
@Import(PrimaryDefaultValidatorPostProcessor.class)
public class ValidationAutoConfiguration {

    //...
  
    @Bean
    @ConditionalOnMissingBean
    public static MethodValidationPostProcessor methodValidationPostProcessor(Environment environment, @Lazy Validator validator, ObjectProvider<MethodValidationExcludeFilter> excludeFilters) {
        FilteredMethodValidationPostProcessor processor = new FilteredMethodValidationPostProcessor(excludeFilters.orderedStream());
        boolean proxyTargetClass = environment.getProperty("spring.aop.proxy-target-class", Boolean.class, true);
        processor.setProxyTargetClass(proxyTargetClass);
        processor.setValidator(validator);
        return processor;
    }
}

另外就是 AbstractMessageConverterMethodArgumentResolver 的幾個(gè)實(shí)現(xiàn)類,均由 RequestMappingHandlerAdapter 實(shí)例化,而 RequestMappingHandlerAdapter 大家知道有 WebMvcAutoConfiguration 自動(dòng)裝配,時(shí)間原因,這就不看了。

public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
        implements BeanFactoryAware, InitializingBean {
    private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
        List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>(30);

        // Annotation-based argument resolution
        resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
        resolvers.add(new RequestParamMapMethodArgumentResolver());
        resolvers.add(new PathVariableMethodArgumentResolver());
        resolvers.add(new PathVariableMapMethodArgumentResolver());
        resolvers.add(new MatrixVariableMethodArgumentResolver());
        resolvers.add(new MatrixVariableMapMethodArgumentResolver());
        resolvers.add(new ServletModelAttributeMethodProcessor(false));
        resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
        resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice));
        resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));
        resolvers.add(new RequestHeaderMapMethodArgumentResolver());
        resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));
        resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));
        resolvers.add(new SessionAttributeMethodArgumentResolver());
        resolvers.add(new RequestAttributeMethodArgumentResolver());
	
        // Type-based argument resolution
        resolvers.add(new ServletRequestMethodArgumentResolver());
        resolvers.add(new ServletResponseMethodArgumentResolver());
        resolvers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
        resolvers.add(new RedirectAttributesMethodArgumentResolver());
        resolvers.add(new ModelMethodProcessor());
        resolvers.add(new MapMethodProcessor());
        resolvers.add(new ErrorsMethodArgumentResolver());
        resolvers.add(new SessionStatusMethodArgumentResolver());
        resolvers.add(new UriComponentsBuilderMethodArgumentResolver());
        if (KotlinDetector.isKotlinPresent()) {
            resolvers.add(new ContinuationHandlerMethodArgumentResolver());
        }
	
        // Custom arguments
        if (getCustomArgumentResolvers() != null) {
            resolvers.addAll(getCustomArgumentResolvers());
        }
	
        // Catch-all
        resolvers.add(new PrincipalMethodArgumentResolver());
        resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
        resolvers.add(new ServletModelAttributeMethodProcessor(true));
	
        return resolvers;
    }
}

小結(jié)

1、在普通 Bean 中如果要通過注解的方式使用 hibernate-validator 進(jìn)行校驗(yàn)的話,需要在類上添加 @Validated 注解,同時(shí)在方法上添加 @Valid 注解。或者也可以直接使用 @NotNull 等注解。

2、普通 Bean 使用 @Validated 是通過動(dòng)態(tài)代理完成的。具體的攔截器便是他 MethodValidationInterceptor。

3、Controller 層之所以能 @Validated 和 @Valid 二選一,是因?yàn)樾r?yàn)的是參數(shù)本身,而普通 Bean 校驗(yàn)的是 Bean 本身。

4、至此,相信大家就不會(huì)沒配置好 @Validated 導(dǎo)致失效了。

到此這篇關(guān)于詳解Spring注解@Validated 失效分析的文章就介紹到這了,更多相關(guān)Spring注解@Validated失效內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • java性能優(yōu)化之代碼緩存優(yōu)化

    java性能優(yōu)化之代碼緩存優(yōu)化

    這篇文章主要介紹了java性能優(yōu)化之代碼緩存優(yōu)化,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下
    2022-07-07
  • 深入理解Java設(shè)計(jì)模式之享元模式

    深入理解Java設(shè)計(jì)模式之享元模式

    這篇文章主要介紹了JAVA設(shè)計(jì)模式之享元模式的的相關(guān)資料,文中示例代碼非常詳細(xì),供大家參考和學(xué)習(xí),感興趣的朋友可以了解下
    2021-11-11
  • 詳解Java的Hibernate框架中的注解與緩存

    詳解Java的Hibernate框架中的注解與緩存

    這篇文章主要介紹了詳解Java的Hibernate框架中的注解與緩存,Hibernate是Java的SSH三大web開發(fā)框架之一,需要的朋友可以參考下
    2015-12-12
  • java反射使用示例分享

    java反射使用示例分享

    這篇文章主要介紹了java反射使用示例,代碼很簡單,需要的朋友可以參考下
    2014-02-02
  • Java遞歸查找層級(jí)文件夾下特定內(nèi)容的文件的方法

    Java遞歸查找層級(jí)文件夾下特定內(nèi)容的文件的方法

    這篇文章主要介紹了Java遞歸查找層級(jí)文件夾下特定內(nèi)容的文件,本文通過示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-06-06
  • spring boot 測試單元修改數(shù)據(jù)庫不成功的解決

    spring boot 測試單元修改數(shù)據(jù)庫不成功的解決

    這篇文章主要介紹了spring boot 測試單元修改數(shù)據(jù)庫不成功的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-09-09
  • Spring-boot oauth2使用RestTemplate進(jìn)行后臺(tái)自動(dòng)登錄的實(shí)現(xiàn)

    Spring-boot oauth2使用RestTemplate進(jìn)行后臺(tái)自動(dòng)登錄的實(shí)現(xiàn)

    這篇文章主要介紹了Spring-boot oauth2使用RestTemplate進(jìn)行后臺(tái)自動(dòng)登錄的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-07-07
  • 解讀Spring定義Bean的兩種方式:<bean>和@Bean

    解讀Spring定義Bean的兩種方式:<bean>和@Bean

    這篇文章主要介紹了Spring定義Bean的兩種方式:<bean>和@Bean,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-04-04
  • Spring容器注入bean的幾種方式詳解

    Spring容器注入bean的幾種方式詳解

    這篇文章主要介紹了Spring容器注入bean的幾種方式詳解,@Configuration用來聲明一個(gè)配置類,然后使用 @Bean 注解,用于聲明一個(gè)bean,將其加入到Spring容器中,這種方式是我們最常用的一種,需要的朋友可以參考下
    2024-01-01
  • Java SpringBoot開發(fā)小技巧詳解

    Java SpringBoot開發(fā)小技巧詳解

    這篇文章主要介紹了淺談SpringBoot項(xiàng)目如何讓前端開發(fā)提高效率(小技巧),主要介紹了Swagger和Nginx提高效率的方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-09-09

最新評(píng)論