Example 类的第一个注解是 @RestController。 这是一个称为 stereotype 注解。 它为阅读代码的人提供了提示;对 Spring 来说,它提供了特殊的角色。 在这种情况下,我们的类是一个 web @Controller,因此 Spring 在处理传入的 web 请求时会考虑它。
@RequestMapping 注解提供”路由”信息。 它告诉 Spring,任何带有 / 路径的 HTTP 请求都应该映射到 home 方法。@RestController 注解告诉 Spring 将结果以字符串的形式直接返回给调用方。
@RestController 和 @RequestMapping 注解是 Spring MVC 的注解。 (它们不是 Spring Boot 特有的。) 有关更多细节,请参见 Spring 参考文档中的 MVC 部分。
@EnableAutoConfiguration 注解
第二个类级别的注解是 @EnableAutoConfiguration。 这个注解告诉 Spring Boot 根据你添加的 jar 依赖项”猜测”您希望如何配置 Spring。 由于 spring-boot-starter-web 添加了 Tomcat 和 Spring MVC,因此自动装配假定您正在开发 web 应用程序并相应地设置 Spring。
Starter 和 自动装配
自动装配被设计成与”Starter”一起工作,但是这两个概念并没有直接联系在一起。 您可以在 Starter 之外自由选择 jar 依赖项。 Spring Boot 仍然尽最大努力自动配置应用程序。
“main” 方法
我们应用的最后一部分是 main 方法。 这只是遵循 Java 约定的应用程序入口点的标准方法。 我们的主方法委托给Spring Boot 的 SpringApplication 类,通过调用 run 来实现。 SpringApplication 启动我们的应用程序,启动 Spring,然后启动自动配置的 Tomcat web 服务器。 我们需要将 Example.class 作为参数传递给 run 方法,以告诉 SpringApplication 哪个是主 Spring 组件。 args 数组暴露出来以接收命令行参数。
我们通过创建一个完全自包含的可执行 jar 文件来完成示例,该文件可以在生产环境中运行。 可执行的 jar (有时称为”fat jars”)是存档文件,其中包含您编译的类以及您的代码需要运行的所有 jar 依赖项。
可执行的 jar 和 Java Java 没有提供加载嵌套 jar 文件(jar 文件本身包含在 jar 中)的标准方法。 如果您希望分发一个自包含的应用程序,这可能会有问题。 为了解决这个问题,许多开发人员使用”超级”罐子(uber jar)。 一个 uber jar 将应用程序的所有依赖项的所有类打包到一个归档文件中。这种方法的问题在于,很难看到应用程序中有哪些库。如果在多个 jar 中使用相同的文件名(但内容不同) ,也会出现问题。 Spring Boot 采用了一种不同的方法,它可以让您直接嵌套 jar。
<!-- Inherit defaults from Spring Boot --> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.5.RELEASE</version> </parent>
<!-- Add typical dependencies for a web application --> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies>
<!-- Package as an executable jar --> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
</project>
spring-boot-starter-parent 是使用 Spring Boot 的一个很好的方法,但它不可能在所有的时候都适用。 有时您可能需要从不同的父 POM 继承,或者您可能不喜欢我们的默认设置。 在这些情况下,请参阅第13.2.2节”在不使用父 POM 的情况下使用 Spring Boot”,以获得使用 import 范围的替代解决方案。
安装Spring Boot CLI
Spring Boot CLI (命令行接口)是一个命令行工具,您可以使用它来快速构建 Spring 的样例。 支持运行 Groovy 脚本,这代表你可以使用类似 Java 的语法,而不需要太多的样板代码。
您不需要使用 CLI 来处理 Spring Boot,但它绝对是获得 Spring 应用程序的最快方法。
下载完成后,请按照未打包的归档文件中的 INSTALL.txt 说明进行操作。 一般情况下,.zip 文件中有一个 bin 文件夹,在该文件夹下存放了一个 spring 脚本(spring.bat ,Windows平台下使用)。 或者,在遇到 .jar 时您可以使用 java -jar (该脚本可以帮助您确保classpath设置正确)。
安装 SDKMAN
SDKMAN!(The Software Development Kit Manager)能帮您管理各种版本的二进制 SDK,包括 Groovy 和 Spring Boot CLI。从 sdkman.io 可以获取 SDKMAN! ,然后运行以下命令来安装 Spring Boot:
1 2 3
$ sdk install springboot $ spring --version Spring Boot v2.1.5.RELEASE
如果您为 CLI 开发特性并希望方便地访问您构建的版本,请使用以下命令:
1 2 3 4
$ sdk install springboot dev /path/to/spring-boot/spring-boot-cli/target/spring-boot-cli-2.1.5.RELEASE-bin/spring-2.1.5.RELEASE/ $ sdk default springboot dev $ spring --version Spring CLI v2.1.5.RELEASE
通过上面的指令安装一个 spring 的本地实例,称为 dev 实例。 它指向您的目标构建位置,因此每次重新构建 Spring Boot 时,Spring 都是最新的。
你可以通过运行以下命令来查看它:
1 2 3 4 5 6 7 8 9 10 11 12 13
$ sdk ls springboot
================================================================================ Available Springboot Versions ================================================================================ > + dev * 2.1.5.RELEASE
================================================================================ + - local version * - installed > - currently in use ================================================================================
OSX Homebrew安装
如果你在 Mac 电脑上使用 Homebrew,则可以使用以下指令安装 Spring Boot CLI:
Class<?> clazz = Class.forName("org.spirit.lemon.reflect.User"); System.out.println("获取所有的公共构造方法:"); Constructor<?>[] constructors = clazz.getConstructors(); for (inti=0; i < constructors.length; i++) { System.out.println(constructors[i]); }
输出结果
1 2 3 4
获取所有的公共构造方法: public org.spirit.lemon.reflect.User(java.lang.Integer,java.lang.String,java.lang.String) public org.spirit.lemon.reflect.User(java.lang.Integer,java.lang.String,java.lang.String,int,java.lang.String) public org.spirit.lemon.reflect.User()
可以看出,只输出了三个public修饰的构造方法,私有的构造方法并未输出
public Constructor getConstructor(Class<?>… parameterTypes)
获取指定的公共构造方法: public org.spirit.lemon.reflect.User(java.lang.Integer,java.lang.String,java.lang.String)
私有构造方法测试: Exception in thread "main" java.lang.NoSuchMethodException: org.spirit.lemon.reflect.User.<init>(java.lang.Integer, java.lang.String) at java.lang.Class.getConstructor0(Class.java:3082) at java.lang.Class.getConstructor(Class.java:1825) at org.spirit.lemon.reflect.Test.main(Test.java:37)
可以看到,只能获取public修饰的构造方法,在获取private修饰的构造方法时,抛出了异常信息
public Constructor<?>[] getDeclaredConstructors()
获得所有的构造方法,包括(public, private,protected,默认权限的)
1 2 3 4 5
System.out.println("获取所有的公共构造方法(包括私有构造方法):"); Constructor[] constructors = clazz.getDeclaredConstructors(); for (inti=0; i < constructors.length; i++) { System.out.println(constructors[i]); }
输出结果
1 2 3 4 5
获取所有的公共构造方法(包括私有构造方法): private org.spirit.lemon.reflect.User(java.lang.Integer,java.lang.String) public org.spirit.lemon.reflect.User(java.lang.Integer,java.lang.String,java.lang.String) public org.spirit.lemon.reflect.User(java.lang.Integer,java.lang.String,java.lang.String,int,java.lang.String) public org.spirit.lemon.reflect.User()
不仅输出了public修饰的构造方法,private的构造方法也有输出
public Constructor getDeclaredConstructor(Class<?>… parameterTypes)
Exception in thread "main" java.lang.IllegalAccessException: Class org.spirit.lemon.reflect.Test can not access a member of classorg.spirit.lemon.reflect.User with modifiers "private" at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102) at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:296) at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:288) at java.lang.reflect.Constructor.newInstance(Constructor.java:413) at org.spirit.lemon.reflect.Test.main(Test.java:55)
// Process last-modified header, if supported by the handler. Stringmethod= request.getMethod(); booleanisGet="GET".equals(method); if (isGet || "HEAD".equals(method)) { longlastModified= ha.getLastModified(request, mappedHandler.getHandler()); if (logger.isDebugEnabled()) { logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified); } if (newServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return; } }
if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; }
// Actually invoke the handler. mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) { return; }
applyDefaultViewName(processedRequest, mv); mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception ex) { dispatchException = ex; } catch (Throwable err) { // As of 4.3, we're processing Errors thrown from handler methods as well, // making them available for @ExceptionHandler methods and other scenarios. dispatchException = newNestedServletException("Handler dispatch failed", err); } processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } catch (Exception ex) { triggerAfterCompletion(processedRequest, response, mappedHandler, ex); } catch (Throwable err) { triggerAfterCompletion(processedRequest, response, mappedHandler, newNestedServletException("Handler processing failed", err)); } finally { if (asyncManager.isConcurrentHandlingStarted()) { // Instead of postHandle and afterCompletion if (mappedHandler != null) { mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); } } else { // Clean up any resources used by a multipart request. if (multipartRequestParsed) { cleanupMultipart(processedRequest); } } } }
protectedvoidnoHandlerFound(HttpServletRequest request, HttpServletResponse response)throws Exception { if (pageNotFoundLogger.isWarnEnabled()) { pageNotFoundLogger.warn("No mapping found for HTTP request with URI [" + getRequestUri(request) + "] in DispatcherServlet with name '" + getServletName() + "'"); } if (this.throwExceptionIfNoHandlerFound) { //抛出异常 thrownewNoHandlerFoundException(request.getMethod(), getRequestUri(request), newServletServerHttpRequest(request).getHeaders()); } else { //返回HttpServletResponse.SC_NOT_FOUND,即404 response.sendError(HttpServletResponse.SC_NOT_FOUND); } }
获取处理器适配器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
protected HandlerAdapter getHandlerAdapter(Object handler)throws ServletException { //handlerAdapters 处理器适配器集合,存放所有的处理器适配器 for (HandlerAdapter ha : this.handlerAdapters) { if (logger.isTraceEnabled()) { logger.trace("Testing handler adapter [" + ha + "]"); } // 检查适配器是否支持给定的处理器 if (ha.supports(handler)) { return ha; } } // 找不到对应的适配器则抛出异常 thrownewServletException("No adapter for handler [" + handler + "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler"); }
value 是可选的,作用和 Ordered 接口中的排序值一样,value 越小,优先级越高,默认值是 Ordered.LOWEST_PRECEDENCE,最低优先级。
从 Spring 4.0 开始,Spring 中的很多组件都支持基于 Order 注解的排序,即使目标组件的排序值(目标类或 @Bean 方法)考虑的集合注入也是如此。虽然此类排序值可能会影响注入点的优先级,但请注意它们不会影响单例启动顺序,这是由依赖关系和 @DependsOn(影响运行时确定的依赖关系)声明确定的正交关注点。
从 Spring 4.1 开始,标准 Priority 注解可以用作排序方案中该注解的替代品。请注意, @Priority 当必须选择单个元素是,可能会有其他语义。具体参见 AnnotationAwareOrderComparator#getPriority。
/** * 返回指定类型的排序值 * <p>Takes care of {@link Order @Order} and {@code@javax.annotation.Priority}. * @param type 要处理的类型,即被注解的类 * @return 排序值,如果没有找到对应的类型,返回null * @see #getPriority(Class) */ publicstatic Integer getOrder(Class<?> type) { return getOrder(type, null); }
/** * 返回指定类型的排序值,如果没有指定的类型,则返回默认的排序值 * <p>Takes care of {@link Order @Order} and {@code@javax.annotation.Priority}. * @param type the type to handle * @return the priority value, or the specified default order if none can be found * @see #getPriority(Class) */ publicstatic Integer getOrder(Class<?> type, Integer defaultOrder) { //优先获取 Order 注解的值 Orderorder= AnnotationUtils.findAnnotation(type, Order.class); if (order != null) { return order.value(); } // 如果没有声明 Order 注解, 返回 Priority 注解的值 IntegerpriorityOrder= getPriority(type); if (priorityOrder != null) { return priorityOrder; } // 如果两个注解都为 null, 则返回传入的默认排序值 defaultOrder return defaultOrder; }
/** * 返回 {@code javax.annotation.Priority} 注解的值,如果类型上没有声明 Priority 注解,则返回 null * @param type the type to handle * @return the priority value if the annotation is declared, or {@code null} if none */ publicstatic Integer getPriority(Class<?> type) { if (priorityAnnotationType != null) { // 获取类上面的 Priority 注解类对象 Annotationpriority= AnnotationUtils.findAnnotation(type, priorityAnnotationType); if (priority != null) { // 获取注解类的 value 值 return (Integer) AnnotationUtils.getValue(priority); } } //如果 Priority 注解没有加载,则直接返回 null returnnull; }