SpringBoot实战 一

Spring Initializer

SpringBoot实战 一

左侧配置项目详情,包括项目使用的依赖管理工具、语言、SpringBoot版本、项目源信息等。

右侧为项目选择需要的功能,各种功能被SpringBoot封装成一个个starter。

比如你想要创建一个web项目,使用jpa作为持久化技术,h2作为开发阶段嵌入式数据库,使用thymeleaf模板,那你就可以添加这些starter。

SpringBoot实战 一

starter的主要功能就是自动为你引入依赖,如果是我们手工引入依赖,就拿Spring Data Jpa来说,在没使用SpringBoot时,我们想要使用spring-data-jpa,我们得先引入JDBC,如果需要事务,你还要引入事务相关的依赖,而且我们要考虑这些依赖版本能不能互相兼容,有没有潜在的兼容性问题。

starter是经过Spring官方测试的一些maven项目而已,它们会自动帮我们引入一些相关依赖,并且我们无需考虑兼容性问题。

如下是我们使用Spring Initializer后得到的pom,jpa、thymeleaf、web、test等相关的依赖都被定义在这些starter中了,通过maven的依赖传递性引入到我们的项目中。

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <scope>runtime</scope>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

随便点开一个starter,这里就用thymeleaf的举例,可以看到这个starter里才是实际引入thymeleaf的位置。

<dependencies>
    <dependency>
        <groupId>org.thymeleaf</groupId>
        <artifactId>thymeleaf-spring5</artifactId>
        <version>3.0.12.RELEASE</version>
        <scope>compile</scope>
    </dependency>
    <dependency>
        <groupId>org.thymeleaf.extras</groupId>
        <artifactId>thymeleaf-extras-java8time</artifactId>
        <version>3.0.4.RELEASE</version>
        <scope>compile</scope>
    </dependency>
</dependencies>

所以starter的目的就是将我们从具体的依赖项和依赖项的版本兼容中解脱出来,如果你要web相关的依赖,那么直接使用一个spring-boot-starter-web,如果你需要jpa相关的功能,那么直接使用spring-boot-starter-data-jpa

如果你很介意starter将你不需要的依赖也传导进你的项目中了,或者你需要使用和starter中定义版本不同的依赖,可以使用exclusions来排除项目。

项目结构

SpringBoot实战 一

如上是使用Spring Initializer得到的项目结构。当然,controller、domain和repository都是我们后添加进去的。

项目遵循maven和gradle的项目结构,src/main/java存放java代码,src/test/java存放测试代码,src/main/resources存储资源。

static文件用于存储web项目中的静态资源,templates用存储视图模板,application.properties是项目的配置文件,BooklistApplication是项目的启动类。

@SpringBootApplication
public class BooklistApplication {

    public static void main(String[] args) {
        SpringApplication.run(BooklistApplication.class, args);
    }

}

启动类很简单,使用了一个@SpringBootApplication注解并在主方法中调用SpringApplication.run启动程序。

@SpringBootApplication注解代码如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
    // ... 
}

@EnableAutoConfiguration启用自动配置功能,@ComponentScan用来扫描包下的类作为Bean,所以我们直接在包下编写@Controller@Service@Repository都可以被SpringBoot扫描。

编写一个项目

图书列表项目

实体类

@Entity
public class Book {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    private String reader;
    private String isbn;
    private String title;
    private String author;
    private String description;
}

Repository

@Repository
public interface BookRepository extends JpaRepository<Book, Long> {
    List<Book> findBooksByReader(String reader);
}

Controller

@RequestMapping("/")
public class BookController {
    @Autowired
    private BookRepository bookRepository;

    @GetMapping("/{reader}")
    public String readerBooks(@PathVariable String reader, Model model) {
        List<Book> books = bookRepository.findBooksByReader(reader);
        model.addAttribute("books", books);
        return "readingList";
    }

    @PostMapping("/{reader}")
    public String addToReadingList(
            @PathVariable("reader") String reader,
            Book book
    ) {
        book.setReader(reader);
        bookRepository.save(book);
        return "redirect:/{reader}";
    }
}

模板

