如何搭建一个SpringBoot项目
步骤
1、创建一个普通maven项目:SpringBootDemo
2、在pom.xml文件设置项目打包方式
<packaging>jar</packaging>
3、在pom.xml文件导入SpringBoot父项目
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.4.3</version> <relativePath/> </parent>
4、在pom.xml文件导入Web项目依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
5、编写普通Controller类
/** * 控制层 */ @Controller public class HelloController { @RequestMapping("hello") public Object hellp(){ return "Hello SpringBoot!"; } }
6、编写启动类,启动SpringBoot项目
/** * SpringBoot项目启动类 */ @SpringBootApplication public class SpringBootDemoApplication { public static void main(String[] args) { SpringApplication.run(SpringBootDemoApplication.class,args); } }
7、浏览器访问测试: http://localhost:8080/hello
问题
- Web打包不应该是war,为什么是jar?
- pom.xml文件中继承的spring-boot-starter-parent有什么用?
- pom.xml文件中导入的依赖spring-boot-starter-web又有什么用?
- 启动类SpringBootDemoApplication头顶上的@SpringBootApplication注解做了什么?
- 没有配置tomcat,没有设置端口服务器怎么来,又怎么启动的?
- main方法中SpringApplication.run(..)又是什么情况?
分析
问题1:Web打包不应该是war,为什么是jar
SpringBoot默认的打包方式就是jar,传统的外接tomcat或插件式tomcat与web项目是相互独立,相互对接时必须满足一定规则,war包结构项目就是规则约定之一。而SpringBoot项目采用的是内嵌式(简单理解为编码方式)tomcat,项目与tomcat融为一体,部署,启动,运行一体化,就没有各种条条框框的约束了。
问题2:pom.xml文件中继承的spring-boot-starter-parent有啥用
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.4.3</version> <relativePath/> </parent>
spring-boot-starter-parent其实是SpringBoot提供了工具工程,工程收集了市面上常用各种第三方jar依赖,并对这些jar依赖进行了版本管理。当我们的项目继承这个项目工程,就自动成为SpringBoot项目,并无偿继承了这些第三方jar依赖。后续项目中需要某个第三方依赖,只需要引入依赖坐标即可, 不需要关系依赖版本。比如:
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency>
一个拓展:
继承是 Maven 中很强大的一种功能,继承可以使得子POM可以获得 parent 中的部分配置(groupId,version,dependencies,build,dependencyManagement等),可以对子pom进行统一的配置和依赖管理。
- parent项目中的dependencyManagement里的声明的依赖 , 只具有声明的作用,并不实现引入,因此子项目需要显式的声明需要用的依赖。如果不在子项目中声明依赖,是不会从父项目中继承下来的;只有在子项目中写了该依赖项,可以不指定具体版本,会从父项目中继承该项,并且version和scope都读取自父pom;另外如果子项目中指定了版本号,那么会使用子项目中指定的jar版本
- parent项目中的dependencies里声明的依赖会被所有的子项目继承
问题3:pom.xml文件中导入的依赖spring-boot-starter-web又有啥用
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
问题2中说了,SpringBoot收集了很多常用的第三方jar依赖,为了进一步提高用户体验感,SpringBoot根据jar依赖特点,对这些依赖进一步分类,整合,再封装,形成一个新的依赖工具集, 每个工具集都解决特定领域的依赖问题。SpringBoot将这些依赖工具称为:启动器(组件),命名规则: spring-boot-starter-xxx。 如果是非SpringBoot定制的启动器, 一般命名规则:xxx-spring-boot-starter
入门案例中导入的spring-boot-starter-web 就是用于解决web项目依赖的工具集(启动器)
编辑
常用的启动器(starter)还有很多:
spring-boot-starter: 核心启动器 , 提供了自动配置,日志和YAML配置支持
spring-boot-starter-aop: 支持使用
Spring AOP
和AspectJ
进行切面编程。spring-boot-starter-freemarker: 支持使用
FreeMarker
视图构建Web 应用spring-boot-starter-test: 支持使用
JUnit
, 测试Spring Boot
应用spring-boot-starter-web: 支持使用
Spring MVC
构建 Web 应用,包括RESTful
应用,使用Tomcat
作为默认的嵌入式容器。spring-boot-starter-actuator: 支持使用 Spring Boot Actuator 提供生产级别的应用程序监控和管理功能。
spring-boot-starter-logging: 提供了对日志的支持 , 默认使用Logback
问题4:启动类App头顶上的@SpringBootApplication注解做了什么
要回答这个问题, 就必须讲清楚springboot的自动配置原理(也称自动装配)
springboot的精髓就在这个注解里。
@SpringBootApplication public class App { public static void main(String[] args) { SpringApplication.run(App.class, args); } }
先回顾一下以前使用SpringMVC搭建web项目时, 我们是怎么做的
1>导入Web, Spring, SpringMVC依赖
2>在web.xml文件配置前端控制器:DispatcherServlet
3>编写controller类
4>配置controller扫描包路径
5>部署到tomcat并启动
从上面步骤上看,刚刚入门案例不需要我们去做的有几步?
1:不需要配置前端控制器
2:不需要配置包扫描路径
3:不需要部署tomcat这步
这些步骤我们都没做,项目却跑起来,那肯定有人帮我们做了,那会是谁?所有问题关键就是这个:@SpringBootApplication注解。
@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 { ...... }
注解上面又有一堆注解,核心的有3个:
- @SpringBootConfiguration
- @EnableAutoConfiguration
- @ComponentScan
这里注意:注解头顶又贴有注解(非元注解),表示当前注解为复合注解,当前注解也拥有所贴注解功能。
@ComponentScan
功能:spring组件扫描注解,扫描所贴的类所在包及其子包所有贴有版型注解的类,并创建对象交给spring容器管理。
入门案例中, 我们将@SpringBootApplication 注解贴在App类里面,那么它会扫描App类所有包及其子包里面所有贴有版型注解的类,并创建实例, 那么controller包下的HelloController类就可以被扫描到。
一个注意点: 一般贴有@SpringBootApplication注解的类称之为启动类,建议放置在根包下。
@SpringBootConfiguration
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Configuration public @interface SpringBootConfiguration { .... }
该注解头顶贴了@Configuration
注解, 前面Javaconfig讲过贴有@Configuration
类为配置类
@SpringBootApplication 头顶贴有 @SpringBootConfiguration
@SpringBootConfiguration 头顶贴有 @Configuration
根据上面注解关系推导下:入门案例中贴有@SpringBootApplication注解的App类就是一个项目配置类。
一个注意点:一般springboot项目只有唯一一个启动类,启动类都是配置类。
@EnableAutoConfiguration
@EnableAutoConfiguration
的作用,告诉SpringBoot基于你所添加的依赖,去“猜测”你想要如何配置Spring。比如当我们引入了spring-boot-starter-web
这个启动器,spring会解析这个启动器,推测当前项目为web项目,根据启动器默认配置要求自动配置web及SpringMVC相关对象创建与管理。比如:前端控制的创建,比如前端控制的路径映射等。
SpringBoot对自带启动器与第三方启动器进行了大量的默认配置,这些配置是否生效,是否加载到spring容器中使用,取决于我们项目是否引入了对启动器依赖,如果有那么默认配置就会生效。
那么
- 这些默认配置是在哪里定义的呢?
- 为何依赖引入就会触发配置生效呢?
看源码:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration { ... }
@EnableAutoConfiguration注解import导入了一个AutoConfigurationImportSelector自动配置类选择器,该类可以实现配置类批量载入 spring容器。其核心方法是getCandidateConfigurations
protected List<String> getCandidateConfigurations( AnnotationMetadata metadata, AnnotationAttributes attributes) { List<String> configurations = SpringFactoriesLoader.loadFactoryNames( getSpringFactoriesLoaderFactoryClass(),getBeanClassLoader()); Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you " + "are using a custom packaging, make sure that file is correct."); return configurations; }
getCandidateConfigurations方法作用是委托SpringFactoriesLoader去读取jar包中的META-INF/spring.factories文件, 并加载里面配置的自动配置对象,其中包括: AOP / PropertyPlaceholder / FreeMarker / HttpMessageConverter / Jackson / DataSource / DataSourceTransactionManager / DispatcherServlet / WebMvc 等等。
编辑
一个注意点:不管是spring定制的启动器,还是第三方定制的启动器,都需要编写META-INF/spring.factories,里面指定启动器的自动配置类
接上面分析, spring.factories文件中配置很多自动配置类,哪些会生效(加载到Spring容器并跑起来)呢, 此时需要满足这些启动类生效条件才会自动装配。
以我们熟悉SpringMVC为例子:
在spring.factories找WebMvcAutoConfiguration自动配置类
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration
点进去看源码
@Configuration(proxyBeanMethods = false) @ConditionalOnWebApplication(type = Type.SERVLET) @ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class }) @ConditionalOnMissingBean(WebMvcConfigurationSupport.class) @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10) @AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class }) public class WebMvcAutoConfiguration { ... }
我们看到这个类上的4个注解:
@Configuration
:声明这个类是一个配置类
@ConditionalOnWebApplication(type = Type.SERVLET)
ConditionalOn,翻译就是在某个条件下,此处就是满足项目的类是是Type.SERVLET类型,我们现在的项目就满足了, 就是一个web服务(web项目)@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
这里的条件是OnClass,也就是满足以下类存在:Servlet、DispatcherServlet、WebMvcConfigurer。这里就是判断你是否引入了SpringMVC相关依赖,引入依赖后该条件成立,当前类的配置才会生效!@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
这个条件与上面不同,OnMissingBean,是说环境中没有指定的Bean这个才生效。其实这就是自定义配置的入口,也就是说,如果我们自己配置了一个WebMVCConfigurationSupport的bean, 代表容器里已经存在该bean了,那么这个默认配置就会失效!这里允许我们定制自己的bean替换spring默认的。
接着,我们查看WebMvcAutoConfiguration该类中定义了什么:(如果它生效了,会创建什么对象并载入容器进行管理)
视图解析器:
@Bean @ConditionalOnMissingBean public InternalResourceViewResolver defaultViewResolver() { InternalResourceViewResolver resolver = new InternalResourceViewResolver(); resolver.setPrefix(this.mvcProperties.getView().getPrefix()); resolver.setSuffix(this.mvcProperties.getView().getSuffix()); return resolver; } @Bean @ConditionalOnBean(View.class) @ConditionalOnMissingBean public BeanNameViewResolver beanNameViewResolver() { BeanNameViewResolver resolver = new BeanNameViewResolver(); resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 10); return resolver; }
WebMvcAutoConfiguration中使用了@AutoConfigureAfter注解, 意为指定的类加载完了后,再加载本类
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class })
而DispatcherServletAutoConfiguration中又做了很多事情, 比如配置了前端控制器
@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME) public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) { DispatcherServlet dispatcherServlet = new DispatcherServlet(); dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest()); dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest()); dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties. isThrowExceptionIfNoHandlerFound()); dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents()); dispatcherServlet.setEnableLoggingRequestDetails(webMvcProperties.isLogRequestDetails()); return dispatcherServlet; }
总结:
@SpringBootApplication注解内部是3大注解功能的集成
- @ComponentScan: 开启组件扫描
- @SpringBootConfiguration: 作用等同于@Configuration注解,也是用于标记配置类
- @EnableAutoConfiguration: 内部导入AutoConfigurationImportSelector,该类中有个getCandidateConfigurations方法, 读取jar包中META-INF/spring.factories文件中配置类, , 再根据条件进行加载和配置, 比如: AOP / PropertyPlaceholder / FreeMarker / HttpMessageConverter / Jackson / DataSource / DataSourceTransactionManager / DispatcherServlet / WebMvc 等等
问题5:没有配置tomcat,没有设置端口服务器怎么来,又怎么启动的
SpringBoot使用嵌入式tomcat,编程实现,默认端口是8080,后续可以通过application.properties文件进行修改
编辑
问题6:main方法中SpringApplication.run(..)又是什么情况
SpringApplication.run(..)的作用:
- 启动SpringBoot应用
- 加载自定义的配置类,完成自动配置功能
- 把当前项目部署到嵌入的Tomcat服务器
- 启动Tomcat服务器跑项目
总结
SpringBoot项目完整结构
编辑
SpringBoot优缺点
优点:
- 创建独立运行的Spring应用程序 ;
- 嵌入的Tomcat,无需部署war文件;
- 简化Maven配置 ;
- 自动配置Spring ;
- 提供生产就绪型功能,如:日志,健康检查和外部配置等;
- 不要求配置 XML;
- 非常容易和第三方框架集成起来。
缺点:
- 版本更新较快,可能出现较大变化;
- 因为约定大于配置,所以经常会出现一些很难解决的问题。