zoukankan      html  css  js  c++  java
  • Spring Boot 学习(2)

    文 by / 林本托

    Tips
    做一个终身学习的人。

    Spring Boot

    源代码:github下的/code01/ch2。

    配置 Web 应用程序

    在上一章中,我们学习了如何创建一个基本的应用程序模板,并添加了一些基本功能,并建立与数据库的连接。 在本章中,我们将继续增强BookPub应用程序,并提供 Web 支持。

    在本章,主要包括以下内容:

    • 创建一个基本的 RESTful 风格的应用程序;
    • 创建一个 Spring Data REST 服务;
    • 配置一个自定义的 Servlet 的过滤器;
    • 配置一个自定义的拦截器;
    • 配置一个HttpMessageConverters的转换器;
    • 配置一个自定义的PropertyEditors编辑器;
    • 配置一个自定义的类型格式化类。

    一. 创建一个基本的 RESTful 风格的应用程序

    虽然命令行应用程序确实有其用途,但今天的大多数应用程序开发都围绕着 Web,REST 和数据服务。 我们开始增强 BookPub 应用程序,提供一个基于Web 的 API,以便访问图书目录。

    我们继续使用前一章创建的应用程序框架,其中定义了实体对象和存储库服务,并配置了与数据库的连接。

    首先,第一件事情是我们需要在build.gradle 文件中添加新的依赖模块spring-boot-starter-web,以便获取基于 web 服务的所需的类库。具体的代码片段如下:

    dependencies {
      compile("org.springframework.boot:spring-boot-starter-data-jpa")
      compile("org.springframework.boot:spring-boot-starter-jdbc")
      compile("org.springframework.boot:spring-boot-starter-web")
      runtime("com.h2database:h2")
      testCompile("org.springframework.boot:spring-boot-
        starter-test")
    }
    

    接下来,创建一个 Spring 控制器,用于处理我们应用程序中获取目录数据的 Web 请求。 新建一个包目录来存放控制器相关的 Java 程序,以便我们的代码按照适当的目的分组。 在 src/main/java/org/test/bookpub 目录下创建一个名为 controllers的包目录。

    因为需要暴露图书的数据,所以,在新建的包下,创建一个控制器类BookController

    package org.test.bookpub.controllers;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.propertyeditors.StringTrimmerEditor;
    import org.springframework.web.bind.WebDataBinder;
    import org.springframework.web.bind.annotation.*;
    import org.test.bookpub.entity.Book;
    import org.test.bookpub.entity.Reviewer;
    import org.test.bookpub.repository.BookRepository;
    
    import java.util.Collections;
    import java.util.List;
    
    @RestController
    @RequestMapping("/books")
    public class BookController {
      @Autowired
      private BookRepository bookRepository;
    
      @RequestMapping(value = "", method = RequestMethod.GET)
      public Iterable<Book> getAllBooks() {
        return bookRepository.findAll();
      }
    
      @RequestMapping(value = "/{isbn}", method = 
        RequestMethod.GET)
      public Book getBook(@PathVariable String isbn) {
        return bookRepository.findBookByIsbn(isbn);
      }
    }
    

    然后, 使用 ./gradlew clean bootRun.命令启动应用程序。

    最后,当应用程序程序启动以后,在浏览器中输入:http://localhost:8080/books, 然后会在页面上显示“[]”,表示目前没有图书的数据。

    获取暴露于Web请求的服务的关键是@RestController注解。这是一个元注解使用的例子,正如Spring文档指出的那样,我们在以前的代码看到过。 在@RestController中,定义了两个注解:@Controller和@ResponseBody。 所以我们可以轻松给BookController类添加注解,如下所示:

    @Controller
    @ResponseBody
    @RequestMapping("/books")
    public class BookController {...}
    

    @Controller是一个类似于@Bean 和@Repository的Spring的元注解,并将已经添加注解的类声明为 MVC 控制器。

    @ResponseBody是一个Spring MVC的注解,指示来自Web请求映射方法的响应,构成HTTP响应主体有效负载的整个内容,这是RESTful应用程序比较典型的使用场景。

    二. 创建一个 Spring Data REST 服务

    在上一个例子中,我们使用REST控制器来展示我们的BookRepository,通过Web RESTful API访问后台的数据。 虽然这是数据访问的一种快捷方便的方式,但它要求我们手动创建一个控制器并定义所有所需操作的映射。 为了最小化代码,Spring为我们提供了一种更方便的方法:spring-boot-starter-data-rest模块。 这允许我们简单地向存储库接口添加一个注解,而Spring将做剩下的事情用以将数据暴露给Web。

    首先,我们需要在build.gradle文件中添加spring-boot-starter-data-rest模块。

    dependencies {
      ...
     compile("org.springframework.boot:spring-boot-starter-data-rest")
      ...
    }
    

    第二步,在src/main/java/org/test/bookpub/repository目录下新建AuthorRepository接口,代码如下:

    package org.test.bookpub.repository;
    
    import org.springframework.data.repository.CrudRepository;
    import org.springframework.data.repository.PagingAndSortingRepository;
    import org.springframework.data.rest.core.annotation.RepositoryRestResource;
    import org.springframework.stereotype.Repository;
    import org.test.bookpub.entity.Author;
    
    @RepositoryRestResource
    public interface AuthorRepository extends PagingAndSortingRepository<Author, Long> {
    }
    

    接下来为剩下的实体模型创建对应的接口,

    package org.test.bookpub.repository;
    
    import org.springframework.data.repository.PagingAndSortingRepository;
    import org.springframework.data.rest.core.annotation.RepositoryRestResource;
    import org.test.bookpub.entity.Publisher;
    
    @RepositoryRestResource
    public interface PublisherRepository extends PagingAndSortingRepository<Publisher, Long> {
    }
    
    package org.test.bookpub.repository;
    
    import org.springframework.data.repository.PagingAndSortingRepository;
    import org.springframework.data.rest.core.annotation.RepositoryRestResource;
    import org.test.bookpub.entity.Reviewer;
    
    @RepositoryRestResource
    public interface ReviewerRepository extends PagingAndSortingRepository<Reviewer, Long> {
    }
    

    代码完成后,执行./gradlew clean bootRun。当启动成功以后,访问http://localhost:8080/profile/authors,在 Chrome 浏览器下,显示如下内容:

    {
      "_embedded" : {
        "authors" : [ ]
      },
      "_links" : {
        "self" : {
          "href" : "http://localhost:8080/authors{?page,size,sort}",
          "templated" : true
        },
        "profile" : {
          "href" : "http://localhost:8080/profile/authors"
        }
      },
      "page" : {
        "size" : 20,
        "totalElements" : 0,
        "totalPages" : 0,
        "number" : 0
      }
    }
    

    从浏览器里显示的内容可以看出,我们将获得比我们编写BookController控制器更多的信息。 之所以显示了更多的信息,由于我们没有扩展CrudRepository接口,而是扩展了PagingAndSortingRepository,而它又是CrudRepository的扩展。 这样做的原因是获得PagingAndSortingRepository提供的额外好处。 这将添加额外的功能,使用分页检索实体,并能够对它们进行排序。

    @RepositoryRestResource注解是可选项,但可以让我们更好地控制作为Web数据服务的存储库的暴露。 例如,如果我们要将URL路径由“authers”改成“rel”,则可以此注解调整,如下所示:

    @RepositoryRestResource(collectionResourceRel = "writers", path = "writers")
    

    修改以后,之前的 url 现在改完“http://localhost:8080/reviewers”。

    由于我们在构建依赖项中包含spring-boot-starter-data-rest模块,我们还获得spring-hateoas类库的支持,此库给我们提供了很好的ALPS元数据,比如_links对象。 这在构建API驱动的UI时可能非常有用,这可以从元数据推导出导航功能并以适合的方式呈现它们。

    Tips
    关于更多 ALPS 元数据的信息,请参考https://spring.io/blog/2014/07/14/spring-data-rest-now-comes-with-alps-metadata。

    三. 配置一个自定义的 Servlet 的过滤器

    在一个真实的Web应用程序中,我们几乎总是需要为服务请求添加装饰器或包装器,用来记录它们,过滤XSS(跨站脚本攻击)的非法字符,执行认证等。Spring Boot自动添加OrderedCharacterEncodingFilter和HiddenHttpMethodFilter 两个过滤器,除此之外,还有其他的过滤器。 让我们看看Spring Boot如何帮助我们实现这个任务。

    在Spring Boot,Spring Web,Spring MVC等的各种框架中,已经有各种不同的servlet过滤器可用,我们所要做的就是将它们作为一个bea定义到配置中。 假设应用程序将在负载平衡器代理之后运行,并且当我们的应用程序实例收到请求时,我们希望将用户使用的实际请求IP转换为代理的IP。 幸运的是,Tomcat 8已经为我们提供了一个实现:RemoteIpFilter。 我们需要做的就是将其添加到我们的过滤器链中。

    根据功能的不同,便于管理和职责清晰,我们需要把不同的类放在不同的包下,我们创建一个WebConfiguration.java 的文件,放在src/main/java/org/test/bookpub 目录下。

    package org.test.bookpub;
    
    import org.apache.catalina.filters.RemoteIpFilter;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
    
    @Configuration
    public class WebConfiguration extends WebMvcConfigurerAdapter {
        @Bean
        public RemoteIpFilter remoteIpFilter() {
            return new RemoteIpFilter();
        }
    }
    

    第二步,执行./gradlew clean bootRun命令,在启动中,查看 log,会出现以下信息,表示过滤器已经添加:

    o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'remoteIpFilter' to: [/*]
    

    这个功能背后的功能其实很简单。 我们从单独的配置类开始,并且将我们的方法用于过滤器bean的检测。

    我们看看主类BookPubApplication,这个类添加了@SpringBootApplication注解,此注解是是一个元注解,它声明了@ComponentScan等其他注解。@ComponentScan的存在指示Spring Boot将WebConfiguration类检测为@Configuration注解的标记类,并将其定义添加到应用程序上下文中。所以,我们将在WebConfiguration类中声明的任何事情就好像我们将它放在BookPubApplication类中是一样的。

    @Bean public RemoteIpFilter remoteIpFilter(){...}声明为RemoteIpFilter类创建一个Spring bean。当Spring Boot 检测到javax.servlet.Filter的所有bean时,它将自动将它们添加到过滤器链中。所以我们要做的就是,如果要添加更多的过滤器,那就是将它们声明为@Bean配置。例如,对于更高级的过滤器配置,如果希望特定的过滤器仅适用于特定的URL模式,可以创建一个FilterBistrationBean类型的@Bean配置,并用来配置精确的设置。

    四. 配置一个自定义的拦截器

    Servlet过滤器是Servlet API的一部分,与Spring完全没有任何关系,除了自动添加到过滤器链中,Spring MVC为我们提供了另一种方式来包装Web请求:HandlerInterceptor拦截器。根据文档,HandlerInterceptor就像一个Filter,但是,拦截器不是在嵌套链中包含请求,而是在处理请求、处理视图或在页面渲染之前,在不同的阶段向我们提供了拦截点,对请求进行拦截。到最后,请求已经完成。它不改变有关请求的任何内容,但是如果拦截器逻辑返回false,它允许我们通过抛出异常来停止执行。

    与过滤器的情况类似,Spring MVC附带了一些预定义的HandlerInterceptor。常用的是LocaleChangeInterceptor和ThemeChangeInterceptor。接下来在应用程序中添加LocaleChangeInterceptor,看看它是如何完成的。

    添加一个拦截器并不像刚才声明一个bean那么简单。 实际上需要通过实现WebMvcConfigurer接口或重写WebMvcConfigurationSupport来实现。

    第一步,WebConfiguration类继承WebMvcConfigurerAdapter类。

    public class WebConfiguration extends
     WebMvcConfigurerAdapter {…}
    

    接下来,为LocaleChangeInterceptor拦截器增加@Bean注解,

    @Bean
    public LocaleChangeInterceptor localeChangeInterceptor() {
      return new LocaleChangeInterceptor();
    }
    

    这实际上只会创建拦截 Spring bean,但不会将其添加到请求处理链中。 为了实现这一点,我们需要重写addInterceptors方法,注册拦截器。

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
      registry.addInterceptor(localeChangeInterceptor());
    }
    

    整个代码如下:

    package org.test.bookpub;
    
    import org.apache.catalina.filters.RemoteIpFilter;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
    import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
    import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
    
    @Configuration
    public class WebConfiguration extends WebMvcConfigurerAdapter {
        @Bean
        public RemoteIpFilter remoteIpFilter() {
            return new RemoteIpFilter();
        }
    
            @Bean
        public LocaleChangeInterceptor localeChangeInterceptor() {
            return new LocaleChangeInterceptor();
        }
    
        @Override
    	public void addInterceptors(InterceptorRegistry registry) {
      		registry.addInterceptor(localeChangeInterceptor());
    	}
    }
    

    执行 ./gradlew clean bootRun

    应用程序启动以后,在浏览器中输入http://localhost:8080/books?locale=foo。

    这时,浏览器报错:

    locale error

    后台的错误 log 如下:

    org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.UnsupportedOperationException: Cannot change HTTP accept header - use a different locale resolution strategy
    

    Tips
    上面的错误不是因为我们输入了无效的区域设置,而是因为默认语言环境解析策略不允许重置浏览器请求的语言环境。出现一个错误,实际上是证明了拦截器已经生效了。

    当涉及到配置Spring MVC内部组件时,它并不像只是定义一堆bean那样简单—— 至少不总是这样。 这是因为需要向请求提供更精细的MVC组件映射。 为了简化难度,Spring为我们提供了WebMvcConfigurerAdapter适配器,它是WebMvcConfigurer接口实现,我们可以扩展和覆盖我们需要的设置。

    在配置拦截器的特定情况下,我们可以重写addInterceptors(InterceptorRegistry registry)方法。 这是一个典型的回调方法,我们给予一个注册类,以便根据需要注册多个附加拦截器。 在MVC自动配置阶段,Spring Boot就像过滤器一样检测到WebMvcConfigurer的实例,并依次调用所有这些回调方法。 这意味着如果需要其他逻辑分离,可以有多个WebMvcConfigurer类的实现。

    五. 配置一个HttpMessageConverters转换器

    在构建RESTful Web服务时,我们定义了控制器,资源库,并在其上面添加了注解,但是从Java实体bean到HTTP数据流输出没有任何类型的对象转换。 实际上,Spring Boot自动配置了HttpMessageConverters转换器将实体bean对象转换为使用了Jackson类库的JSON的格式,将生成的JSON数据写入HTTP响应输出流。 当多个转换器可用时,根据消息对象类和请求的内容类型选择最适用的转换器。

    HttpMessageConverters的目的是将各种对象类型转换为相应的HTTP输出格式。 转换器可以支持一系列多种数据类型或多种输出格式,或两者的组合。 例如,MappingJackson2HttpMessageConverter类可以将任何Java对象转换为application/json的格式,而ProtobufHttpMessageConverter类只能对com.google.protobuf.Message的实例进行操作,但可以将其作为application/json,application/xml,text/plain或application/xprotobuf格式。 HttpMessageConverters不仅支持写入HTTP流,还支持将HTTP请求转换为适当的Java对象。

    我们可以通过多种方式配置转换器。 这一切都取决于你喜欢哪一个,或者想要实现多少控制。

    首先,我们在WebConfiguration类中增加ByteArrayHttpMessageConverter,并加上@Bean注解。

    @Bean
    public 
      ByteArrayHttpMessageConverter byteArrayHttpMessageConverter() {
        return new ByteArrayHttpMessageConverter();
    }
    

    另一种实现方式是重写WebConfiguration类中的configureMessageConverters方法,首先需要继承WebMvcConfigurerAdapter类,具体的代码如下:

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(new ByteArrayHttpMessageConverter());
    }
    

    如果想获取更多的控制,还可以重写extendMessageConverters方法。

    @Override
    public void 
      extendMessageConverters(List<HttpMessageConverter<?>> 
        converters) {
      converters.clear();
      converters.add(new ByteArrayHttpMessageConverter());
    }
    

    如上所示,Spring给了我们多种方式来实现同样的事情,这一切都取决于我们的偏好或具体的实现细节。

    我们介绍了将HttpMessageConverter添加到应用程序中的三种不同的方法。 那有什么区别呢?

    将HttpMessageConverter声明为@Bean是向应用程序添加自定义转换器的最快捷,最简单的方法。 它类似于我们在前面的例子中添加了Servlet过滤器。 如果Spring检测到一个HttpMessageConverter类型的bean,它将自动将其添加到列表中。 如果WebConfiguration类没有继承WebMvcConfigurerAdapter父类,那么这是首选方法。

    当应用程序需要指定WebMvcConfigurerAdapter的扩展以配置其他的东西,如拦截器,那么重写configureMessageConverters方法并将我们的转换器添加到列表将更为协调一致。可以从Spring Boot 的模块中添加多个WebMvcConfigurers实例并自动配置,但是不能保证我们的方法可以以任何特定的顺序被调用。

    如果我们需要做一些更加具体的事情,比如从列表中删除所有其他转换器或清除重复的转换器,这需要重写extendMessageConverters方法的地方。 所有WebMvcConfigurer被调用到configureMessageConverter方法并且转换器列表被完全填充后调用此方法。 当然,WebMvcConfigurer的其他一些实例完全可以重写extendMessageConverters, 但是这样做的机会并不多。

    六. 配置一个自定义的PropertyEditors编辑器

    在前面的例子中,我们学习了如何为HTTP请求和响应数据配置转换器。 还有其他类型的转换,特别是在将参数动态转换为各种对象时,例如String类型转换为Date或Integer。

    当我们在控制器中声明一个映射方法时,Spring使用确切的对象类型来自由定义方法签名。 这个方式是通过使用PropertyEditor实现的。 PropertyEditor是一个默认概念,定义为JDK的一部分,旨在允许将文本值转换为给定类型。 它最初用于构建Java Swing / AWT GUI,后来被证明是适合Spring需要将Web参数转换为方法参数类型的需要。

    Spring MVC已经为很多常见类型(如布尔型,货币型和类)提供了大量的PropertyEditor实现。 假设我们要创建一个Isbn类对象,并在我们的控制器中使用它,而不是一个纯粹的String类型。

    首先,我们需要在WebConfiguration类中移除extendMessageConverters方法,因为调用converters.clear()这段代码会中断渲染,因为删除了所有支持的类型转换器。

    然后,定义Isbn类,和对应的IsbnEditor属性编辑器,以及重写initBinde方法给我们的BookController类,使用以下内容配置IsbnEditor

    public class Isbn {
      private String isbn;
    
      public Isbn(String isbn) {
        this.isbn = isbn;
      }
    
      public String getIsbn() {
        return isbn;
      }
    }
    
    public class IsbnEditor extends PropertyEditorSupport {
      @Override
      public void setAsText(String text) throws IllegalArgumentException {
          if (StringUtils.hasText(text)) {
            setValue(new Isbn(text.trim()));
          }
          else {
            setValue(null);
          }
        }
    
      @Override
      public String getAsText() {
        Isbn isbn = (Isbn) getValue();
        if (isbn != null) {
          return isbn.getIsbn();
        }
        else {
          return "";
        }
      }
    }
    
    @InitBinder
    public void initBinder(WebDataBinder binder) {
      binder.registerCustomEditor(Isbn.class, new IsbnEditor());
    }
    

    第三步,在BookController类中修改getBook方法,以便可以接受 Isbn类型的对象,

    @RequestMapping(value = "/{isbn}", method = RequestMethod.GET)
    public Book getBook(@PathVariable Isbn isbn) {
      return bookRepository.findBookByIsbn(isbn.getIsbn());
    }
    

    第四步,启动./gradlew clean bootRun,启动成功以后,在浏览器中输入http://localhost:8080/books/978-1-78528-415-1。

    虽然我们不会观察到任何可见的更改,但IsbnEditor确实在工作,从{isbn}参数中创建Isbn类对象实例。我们打印了传递过来的Isbn实例,重写了toString()方法。

    属性转化为对象实例

    Spring自动配置大量的默认编辑器,但是对于自定义类型,我们必须明确地为每个Web请求实例化新的编辑器。 这是在控制器中使用@InitBinder注解的方法完成的。 扫描此注解,所有检测到的方法应具有接受WebDataBinder作为参数的签名。 除此之外,WebDataBinder还为我们提供了注册尽可能多的自定义编辑器的能力,要求控制器的方法被正确绑定。

    Tips
    PropertyEditor不是线程安全的!
    因此,我们必须为每个Web请求创建一个新的自定义编辑器实例,并将其注册到WebDataBinder。

    如果需要新的PropertyEditor,最好通过扩展PropertyEditorSupport类并自定义重写所需的方法来创建。

    七. 配置一个自定义的类型格式化类

    PropertyEditor因为它的状态和非线程安全,从版本3起,Spring添加了一个Formatter接口作为PropertyEditor的替代。 格式化类旨在提供类似的功能,但是以完全线程安全的方式,并专注于解析对象类型中的String并将对象转换为其字符串表示形式的非常具体的任务。

    对于我们的应用程序,希望有一个格式化程序可以使用一个字符串形式的书籍的ISBN号码并将其转换为一个Book实体对象。 这样,当请求URL签名仅包含ISBN号码或数据库ID时,就可以使用Book类型的参数定义控制器请求的方法。

    首先,在src/main/java/org/test/bookpub目录下创建一个新的包formatters,在此包下,创建BookFormatter类并实现Formatter接口,代码示例如下:

    public class BookFormatter implements Formatter<Book> {
      private BookRepository repository;
      public BookFormatter(BookRepository repository) {
        this.repository = repository;
      }
      @Override
      public Book parse(String bookIdentifier, Locale locale) throws ParseException {
        Book book = repository.findBookByIsbn(bookIdentifier);
        return book != null ? book : repository.findOne(Long.valueOf(bookIdentifier));
      }
      @Override
      public String print(Book book, Locale locale) {
        return book.getIsbn();
      }
    }
    

    然后,在WebConfiguration类中,重写addFormatters(FormatterRegistry registry)方法,并把BookFormatter类注册进去。

    @RequestMapping(value = "/{isbn}/reviewers", method = RequestMethod.GET)
    public List<Reviewer> getReviewers(@PathVariable("isbn") Book book) {
      return book.getReviewers();
    }
    

    接下来,在BookController类中,新增一个请求方法,用来根据给定的图书的 isbn来显示评论者,

    @RequestMapping(value = "/{isbn}/reviewers", method = RequestMethod.GET)
    public List<Reviewer> getReviewers(@PathVariable("isbn") Book book) {
      return book.getReviewers();
    }
    

    为了一些数据,现在手动添加一些测试数据填充数据库,通过向StartupRunner类添加两个自动装配的资源库:

    @Autowired private AuthorRepository authorRepository;
    @Autowired private PublisherRepository publisherRepository;
    

    下面这些代码添加到StartupRunner类的run(...)方法中:

    Author author = new Author("Alex", "Antonov");
    author = authorRepository.save(author);
    Publisher publisher = new Publisher("Packt");
    publisher = publisherRepository.save(publisher);
    Book book = new Book("978-1-78528-415-1", "Spring Boot Recipes", author, publisher);
    bookRepository.save(book);
    

    输入./gradlew clean bootRun,在控制台,启动应用程序。

    访问http://localhost:8080/books/978-1-78528-415-1/reviewers,在浏览器中可以看到如下结果:

    reviewers

    格式化功能旨在提供与PropertyEditors类似的功能。 通过将FormatterRegistry注册在重写的addFormatter方法中,告诉Spring使用Formatter将Book的文本表示转换为实体对象并返回。 由于格式化是无状态的,因此我们无需在控制器中每次都要注册; 我们只做一次就好,这确保Spring为每个Web请求使用它。

    Tips
    如果要定义一个常用类型的转换(例如String或Boolean),就像我们在IsbnEditor示例中所做的那样,最好是通过Controller的InitBinder方法中的PropertyEditors初始化来做,因为这样的改变可能不是全局所期望的,只是针对特定的控制器的功能。

    你可能已经注意到,我们还将BookRepository自动装配到WebConfiguration类,因为这是创建BookFormatter所需的。 这是Spring的一个很酷的东西,它让我们可以组合配置类,并使它们同时依赖于其他bean。 正如我们指出,为了创建一个WebConfiguration类,我们需要一个BookRepository,Spring确保在创建WebConfiguration类时首先创建BookRepository,然后自动注入作为依赖。 实例化WebConfiguration之后,将对其进行处理以进行配置说明。

  • 相关阅读:
    完全背包
    二分求值(二分适合求答案在两个数之间的题目)
    set<pair<int,int> >的用法
    01背包 (dp专题)
    矩阵快速幂
    BZOJ1977 [BeiJing2010组队]次小生成树 Tree
    BZOJ1854 [Scoi2010]游戏
    BZOJ1054 [HAOI2008]移动玩具
    NOIP系列复习及题目集合
    BZOJ2708 [Violet 1]木偶
  • 原文地址:https://www.cnblogs.com/IcanFixIt/p/6901949.html
Copyright © 2011-2022 走看看