- 使用 Spring Initializr 创建 Spring Boot 应用程序
- 在Spring Boot中配置Cassandra
- 在 Spring Boot 上配置 Tomcat 连接池
- 将Camel消息路由到嵌入WildFly的Artemis上
上一篇我们对DataBinder的源码进行了详细的分析,下面我们对DataBinder的实现子类来做一下具体分析:
通过继承树可以看出,DataBinder的首席大弟子是WebDataBinder,以Web打头,我们大概就可以猜到,该类是用在Web请求参数到具体JavaBean属性的绑定工作中的。
单从WebDataBinder来说,它对父类进行了增强,提供的增强能力如下:
下面我们就来看看这些增强功能的具体实现吧:
WebDataBinder中提供了两个标记符合,当请求参数中存在这两个标记符合时,会进行特殊处理:
//如果请求参数中有以_开头的,那么会取请求参数对应类型的默认值
public static final String DEFAULT_FIELD_MARKER_PREFIX = "_";
//如果请求参数中有以!开头的,那么会去请求参数对应值作为默认值
public static final String DEFAULT_FIELD_DEFAULT_PREFIX = "!";
@Nullable
private String fieldMarkerPrefix = DEFAULT_FIELD_MARKER_PREFIX;
@Nullable
private String fieldDefaultPrefix = DEFAULT_FIELD_DEFAULT_PREFIX;
直接讲标记的作用,会是一头雾水,所以下面我们来看看这些标记具体的应用:
@Override
protected void doBind(MutablePropertyValues mpvs) {
checkFieldDefaults(mpvs);
checkFieldMarkers(mpvs);
adaptEmptyArrayIndices(mpvs);
super.doBind(mpvs);
}
protected void checkFieldDefaults(MutablePropertyValues mpvs) {
String fieldDefaultPrefix = getFieldDefaultPrefix();
if (fieldDefaultPrefix != null) {
PropertyValue[] pvArray = mpvs.getPropertyValues();
for (PropertyValue pv : pvArray) {
// 若你给定的PropertyValue的属性名确实是以!打头的 那就做处理如下:
// 如果JavaBean的该属性可写 && mpvs不存在去掉!后的同名属性,那就添加进来表示后续可以使用了(毕竟是默认值,没有精确匹配的高的)
// 然后把带!的给移除掉(因为默认值以已经转正了~~~)
// 其实这里就是说你可以使用!来给个默认值。比如!name表示若找不到name这个属性的时,就取它的值~~~
// 也就是说你request里若有!name保底,也就不怕出现null值啦~
if (pv.getName().startsWith(fieldDefaultPrefix)) {
String field = pv.getName().substring(fieldDefaultPrefix.length());
if (getPropertyAccessor().isWritableProperty(field) && !mpvs.contains(field)) {
mpvs.add(field, pv.getValue());
}
mpvs.removePropertyValue(pv);
}
}
}
}
例如: 请求参数为 name=dhy&!name=xpy,那么name最终取值为dhy, 如果只有!name=xpy,那么最终我们Controller拿到的name=xpy
// 处理_的步骤
// 若传入的字段以_打头
// JavaBean的这个属性可写 && mpvs木有去掉_后的属性名字
// getEmptyValue(field, fieldType)就是根据Type类型给定默认值。
// 比如Boolean类型默认给false,数组给空数组[],集合给空集合,Map给空map 可以参考此类:CollectionFactory
// 当然,这一切都是建立在你传的属性值是以_打头的基础上的,Spring才会默认帮你处理这些默认值
protected void checkFieldMarkers(MutablePropertyValues mpvs) {
String fieldMarkerPrefix = getFieldMarkerPrefix();
if (fieldMarkerPrefix != null) {
PropertyValue[] pvArray = mpvs.getPropertyValues();
for (PropertyValue pv : pvArray) {
if (pv.getName().startsWith(fieldMarkerPrefix)) {
String field = pv.getName().substring(fieldMarkerPrefix.length());
if (getPropertyAccessor().isWritableProperty(field) && !mpvs.contains(field)) {
Class<?> fieldType = getPropertyAccessor().getPropertyType(field);
mpvs.add(field, getEmptyValue(field, fieldType));
}
mpvs.removePropertyValue(pv);
}
}
}
}
例如: 请求参数为 name=dhy&_name=xpy,那么name最终取值为dhy, 如果只有_name=xpy,那么最终我们Controller拿到的name=null,因为String类型的默认值就是null
protected void bindMultipart(Map<String, List<MultipartFile>> multipartFiles, MutablePropertyValues mpvs) {
multipartFiles.forEach((key, values) -> {
if (values.size() == 1) {
MultipartFile value = values.get(0);
if (isBindEmptyMultipartFiles() || !value.isEmpty()) {
mpvs.add(key, value);
}
}
else {
mpvs.add(key, values);
}
});
}
@Test
public void testWebDataBinder02() throws BindException {
People people = new People();
WebDataBinder webDataBinder = new WebDataBinder(people);
MutablePropertyValues mpvs = new MutablePropertyValues();
mpvs.add("!name","大忽悠");
mpvs.add("_age",20);
webDataBinder.bind(mpvs);
webDataBinder.close();
System.out.println(people);
}
大家也可以自行去尝试一下,WebDataBinder提供的!和_标记,其实是考虑到如果对应属性不存在的情况下,我们可以给出一个默认值替代。
ServletRequestDataBinder名字中的ServletRequest已经暴露了该类的使用场景,它就是遵循了Servlet规范的request参数绑定类。
上面的WebDataBinder本质只是做了一些增强,并没有涉及到Web request请求参数的绑定,相当于并没有和Servlet规范联系到一起,而ServletRequestDataBinder就和servlet规范联系到了一块。
ServletRequestDataBinder的源码也比较简单,就不拆开讲了,直接看吧:
public class ServletRequestDataBinder extends WebDataBinder {
... // 沿用父类构造
// 注意这个可不是父类的方法,是本类增强的~~~~意思就是kv都从request里来~~当然内部还是适配成了一个MutablePropertyValues
public void bind(ServletRequest request) {
// 内部最核心方法是它:WebUtils.getParametersStartingWith() 把request参数转换成一个Map
// request.getParameterNames()
MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(request);
MultipartRequest multipartRequest = WebUtils.getNativeRequest(request, MultipartRequest.class);
// 调用父类的bindMultipart方法,把MultipartFile都放进MutablePropertyValues里去~~~
if (multipartRequest != null) {
bindMultipart(multipartRequest.getMultiFileMap(), mpvs);
}
// 这个方法是本类流出来的一个扩展点~~~子类可以复写此方法自己往里继续添加
// 比如ExtendedServletRequestDataBinder它就复写了这个方法,进行了增强(下面会说) 支持到了uriTemplateVariables的绑定
addBindValues(mpvs, request);
doBind(mpvs);
}
// 这个方法和父类的close方法类似,很少直接调用
public void closeNoCatch() throws ServletRequestBindingException {
if (getBindingResult().hasErrors()) {
throw new ServletRequestBindingException("Errors binding onto object '" + getBindingResult().getObjectName() + "'", new BindException(getBindingResult()));
}
}
protected void addBindValues(MutablePropertyValues mpvs, ServletRequest request) {
}
}
ServletRequestDataBinder完成了从request对象中取出所有请求参数,然后封装为MutablePropertyValues的工作,并且还增加对文件上传请参数封装的支持和子类扩展mvps的回调接口。
@Test
public void testServletRequestDataBinder03() throws BindException {
People people = new People();
ServletRequestDataBinder srdb = new ServletRequestDataBinder(people);
//需要添加spring-test依赖支持
MockHttpServletRequest mockReq = new MockHttpServletRequest();
mockReq.addParameter("name","大忽悠");
mockReq.addParameter("age","18");
mockReq.addParameter("mother.name","1");
mockReq.addParameter("mother.age","2");
mockReq.addParameter("father.name","3");
mockReq.addParameter("father.age","4");
mockReq.addParameter("list","1","2","3","4");
mockReq.addParameter("map[1]","1");
mockReq.addParameter("map[2]","2");
srdb.bind(mockReq);
System.out.println(people);
}
注意: 对于map属性的赋值而言,不能写成下面这样:
mockReq.addParameter("map","{'name',1}");
即不能使用JSON字符串进行map的赋值,因为这和AbstractNestablePropertyAccessor底层对集合表达式解析并赋值的过程由密切关系,不清楚的可以去研究一下这部分源码。
如果想要用JSON赋值,需要添加一个前置处理,就是把JSON转换为上面的合法的map赋值格式,即可。
ExtendedServletRequestDataBinder
是对ServletRequestDataBinder
的一个增强,它用于把URI template variables参数添加进来用于绑定。它会去从request的HandlerMapping.class.getName() + ".uriTemplateVariables"
这个属性里查找到值出来用于绑定。
比如我们熟悉的@PathVariable
它就和这相关:
具体是在AbstractUrlHandlerMapping类的lookupHandler方法中,完成了对URI template variables的解析。
Spring MVC各组件近距离接触–上–02
该类的具体源码如下:
// @since 3.1
public class ExtendedServletRequestDataBinder extends ServletRequestDataBinder {
... // 沿用父类构造
//本类的唯一方法
@Override
@SuppressWarnings("unchecked")
protected void addBindValues(MutablePropertyValues mpvs, ServletRequest request) {
// 它的值是:HandlerMapping.class.getName() + ".uriTemplateVariables";
String attr = HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE;
// 注意:此处是attr,而不是parameter
Map<String, String> uriVars = (Map<String, String>) request.getAttribute(attr);
if (uriVars != null) {
uriVars.forEach((name, value) -> {
// 若已经存在确切的key了,不会覆盖~~~~
if (mpvs.contains(name)) {
if (logger.isWarnEnabled()) {
logger.warn("Skipping URI variable '" + name + "' because request contains bind value with same name.");
}
} else {
mpvs.addPropertyValue(name, value);
}
});
}
}
}
它是Spring5.0后提供的,对Reactive编程的Mono数据绑定提供支持.
它位于org.springframework.data.web是和Spring-Data相关,专门用于处理target是Map<String, Object>类型的目标对象的绑定,它并非一个public类.
它是用于处理Spring自己定义的org.springframework.web.context.request.WebRequest的,旨在处理和容器无关的web请求数据绑定.
虽然DataBinder内部提供了对类型转换的支持,但是由于某些情况下,不存在对应的自定义转换器,会导致赋值失败,那么这种情况下,应该怎么处理呢?
@Test
public void testServletRequestDataBinder04() throws BindException {
People people = new People();
ServletRequestDataBinder srdb = new ServletRequestDataBinder(people);
//需要添加spring-test依赖支持
MockHttpServletRequest mockReq = new MockHttpServletRequest();
mockReq.addParameter("name","大忽悠");
mockReq.addParameter("age","18");
mockReq.addParameter("birthday","2002-1-2");
srdb.bind(mockReq);
//检查是否在数据绑定和数据校验期间发生了异常,如果发生了则在此处抛出异常
srdb.close();
System.out.println(people);
}
显然错误出在了对birthday属性的赋值上,birthday属性是一个Date类型,我们传入的值是"2002-1-2",那为什么会报错呢?
之前讲过,数据绑定默认是通过BeanWrapper完成的
BeanWrapper调用setPropertyValue()给属性赋值,传入的value值都会交给convertForProperty()方法根据get方法的返回值类型进行转换~(比如此处为Date类型)
委托给this.typeConverterDelegate.convertIfNecessary进行类型转换(比如此处为string->Date类型)
先this.propertyEditorRegistry.findCustomEditor(requiredType,propertyName);找到一个合适的PropertyEditor(显然此处我们没有自定义Custom处理Date的PropertyEditor,返回null)
回退到使用ConversionService,显然此处我们也没有设置,返回null
回退到使用默认的editor = findDefaultEditor(requiredType)
PropertyEditorRegistrySupport中提供的默认类型转换器中没有对Date类型、以及Jsr310提供的各种事件、日期类型的转换(当然也包括我们的自定义类型)。
最终的最终,回退到Spring对Array、Collection、Map的默认值处理问题,最终若是String类型,都会调用
BeanUtils.instantiateClass(strCtor, convertedValue)也就是有参构造进行初始化~~~(请注意这必须是String类型才有的权利)
所以本例中,到最后一步就相当于new Date(“2002-1-2”),因为该字符串是不符合默认的格式规范,所以会抛出异常。
Spring读源码系列番外篇08—BeanWrapper没有那么简单–上
要解决上面这个问题,那么就是需要注册一个可以转换Date的类型转换器进去,有下面几种方法:
@Test
public void testServletRequestDataBinder04() throws BindException {
People people = new People();
ServletRequestDataBinder srdb = new ServletRequestDataBinder(people);
//需要添加spring-test依赖支持
MockHttpServletRequest mockReq = new MockHttpServletRequest();
mockReq.addParameter("name","大忽悠");
mockReq.addParameter("age","18");
mockReq.addParameter("birthday","2002-1-2");
srdb.addCustomFormatter(new DateFormatter());
srdb.bind(mockReq);
srdb.close();
System.out.println(people);
}
public class MyDatePropertyEditor extends PropertyEditorSupport {
private static final String PATTERN = "yyyy-MM-dd";
@Override
public String getAsText() {
Date date = (Date) super.getValue();
return new SimpleDateFormat(PATTERN).format(date);
}
@Override
public void setAsText(String text) throws IllegalArgumentException {
try {
super.setValue(new SimpleDateFormat(PATTERN).parse(text));
} catch (ParseException e) {
System.out.println("ParseException....................");
}
}
}
srdb.registerCustomEditor(Date.class, new MyDatePropertyEditor());
WebBindingInitializer:实现此接口重写initBinder方法注册的属性编辑器是全局的属性编辑器,对所有的Controller都有效。
可以简单粗暴的理解为:WebBindingInitializer为编码方式,@InitBinder为注解方式(当然注解方式还能控制到只对当前Controller有效,实现更细粒度的控制)
public interface WebBindingInitializer {
void initBinder(WebDataBinder binder);
//spring 5.0之后废弃了该方法
@Deprecated
default void initBinder(WebDataBinder binder, WebRequest request) {
initBinder(binder);
}
}
此接口它的内建唯一实现类为:ConfigurableWebBindingInitializer,若你自己想要扩展,建议继承它:
public class ConfigurableWebBindingInitializer implements WebBindingInitializer {
//默认支持级联属性
private boolean autoGrowNestedPaths = true;
//底层默认使用BeanWrapper
private boolean directFieldAccess = false;
@Nullable
private MessageCodesResolver messageCodesResolver;
@Nullable
private BindingErrorProcessor bindingErrorProcessor;
@Nullable
private Validator validator;
@Nullable
private ConversionService conversionService;
// 此处使用的PropertyEditorRegistrar来管理的,最终都会被注册进PropertyEditorRegistry
@Nullable
private PropertyEditorRegistrar[] propertyEditorRegistrars;
... // 省略所有get/set
// 它做的事无非就是把配置的值都放进去而已~~
@Override
public void initBinder(WebDataBinder binder) {
binder.setAutoGrowNestedPaths(this.autoGrowNestedPaths);
if (this.directFieldAccess) {
binder.initDirectFieldAccess();
}
if (this.messageCodesResolver != null) {
binder.setMessageCodesResolver(this.messageCodesResolver);
}
if (this.bindingErrorProcessor != null) {
binder.setBindingErrorProcessor(this.bindingErrorProcessor);
}
// 可以看到对校验器这块 内部还是做了容错的
if (this.validator != null && binder.getTarget() != null && this.validator.supports(binder.getTarget().getClass())) {
binder.setValidator(this.validator);
}
if (this.conversionService != null) {
binder.setConversionService(this.conversionService);
}
if (this.propertyEditorRegistrars != null) {
for (PropertyEditorRegistrar propertyEditorRegistrar : this.propertyEditorRegistrars) {
propertyEditorRegistrar.registerCustomEditors(binder);
}
}
}
}
此实现类主要是提供了一些可配置项,用来覆盖DataBinder的默认配置。
注意:此接口一般不直接使用,而是结合InitBinderDataBinderFactory、WebDataBinderFactory等一起使用~
顾名思义它就是来创造一个WebDataBinder的工厂。
// @since 3.1 注意:WebDataBinder 可是1.2就有了~
public interface WebDataBinderFactory {
// 此处使用的是Spring自己的NativeWebRequest 后面两个参数就不解释了
WebDataBinder createBinder(NativeWebRequest webRequest, @Nullable Object target, String objectName) throws Exception;
}
继承树如下:
public class DefaultDataBinderFactory implements WebDataBinderFactory {
@Nullable
private final WebBindingInitializer initializer;
// 注意:这是唯一构造函数
public DefaultDataBinderFactory(@Nullable WebBindingInitializer initializer) {
this.initializer = initializer;
}
// 实现接口的方法
@Override
@SuppressWarnings("deprecation")
public final WebDataBinder createBinder(NativeWebRequest webRequest, @Nullable Object target, String objectName) throws Exception {
WebDataBinder dataBinder = createBinderInstance(target, objectName, webRequest);
// 可见WebDataBinder 创建好后,此处就会回调(只有一个)
if (this.initializer != null) {
//通过WebBindingInitializer的initBinder方法,我们可以覆盖掉dataBinder的默认配置
this.initializer.initBinder(dataBinder, webRequest);
}
// 空方法 子类去实现,比如InitBinderDataBinderFactory实现了词方法
initBinder(dataBinder, webRequest);
return dataBinder;
}
// 子类可以复写,默认实现是WebRequestDataBinder
// 比如子类ServletRequestDataBinderFactory就复写了,使用的new ExtendedServletRequestDataBinder(target, objectName)
protected WebDataBinder createBinderInstance(@Nullable Object target, String objectName, NativeWebRequest webRequest) throws Exception
return new WebRequestDataBinder(target, objectName);
}
//空实现
protected void initBinder(WebDataBinder dataBinder, NativeWebRequest webRequest)
throws Exception {
}
}
按照Spring一贯的设计,本方法实现了模板动作,子类只需要复写对应的动作即可达到效果。
它继承自DefaultDataBinderFactory,主要用于处理标注有@InitBinder的方法做初始绑定~
关于@InitBinder注解的原理,后续会出一篇文章专门讲解
// @since 3.1
public class InitBinderDataBinderFactory extends DefaultDataBinderFactory {
// 需要注意的是:`@InitBinder`可以标注N多个方法~ 所以此处是List
//拿到的InvocableHandlerMethod就是Controller类中标注了@InitBinder的方法
private final List<InvocableHandlerMethod> binderMethods;
// 此子类的唯一构造函数
public InitBinderDataBinderFactory(@Nullable List<InvocableHandlerMethod> binderMethods, @Nullable WebBindingInitializer initializer) {
super(initializer);
this.binderMethods = (binderMethods != null ? binderMethods : Collections.emptyList());
}
// 上面知道此方法的调用方法生initializer.initBinder之后
// 所以使用注解它生效的时机是在直接实现接口的后面的~
@Override
public void initBinder(WebDataBinder dataBinder, NativeWebRequest request) throws Exception {
for (InvocableHandlerMethod binderMethod : this.binderMethods) {
// 判断@InitBinder是否对dataBinder持有的target对象生效~~~(根据name来匹配的)
if (isBinderMethodApplicable(binderMethod, dataBinder)) {
// 关于目标方法执行这块---binderMethod只有一个参数为WebDataBinder,并且返回值必须为null
Object returnValue = binderMethod.invokeForRequest(request, null, dataBinder);
// 标注@InitBinder的方法不能有返回值
if (returnValue != null) {
throw new IllegalStateException("@InitBinder methods must not return a value (should be void): " + binderMethod);
}
}
}
}
//@InitBinder有个Value值,它是个数组。它是用来匹配dataBinder.getObjectName()是否匹配的 若匹配上了,现在此注解方法就会生效
// 若value为空,那就对所有生效~~~
protected boolean isBinderMethodApplicable(HandlerMethod initBinderMethod, WebDataBinder dataBinder) {
InitBinder ann = initBinderMethod.getMethodAnnotation(InitBinder.class);
Assert.state(ann != null, "No InitBinder annotation");
String[] names = ann.value();
return (ObjectUtils.isEmpty(names) || ObjectUtils.containsElement(names, dataBinder.getObjectName()));
}
}
它继承自InitBinderDataBinderFactory,作用就更明显了。既能够处理@InitBinder,而且它使用的是更为强大的数据绑定器:ExtendedServletRequestDataBinder
// @since 3.1
public class ServletRequestDataBinderFactory extends InitBinderDataBinderFactory {
public ServletRequestDataBinderFactory(@Nullable List<InvocableHandlerMethod> binderMethods, @Nullable WebBindingInitializer initializer) {
super(binderMethods, initializer);
}
@Override
protected ServletRequestDataBinder createBinderInstance(
@Nullable Object target, String objectName, NativeWebRequest request) throws Exception {
return new ExtendedServletRequestDataBinder(target, objectName);
}
}
此工厂是RequestMappingHandlerAdapter这个适配器默认使用的一个数据绑定器工厂,而RequestMappingHandlerAdapter却又是当下使用得最频繁、功能最强大的一个适配器
WebDataBinder在SpringMVC中使用,它不需要我们自己去创建,我们只需要向它注册参数类型对应的属性编辑器PropertyEditor。PropertyEditor可以将字符串转换成其真正的数据类型,它的void setAsText(String text)方法实现数据转换的过程。
而如果我们想要获取到WebDataBinder来进行属性编辑器的注册或者其他干一些其他事情,可以通过WebBindingInitializer接口或者@InitBinder注解来完成。
我尝试阅读有关 Spring BOM、Spring Boot 和 Spring IO 的文档。 但是没有说明,我们应该如何一起使用它们? 在我的项目中,我们已经有了自己的 Parent POM ,所以
我正在开发的很酷的企业应用程序正在转向 Spring。这对所有团队来说都是非常酷和令人兴奋的练习,但也是一个巨大的压力源。我们所做的是逐渐将遗留组件移至 Spring 上下文。现在我们有一个 huuu
我正在尝试使用 @Scheduled 运行 Spring 批处理作业注释如下: @Scheduled(cron = "* * * * * ?") public void launchMessageDi
我对这两个概念有点困惑。阅读 Spring 文档,我发现,例如。 bean 工厂是 Spring 容器。我还读到“ApplicationContext 是 BeanFactory 的完整超集”。但两者
我们有一个使用 Spring BlazeDS 集成的应用程序。到目前为止,我们一直在使用 Spring 和 Flex,它运行良好。我们现在还需要添加一些 Spring MVC Controller 。
假设我有一个类(class) Person带属性name和 age ,它可以像这样用 Spring 配置: 我想要一个自定义的 Spring 模式元素,这很容易做到,允许我在我的 Sp
如何在 Java 中以编程方式使用 Spring Data 创建 MongoDB 复合索引? 使用 MongoTemplate 我可以创建一个这样的索引:mongoTemplate.indexOps(
我想使用 spring-complex-task 执行我的应用程序,并且我已经构建了复杂的 spring-batch Flow Jobs,它执行得非常好。 你能解释一下spring批处理流作业与spr
我实现了 spring-boot 应用程序,现在我想将它用作非 spring 应用程序的库。 如何初始化 lib 类,以便 Autowiring 的依赖项按预期工作?显然,如果我使用“new”创建类实
我刚开始学习 spring cloud security,我有一个基本问题。它与 Spring Security 有何不同?我们是否需要在 spring boot 上构建我们的应用程序才能使用 spr
有很多人建议我使用 Spring Boot 而不是 Spring 来开发 REST Web 服务。我想知道这两者到底有什么区别? 最佳答案 总之 Spring Boot 减少了编写大量配置和样板代码的
您能向我解释一下如何使用 Spring 正确构建 Web 应用程序吗?我知道 Spring 框架的最新版本是 4.0.0.RELEASE,但是 Spring Security 的最新版本是 3.2.0
我如何才能知道作为 Spring Boot 应用程序的一部分加载的所有 bean 的名称?我想在 main 方法中有一些代码来打印服务器启动后加载的 bean 的详细信息。 最佳答案 如spring-
我有一个使用 Spring 3.1 构建的 RESTful API,也使用 Spring Security。我有一个 Web 应用程序,也是一个 Spring 3.1 MVC 应用程序。我计划让移动客
升级到 Spring 5 后,我在 Spring Rabbit 和 Spring AMQP 中遇到错误。 两者现在都设置为 1.5.6.RELEASE 有谁知道哪些版本应该与 Spring 5 兼容?
我现在已经使用 Spring Framework 3.0.5 和 Spring Security 3.0.5 多次了。我知道Spring框架使用DI和AOP。我还知道 Spring Security
我收到错误 Unable to Location NamespaceHandler when using context:annotation-config running (java -jar) 由
在 Spring 应用程序中嵌入唯一版本号的策略是什么? 我有一个使用 Spring Boot 和 Spring Web 的应用程序。 它已经足够成熟,我想对其进行版本控制并在运行时看到它显示在屏幕上
我正在使用 spring data jpa 进行持久化。如果存在多个具有相同名称的实体,是否有一种方法可以将一个实体标记为默认值。类似@Primary注解的东西用来解决多个bean的依赖问题 @Ent
我阅读了 Spring 框架的 DAOSupport 类。但是我无法理解这些 DAOSuport 类的优点。在 DAOSupport 类中,我们调用 getXXXTemplate() 方法来获取特定的
我是一名优秀的程序员,十分优秀!