在山上stands立着Spring Boot ...

...他的四个正在调试。


受Vladimir Plizgi的一份报告( Spring Boot 2:他们在发行说明中未写的内容 )的启发,我决定谈论一下我在Spring Booth上的经历,其功能和遇到的陷阱。


春季数据JPA


弗拉基米尔(Vladimir)的报告致力于迁移到Spring Booth 2以及由此带来的困难。 在第一部分中,他描述了编译错误,因此我也将从它们开始。


我认为许多人都熟悉Spring Data框架及其用于各种数据仓库的派生工具。 我只使用其中之一-Spring Data JPA。 此框架中使用的主要接口JpaRepository在版本2中进行了重大更改。


考虑最常用的方法:


  // interface JpaRepository<T, ID> { T findOne(ID id); List<T> findAll(Iterable<ID> ids); <S extends T> Iterable<S> save(Iterable<S> entities); } // interface JpaRepository<T, ID> { Optional<T> findById(ID id); List<T> findAllById(Iterable<ID> ids); <S extends T> Iterable<S> saveAll(Iterable<S> entities); } 

实际上,还有更多更改,并且移至版本2之后。*所有这些都将变成编译错误。


当我们的一个项目提出了迁移到Spring Booth 2的问题并采取了第一步(替换pom.xml中的版本)时,整个项目实际上变成了红色:数十个存储库和对其方法的数百次调用导致了迁移“(使用新方法)将每隔一个钩文件。 想象一下最简单的代码:


 SomeEntity something = someRepository.findOne(someId); if (something == null) { something = someRepository.save(new SomeEntity()); } useSomething(something); 

为了使项目顺利进行,您需要:


  • 调用另一个方法
  • 将变量的类型更改为Optional<SomeEntity>
  • Optional::isPresentOptional::orElse / Optional::orElseGet替换检查空引用
  • 修复测试

幸运的是,有一种简单的方法,因为Spring Date为用户提供了前所未有的机会,可以根据自己的需求定制存储库。 我们需要定义(如果尚未定义)接口和类,这些接口和类将构成我们项目的所有存储库的基础。 这样做是这样的:


 //    @NoRepositoryBean public interface BaseJpaRepository<T, ID extends Serializable> extends JpaRepository<T, ID> { //   findOne,     1.* @Deprecated T findOne(ID id); //    findAll @Deprecated //         List<T> findAll(Iterable<ID> ids); } 

