一、前言
随着 Spring 的功能越来越强,在使用 Spring 的时候,门槛也变得高了起来,诸如搭建一个基于 Spring 的 Web 程序却并不简单,需要进行各种配置,Spring 通过多年的发展,本身变得很臃肿,过时的使用方式,如 XML 系统配置方式,没有被淘汰的同时,出现了新的注解配置方式,这给初学者带来了使用上的混乱。同时 Spring 也集成了越来越多的第三方技术,如何方便地使用这些技术,各版本之间是否会产生冲突,也需要一定的实践。庆幸的是,Spring 的开发者们认识到了这个问题,推出了基于 Spring 技术的 Spring Boot,解决了上面的许多问题,尤其是上手难,技术使用不统一这两个缺点。Sping Boot 简化了 Spring 应用开发,不需要配置就能运行 Spring 应用,Spring Boot 管理Spring 容器,第三方插件,并提供很多系统级别的服务。
二、环境搭建
由于 Spring Boot 需要 JDK8、Maven3 的支持,所有需要配置对应版本的环境:
2.1 下载 JDK
在系统环境变量中设置好 JAVA_HOME,PATH,CLASSPATH。JDK 所有版本下载地址为:http://www.oracle.com/technetwork/java/javase/archive-139210.html
比如下载Windos 64 位 JDK1.8.0_144,地址为:http://download.oracle.com/otn/java/jdk/8u144-b01/090f390dda5b47b9b721c7dfaa008135/jdk-8u144-windows-x64.exe,可以下载 Windos,Linux,Mac 等版本的 JDK。
2.2 下载 Maven
在系统环境变量中设置 M2_HOME。Maven3 所有版本的下载地址为:https://archive.apache.org/dist/maven/maven-3/3.3.9/binaries/
比如下载 apache-maven-3.3.9,地址为:https://archive.apache.org/dist/maven/maven-3/3.3.9/binaries/apache-maven-3.3.9-bin.zip
2.3 下载 CURL 工具
curl 是利用 URL 语法在命令行方式下工作的开源文件传输工具。它被广泛应用在 UNIX、多种 Linux 发行版中,并且有 DOS 和 Win32、Win 64 下的移植版本,Mac 和 Linux 中,会自带 curl,但是 Windos 系统,需要自己下载安装,如果已经安装了 git shell,也会自带了 curl。curl 自行下载地址为:https://curl.haxx.se/download.html
比如拖动到最下面,下载 Win64 x86_64 CAB,并在系统环境变量中设置 CURL_HOME,在 PATH 末尾添加";%CURL_HOME%\I386",地址为:https://skanthak.homepage.t-online.de/download/curl-7.57.0.cab
三、创建应用
3.1 以 Eclipse 为例,File - New - Maven Project,勾选 Create a simple project(skip archetype selection),
进入到填写坐标的对话框中,组织 Group Id 填写:springboot.example,项目标识 Artifact Id 填写:spring-boot-hello,版本号 Version 和打包方式 Packaging 可以保持默认,如下所示:
再创建相应的包,目录结构如下:
3.2 添加依赖
在工程的 pom.xml 中添加 maven 依赖,其中 spring-boot-starter-web 属于搭建 Web 应用时必须添加的依赖,默认会使用内置 Tomcat,并支持 Spring MVC、RESTFul服务等,如下
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>springboot.example</groupId>
<artifactId>spring-boot-hello</artifactId>
<version>0.0.1-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.6.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- 上边已经引入 parent,因此下边无需指定版本 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
3.3 创建启动类
在 com.light.springboot 包下,创建 SpringbootApplication.java,如下:
package com.light.springboot;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* 该注解指定项目为springboot,由此类当作程序入口,自动装配web依赖的环境
* @author Administrator
*
*/
@SpringBootApplication
public class SpringbootApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootApplication.class, args);
}
}
3.4 创建演示案例
在 com.light.springboot.controller 包下,创建 HelloWorldController.java,基于 RestFul 架构风格的请求,如下:
package com.light.springboot.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping(value="/helloWorldController")
public class HelloWorldController {
@GetMapping("/helloworld1/{name}")
public String helloworld1(@PathVariable(required = false) String name) {
return "hello" + name + "World1";
}
}
四、启动应用
打开 SpringbootApplication.java,右键 Run as - Java Application,控制台显示如下日志:
21:25:44.465 [main] DEBUG org.springframework.boot.devtools.settings.DevToolsSettings - Included patterns for restart : []
21:25:44.467 [main] DEBUG org.springframework.boot.devtools.settings.DevToolsSettings - Excluded patterns for restart : [/spring-boot-starter/target/classes/, /spring-boot-autoconfigure/target/classes/, /spring-boot-starter-[\w-]+/, /spring-boot/target/classes/, /spring-boot-actuator/target/classes/, /spring-boot-devtools/target/classes/]
21:25:44.467 [main] DEBUG org.springframework.boot.devtools.restart.ChangeableUrls - Matching URLs for reloading : [file:/F:/workspace/spring-boot-hello/target/classes/]
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v1.5.6.RELEASE)
2018-01-21 21:25:44.708 INFO 7496 --- [ restartedMain] c.l.springboot.SpringbootApplication : Starting SpringbootApplication on PC201601051134 with PID 7496 (F:\workspace\spring-boot-hello\target\classes started by Administrator in F:\workspace\spring-boot-hello)
2018-01-21 21:25:44.709 INFO 7496 --- [ restartedMain] c.l.springboot.SpringbootApplication : No active profile set, falling back to default profiles: default
2018-01-21 21:25:44.760 INFO 7496 --- [ restartedMain] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@3e2d4e88: startup date [Sun Jan 21 21:25:44 CST 2018]; root of context hierarchy
2018-01-21 21:25:46.681 INFO 7496 --- [ restartedMain] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 8080 (http)
2018-01-21 21:25:46.694 INFO 7496 --- [ restartedMain] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2018-01-21 21:25:46.695 INFO 7496 --- [ restartedMain] org.apache.catalina.core.StandardEngine : Starting Servlet Engine: Apache Tomcat/8.5.16
2018-01-21 21:25:46.780 INFO 7496 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2018-01-21 21:25:46.780 INFO 7496 --- [ost-startStop-1] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 2023 ms
......
2018-01-21 21:25:47.304 INFO 7496 --- [ restartedMain] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@3e2d4e88: startup date [Sun Jan 21 21:25:44 CST 2018]; root of context hierarchy
2018-01-21 21:25:47.373 INFO 7496 --- [ restartedMain] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/helloWorldController/helloworld1/{name}],methods=[GET]}" onto public java.lang.String com.light.springboot.controller.HelloWorldController.helloworld1(java.lang.String)
......
2018-01-21 21:25:48.286 INFO 7496 --- [ restartedMain] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 8081 (http)
2018-01-21 21:25:48.287 INFO 7496 --- [ restartedMain] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2018-01-21 21:25:48.287 INFO 7496 --- [ restartedMain] org.apache.catalina.core.StandardEngine : Starting Servlet Engine: Apache Tomcat/8.5.16
2018-01-21 21:25:48.299 INFO 7496 --- [ost-startStop-1] o.a.c.c.C.[Tomcat-1].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2018-01-21 21:25:48.300 INFO 7496 --- [ost-startStop-1] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 172 ms
2018-01-21 21:25:48.310 INFO 7496 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean : Mapping servlet: 'dispatcherServlet' to [/]
2018-01-21 21:25:48.310 INFO 7496 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'springSecurityFilterChain' to: [/*]
2018-01-21 21:25:48.395 INFO 7496 --- [ restartedMain] o.s.b.a.e.mvc.EndpointHandlerMapping : Mapped "{[/manage/mappings || /manage/mappings.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2018-01-21 21:25:48.396 INFO 7496 --- [ restartedMain] o.s.b.a.e.mvc.EndpointHandlerMapping : Mapped "{[/manage/trace || /manage/trace.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2018-01-21 21:25:48.399 INFO 7496 --- [ restartedMain] o.s.b.a.e.mvc.EndpointHandlerMapping : Mapped "{[/manage/shutdown || /manage/shutdown.json],methods=[POST],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.ShutdownMvcEndpoint.invoke()
......
2018-01-21 21:25:48.533 INFO 7496 --- [ restartedMain] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8081 (http)
2018-01-21 21:25:48.537 INFO 7496 --- [ restartedMain] o.s.c.support.DefaultLifecycleProcessor : Starting beans in phase 0
2018-01-21 21:25:48.606 INFO 7496 --- [ restartedMain] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2018-01-21 21:25:48.610 INFO 7496 --- [ restartedMain] c.l.springboot.SpringbootApplication : Started SpringbootApplication in 4.13 seconds (JVM running for 4.463)
日志中会显示已经扫描到的 Mapping,tomcat 启动时间等信息。启动成功之后输入:http://localhost:8080/helloWorldController/helloworld1/Java,如下图所示:
4.1 添加热部署
当我们修改或者添加类以及配置文件时,必须再次运行应用,对于开发者来说非常不方便。Spring Boot 提供了 spring-boot-devtools,它能在修改类或者配置文件的时候自动重新加载 Spring Boot 应用,需要在 pom 文件中添加如下依赖:
<!-- 热部署 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
配置好 pom.xml 文件,待依赖的包下载完毕之后,我们启动项目,随便创建/修改一个文件并保存,会发现控制台打印 springboot 重新加载文件的信息。如向 HelloWorldController.java 中添加方法:
@RequestMapping(value="/helloworld2/{name}")
public String helloworld2(@PathVariable(value = "name",required = false) String name) {
return "hello" + name + "World2";
}
同时,Spring Boot 会重新加载,如下:
......2018-01-21 21:39:23.664 INFO 7496 --- [ restartedMain] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/helloWorldController/helloworld2/{name}]}" onto public java.lang.String com.light.springboot.controller.HelloWorldController.helloworld2(java.lang.String)......
五、关闭应用
5.1 打包应用
Spring Boot 应用可以打成 jar 包,其中内置了 tomcat,因此可以直接启动使用。上面的演示中是使用启动类启动了应用,此处将应用打成 jar 包,通过 CMD 进入到工程的根目录下执行命令:mvn clean package -Dmaven.test.skip=true,或**在 Eclipse 中自定义 maven 命令,运行 run -> Maven build...**,在 Goals 中填写 clean package -Dmaven.test.skip=true,运行,日志中出现 BUILD SUCCESS 则打包完成。注意:maven 跳过测试打包可以使用 mvn clean package -DskipTests 或者 mvn clean package -Dmaven.test.skip=true,但是这两个命令并不完全相同。
在使用 mvn clean package 进行编译、打包时,Maven会执行 src/test/java 中的 JUnit 测试用例,有时为了跳过测试,会使用参数 -DskipTests 和 -Dmaven.test.skip=true,这两个参数的主要区别是:
-DskipTests,不执行测试用例,但编译测试用例类生成相应的 class 文件至 target/test-classes 下;
-Dmaven.test.skip=true,不执行测试用例,也不编译测试用例类。
5.2 运行应用
进入到生成 jar 包的目录下 target\spring-boot-hello-0.0.1-SNAPSHOT.jar,执行:java -jar spring-boot-hello-0.0.1-SNAPSHOT.jar 命令,运行应用,显示的日志与 3.4 中的一样,最后启动成功,便可以通过浏览器访问应用。
5.3 关闭应用
由于 Spring Boot 内置了 tomcat,所以我们不能直接关闭关闭,需要添加 spring-boot-starter-actuator 依赖,建议在开发环境中禁用密码验证,在正式环境中开启密码验证,如下
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
在 resources 目录下新建 application.properties 文件,并添加:
# 启用shutdown
endpoints.shutdown.enabled=true
# 禁用密码验证
endpoints.shutdown.sensitive=false
在 CMD 中执行关闭命令:curl -X POST http://localhost:8080/shutdown,则显示:{"message":"Shutting down, bye..."},如下:
如果要配置路径,需要在 application.properties 中添加 management.context-path=/manage,则关闭命令变为:curl -X POST host:port/manage/shutdown
5.4 安全验证
通过 5.3 中的命令可以直接关闭应用,但是每个人都可以关闭,很不安全,我们通过设置用户名和密码来管理关闭的权限验证,但是要添加 spring-boot-starter-security 依赖,如下:
<!-- 安全验证 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
且 application.properties 需要被修改为:
# 启用shutdown
endpoints.shutdown.enabled=true
# 开启密码验证
endpoints.shutdown.sensitive=true
# 验证用户名
security.user.name=admin
# 验证密码
security.user.password=secret
# 角色
management.security.role=SUPERUSER
# 也可以统一指定所有endpoints的路径'management.context-path=/manage'
management.context-path=/manage
# 指定端口
management.port=8081
# 指定地址
management.address=127.0.0.1
使用安全验证之后,则命令变为:curl -u admin:secret -X POST http://127.0.0.1:8081/manage/shutdown
六、多环境切换
在日常的软件开发中,我们一般都是使用自己的机器开发,测试的时候需要用到测试服务器,上线时使用正式环境的服务器。这三种环境需要的配置信息往往都不一样,比如数据源地址等。当我们切换环境运行项目时,一般都需要手动的修改配置文件,非常不方便且容易出错。为了解决上述问题,Spring Boot 提供多环境配置的机制,让开发者很容易的根据需求而切换不同的配置环境。
在 Spring Boot 中,将所需要的配置信息放在 application.properties 中,在 src/main/resources 目录下创建三个配置文件,分别对应开发、测试和生产三个环境:
application-dev.properties:用于开发环境
application-test.properties:用于测试环境
application-prod.properties:用于生产环境
我们可以在这个三个配置文件中设置不同的信息,比如访问应用的端口号,并在 application.properties 配置激活信息。
spring.profiles.active=dev
表示激活 application-dev.properties 文件配置, Spring Boot 会加载使用 application.properties 和 application-dev.properties 配置文件的信息。同理可将 spring.profiles.active 的值修改成 test 或 prod 达到切换到测试环境或生产环境的目的。
七、配置日志
Spring Boot 官方推荐使用 logback,其次也可以使用 log4j2 来配置日志。
7.1 配置 logback
Spring Boot 默认会加载 classpath:logback-spring.xml 或者 classpath:logback-spring.groovy。如需要自定义文件名称,在 application.properties 中配置 logging.config 选项即可。在 src/main/resources 下创建 logback-spring.xml 文件,内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 文件输出格式 -->
<property name="PATTERN" value="%-12(%d{yyyy-MM-dd HH:mm:ss.SSS}) |-%-5level [%thread] %c [%L] -| %msg%n" />
<!-- test文件路径 -->
<property name="TEST_FILE_PATH" value="F:/springlog/test.log" />
<!-- pro文件路径 -->
<property name="PRO_FILE_PATH" value="/opt/test/log" />
<!-- 开发环境 -->
<springProfile name="dev">
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${PATTERN}</pattern>
</encoder>
</appender>
<logger name="com.light.springboot" level="debug" />
<root level="info">
<appender-ref ref="CONSOLE" />
</root>
</springProfile>
<!-- 测试环境 -->
<springProfile name="test">
<!-- 每天产生一个文件 -->
<appender name="TEST-FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 文件路径 -->
<file>${TEST_FILE_PATH}</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 文件名称 -->
<fileNamePattern>${TEST_FILE_PATH}/info.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 文件最大保存历史数量 -->
<MaxHistory>100</MaxHistory>
</rollingPolicy>
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>${PATTERN}</pattern>
</layout>
</appender>
<root level="info">
<appender-ref ref="TEST-FILE" />
</root>
</springProfile>
<!-- 生产环境 -->
<springProfile name="prod">
<appender name="PROD_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${PRO_FILE_PATH}</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${PRO_FILE_PATH}/warn.%d{yyyy-MM-dd}.log</fileNamePattern>
<MaxHistory>100</MaxHistory>
</rollingPolicy>
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>${PATTERN}</pattern>
</layout>
</appender>
<root level="warn">
<appender-ref ref="PROD_FILE" />
</root>
</springProfile>
</configuration>
其中,springProfile 标签的 name 属性对应 application.properties 中的 spring.profiles.active 的配置。即 spring.profiles.active 的值可以看作是日志配置文件中对应的 springProfile 是否生效的开关。
7.2 配置 log4j2
要使用 log4j2,需要添加 spring-boot-starter-log4j2 依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
spring boot 默认会加载 classpath:log4j2.xml 或者 classpath:log4j2-spring.xml。如需要自定义文件名称,在 application.properties 中配置 logging.config 选项即可。log4j2.xml 文件内容如下:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<properties>
<!-- 文件输出格式 -->
<property name="PATTERN">%d{yyyy-MM-dd HH:mm:ss.SSS} |-%-5level [%thread] %c [%L] -| %msg%n</property>
</properties>
<appenders>
<Console name="CONSOLE" target="system_out">
<PatternLayout pattern="${PATTERN}" />
</Console>
</appenders>
<loggers>
<logger name="com.light.springboot" level="debug" />
<root level="info">
<appenderref ref="CONSOLE" />
</root>
</loggers>
</configuration>
log4j2 不能像 logback 那样在一个文件中设置多个环境的配置数据,只能命名 3 个不同名的日志文件,分别在 application-dev,application-test 和 application-prod 中配置 logging.config 选项。
除了在日志配置文件中设置参数之外,还可以在 application-*.properties 中设置,日志相关的配置:
logging.config # 日志配置文件路径,如 classpath:logback-spring.xml
logging.exception-conversion-word # 记录异常时使用的转换词
logging.file # 记录日志的文件名称,如:test.log
logging.level.* # 日志映射,如:logging.level.root=WARN,logging.level.org.springframework.web=DEBUG
logging.path # 记录日志的文件路径,如:d:/
logging.pattern.console # 向控制台输出的日志格式,只支持默认的 logback 设置。
logging.pattern.file # 向记录日志文件输出的日志格式,只支持默认的 logback 设置。
logging.pattern.level # 用于呈现日志级别的格式,只支持默认的 logback 设置。
logging.register-shutdown-hook # 初始化时为日志系统注册一个关闭钩子
八、应用打包
常用应用打包的形式有两种:jar 和 war。
8.1 打包成可执行的 jar 包
默认情况下,通过 maven 执行 package 命令后,会生成 jar 包,且该 jar 包会内置了 tomcat 容器,因此我们可以通过 java -jar 就可以运行项目。
8.2 打包成部署的 war 包
Spring Boot 生成的 war 包,需要启动类继承自SpringBootServletInitializer方可正常部署至常规tomcat下,其主要能够起到 web.xml 的作用。让 SpringbootApplication 类继承 SpringBootServletInitializer 并重写 configure 方法,如下:
package com.light.springboot;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.support.SpringBootServletInitializer;
/**
* 该注解指定项目为springboot,由此类当作程序入口,自动装配web依赖的环境
* @author Administrator
*
*/
@SpringBootApplication
public class SpringbootApplication extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(SpringbootApplication.class);
}
public static void main(String[] args) {
SpringApplication.run(SpringbootApplication.class, args);
}
}
修改 pom.xml 文件,将 jar 改成 war,如下:
<packaging>war</packaging>
打包成功后,将 war 包部署到 tomcat 容器中运行即可。