<!DOCTYPE html>
<html lang="en">
<head>
    
    <title>Reading List</title>
</head>
<body>
    <h2>Your Reading List</h2>
    <div th:unless="${#lists.isEmpty(books)}">
        <dl th:each="book : ${books}">
            <dt class="bookHeadline">
                <span th:text="${book.title}">TITLE</span> by
                <span th:text="${book.author}">AUTHOR</span>
                (ISBN: <span th:text="${book.isbn}"></span>)
            </dt>
            <dd class="bookDescription">
                <span th:if="${book.description}" th:text="${book.description}">DESCRIPTION</span>
                <span th:if="${book.description eq null}">No Description Available</span>
            </dd>
        </dl>
    </div>
    <div th:if="${#lists.isEmpty(books)}">
        You have no books in your list
    </div>

    <hr>

    <h3>Add A Book</h3>
    <form method="post">
        Title: <input type="text" name="title"><br>
        Author: <input type="text" name="author"><br>
        isbn: <input type="text" name="isbn"><br>
        Description: <textarea type="text" name="description" cols="80"></textarea><br>
        <input type="submit" value="Submit"/>
    </form>
</body>
</html>

现在这个项目就能运行了,确实没有使用一行配置代码。

自动配置是如何运行的

前面,确实没有使用一行配置代码,项目就跑起来了。前几天我使用SpringMVC编写程序时,光是配置就弄了一下午。

条件化定义Bean

要想了解自动配置是如何运行的,就得知道Spring的条件化Bean定义。

Spring提供了@Conditional注解,这个注解需要指定一个实现Condition接口的类,这个类代表一个条件,条件的真假在matches方法中返回。比如下面的条件用来查看classpath中是否存在JdbcTemplate

SpringBoot实战 一

然后我们定义Bean时可以这样

SpringBoot实战 一

这时只有当JdbcTemplate类存在于classpath下,才创建这个MyServiceBean。

Spring还提供了一些方便的条件注解

SpringBoot实战 一

AutoConfiguration

所以我们就可以大体猜到了,这些自动配置的Bean全是根据条件创建的。

比如之前如果我们想使用Thymeleaf,我们得先定义一个SpringResourceTemplateResolver Bean,现在SpringBoot使用条件来自动决策是否定义这个Bean,所以便不用我们配置了。

这是ThymeleafAutoConfiguration,它就是一个普通的Configuration类,唯一不同的是,它检测了类路径下是否有需要的类,如果有,这个配置类才生效。

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(ThymeleafProperties.class)
@ConditionalOnClass({ TemplateMode.class, SpringTemplateEngine.class })
@AutoConfigureAfter({ WebMvcAutoConfiguration.class, WebFluxAutoConfiguration.class })
public class ThymeleafAutoConfiguration {
    // ...
}

再看看其中定义视图解析器的代码,也不过是检测,如果不存在thymeleafViewResolver这个Bean的话就定义。

@Configuration(proxyBeanMethods = false)
static class ThymeleafViewResolverConfiguration {

    @Bean
    @ConditionalOnMissingBean(name = "thymeleafViewResolver")
    ThymeleafViewResolver thymeleafViewResolver(ThymeleafProperties properties,
            SpringTemplateEngine templateEngine) {
        ThymeleafViewResolver resolver = new ThymeleafViewResolver();
        resolver.setTemplateEngine(templateEngine);
        resolver.setCharacterEncoding(properties.getEncoding().name());
        resolver.setContentType(
                appendCharset(properties.getServlet().getContentType(), resolver.getCharacterEncoding()));
        resolver.setProducePartialOutputWhileProcessing(
                properties.getServlet().isProducePartialOutputWhileProcessing());
        resolver.setExcludedViewNames(properties.getExcludedViewNames());
        resolver.setViewNames(properties.getViewNames());
        // This resolver acts as a fallback resolver (e.g. like a
        // InternalResourceViewResolver) so it needs to have low precedence
        resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 5);
        resolver.setCache(properties.isCache());
        return resolver;
    }
    // ...
}
上一篇:csv文件转换json文件


下一篇:一文读懂 .NET 中的高性能队列 Channel