我们所有的存储库都将从该接口继承。 现在执行:


 public class BaseJpaRepositoryImpl<T, ID extends Serializable> extends SimpleJpaRepository<T, ID> implements BaseJpaRepository<T, ID> { private final JpaEntityInformation<T, ?> entityInfo; private final EntityManager entityManager; public BaseJpaRepositoryImpl(JpaEntityInformation<T, ?> entityInfo, EntityManager entityManager) { super(entityInfo, entityManager); this.entityInfo = entityInfo; this.entityManager = entityManager; } @Override public T findOne(ID id) { return findById(id).orElse(null); //    1.* } @Override public List<T> findAll(Iterable<ID> ids) { return findAllById(ids); //      } } 

剩下的就是形象和肖像。 所有的编译错误都消失了,代码像以前一样工作,只更改了2个类,而不是200个类。现在,随着开发的进行,您可以慢慢地用新的API替换过时的API,因为Idea会小心地将@Deprecated方法的所有调用染成黄色。


最后一招-我们通知Spring从现在开始,存储库应该建立在我们的类之上:


 @Configuration @EnableJpaRepositories(repositoryBaseClass = BaseJpaRepositoryImpl.class) public class SomeConfig{ } 

在一个平静的游泳池中,可以找到垃圾箱


Spring Booth基于两个概念-启动器和自动配置。 在生活中,这的结果是一个重要的属性-进入应用程序类路径的库将由SB扫描,如果在其中找到包含某个设置的类,则该类将在您不知情和明确指示的情况下打开。 我们用一个简单的例子说明这一点。


许多人使用gson库将对象转换为JSON字符串,反之亦然。 您通常可以看到以下代码:


 @Component public class Serializer { public <T> String serialize(T obj) { return new Gson().toJson(obj); } } 

频繁访问时,此代码将另外加载垃圾回收器,这是我们不希望的。 特别聪明的人会创建一个对象并使用它:


 @Configuration public class SomeConfig { @Bean public Gson gson() { return new Gson(); } } @Component @RequiredArgsConstructor public class Serializer { private final Gson gson; public String serialize(T obj) { return gson.toJson(obj); } } 

...甚至没有意识到Spring Booth可以自己做所有事情。 缺省情况下,SB带有org.springframework.boot:spring-boot-autoconfigure依赖项,其中包括许多名为*AutoConfiguration类,例如:


 @Configuration @ConditionalOnClass(Gson.class) @EnableConfigurationProperties(GsonProperties.class) public class GsonAutoConfiguration { @Bean @ConditionalOnMissingBean public GsonBuilder gsonBuilder(List<GsonBuilderCustomizer> customizers) { GsonBuilder builder = new GsonBuilder(); customizers.forEach((c) -> c.customize(builder)); return builder; } @Bean @ConditionalOnMissingBean public Gson gson(GsonBuilder gsonBuilder) { return gsonBuilder.create(); } } 

设置很简单:如果应用程序中有Gson类,并且没有这种类型的手动定义的bean,那么将创建一个默认实现。 这是一种巨大的功能,它允许一个依赖项和几个注释来引发一个完整的配置,该配置的描述过去通常采用XML工作表和许多手写的bin。


但这是安全理事会的极大阴险。 阴险在于保密,因为根据预先编写的脚本,很多工作在后台进行。 这对于典型的应用程序是很好的,但是离开人迹罕至的轨道可能会导致问题-车队射击时没有警告。 在我们不了解的情况下创建了许多bean,Gson的示例很好地说明了这一点。 小心类和您的依赖项,因为...


任何进入您的类路径的东西都可以用来对付您。


人生案例。 有两个应用程序:一个使用LdapTemplate ,另一个使用一次。 这两个应用程序都依赖于core项目,在该项目中取出了一些通用的类,并且将这两个应用程序的通用库仔细折叠在pom.xml中。


一段时间后,在第二个项目中,不必要地LdapTemplateLdapTemplate所有使用。 但是org.springramework:spring-ldap库仍然处于core 。 然后SB罢工了, org.springramework.ldap:ldap-core变成了org.springramework.boot:spring-boot-starter-data-ldap


因此,有趣的消息出现在日志中:


 Multiple Spring Data modules found, entering strict repository configuration mode! Spring Data LDAP - Could not safely identify store assignment for repository candidate interface .... 

core领导的org.springramework.boot:spring-boot-starter-data-ldap依赖org.springramework.boot:spring-boot-starter-data-ldap适用于完全不使用LDAP的项目。 犹豫是什么意思? 打classpath :)。 进一步的停止:


  • Spring Boot注意到org.springramework.boot:spring-boot-starter-data-ldap classpath中的org.springramework.boot:spring-boot-starter-data-ldap
  • 现在检查每个存储库,以查看它是否适合与Spring Data JPA或Spring Data LDAP一起使用
  • 重组依赖关系并org.springramework.boot:spring-boot-starter-data-ldap不必要的org.springramework.boot:spring-boot-starter-data-ldap将应用程序启动时间平均缩短了20(!)秒,总共40-50秒

细心且挑剔的读者可能会问:如果不使用Spring Data LDAP,但仅使用一个,为什么需要将org.springramework.ldap:ldap-core更改为org.springramework.boot:spring-boot-starter-data-ldaporg.springframework.ldap.LdapTempate吗?


答:这是一个错误。 事实是,在2.1.0.M1版本之前,LDAP的自动调整看起来像这样


 @Configuration @ConditionalOnClass({ContextSource.class}) @EnableConfigurationProperties({LdapProperties.class}) public class LdapAutoConfiguration { private final LdapProperties properties; private final Environment environment; public LdapAutoConfiguration(LdapProperties properties, Environment environment) { this.properties = properties; this.environment = environment; } @Bean @ConditionalOnMissingBean public ContextSource ldapContextSource() { LdapContextSource source = new LdapContextSource(); source.setUserDn(this.properties.getUsername()); source.setPassword(this.properties.getPassword()); source.setBase(this.properties.getBase()); source.setUrls(this.properties.determineUrls(this.environment)); source.setBaseEnvironmentProperties(Collections.unmodifiableMap(this.properties.getBaseEnvironment())); return source; } } 

LdapTemplate在哪里? 但是他不是:)。 更确切地说,它在但位于其他地方:


 @Configuration @ConditionalOnClass({LdapContext.class, LdapRepository.class}) @AutoConfigureAfter({LdapAutoConfiguration.class}) public class LdapDataAutoConfiguration { @Bean @ConditionalOnMissingBean({LdapOperations.class}) public LdapTemplate ldapTemplate(ContextSource contextSource) { return new LdapTemplate(contextSource); } } 

因此,假设您LdapTemplate通过满足@ConditionalOnClass({LdapContext.class, LdapRepository.class}) LdapTemplate在应用程序中获取LdapTemplate ,这是在将spring-boot-starter-data-ldap依赖项添加到类路径中时实现的。


另一种可能性:用您的双手确定这个bean,这是您不想要的(因为为什么我们那时需要SB)。 org.springramework.boot:spring-boot-starter-data-ldap在用org.springramework.ldap:ldap-core替换了org.springramework.boot:spring-boot-starter-data-ldap之后提出了这个建议。


问题已在此处解决: https : //github.com/spring-projects/spring-boot/pull/13136 。 主要更改: LdapTemplate bean LdapTemplate移至LdapAutoConfiguration 。 现在,您可以使用LDAP,而无需绑定到spring-boot-starter-data-ldap并手动定义LdapTemplate bin。


胡德大惊小怪


如果您使用Hibernate,则您可能熟悉“ 打开视图”反模式。 我们将不去赘述它的描述,通过引用对其进行足够详细的描述,并且在其他来源中将对其有害作用进行非常详细的描述。


特殊标志负责打开/关闭此模式:


 spring: jpa: open-in-view: true 

在SB版本1中。*默认情况下启用此功能,而用户未获悉。 在版本2中。*仍处于启用状态,但是现在将警告写入日志:


 WARN spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning. 

最初,有一个禁用此模式的请求, 有关该主题史诗级内容包含数十个(!)详细注释,包括 来自OliverGörke(Spring Date开发人员),Vlad Michalce(休眠开发人员),Phil Web和Vedran Pavic(Spring开发人员)的优缺点。


他们同意行为不会改变,但是会显示警告(已观察到)。 还有一个相当普遍的技巧来禁用此模式:


 spring: jpa: open-in-view: false 

仅此而已,请写下您的举止和安全理事会的有趣特征-这确实是一个取之不尽的话题。

Source: https://habr.com/ru/post/zh-CN439918/


All Articles