-- 附答案,不带答案的面试题都是耍流氓
1. springboot中的核心配置文件是什么?如何配置端口号
application.yml 或 application.properies
server.port=8080 #application.properies配置方法
server:
port: 8080 #application.yml配置方法
2. 什么是springboot, 有哪些优点
用来简化spring应用的初始搭建及开发过程,使用特定的方式来进行配置(properties,yml)创建独立的spring引用程序,main方法运行,嵌入tomcat,无需部署war, 自动配置spring,添加对应功能的starter自动化配置
优点:
快速创建独立运行的spring项目与主流框架集成;
使用嵌入式的servlet容器,应用无需打成war包
starter自动依赖与版本控制
大量的自动配置,简化开发,也可以修改默认值
准生产韩静的运行应用监控
与云计算的天然集成
缺点:
组件过于繁重,臃肿
3. springboot的核心注解有哪些
springboot的核心注解是 @SpringBootApplication, 他主要包含了一下几个注解:
@SpringBootConfiguration:组合了 @Configuration 注解,实现配置文件的功能。
@EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选项,如关闭数据源自动配置功能: @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })。
@ComponentScan:Spring组件扫描
4. springboot需要独立的容器运行么
不需要, springboot中默认依赖了tomcat, 也可以选择使用jetty, springboot只需要打成jar包,通过main方法就可以直接运行
5. springboot自动配置的原理是什么
从main函数说起,一切的开始要从SpringbootApplication注解说起。每个main方法上都有SpringBootApplication注解
代码语言:javascript复制@SpringBootApplication
public class MyBootApplication {
public static void main(String[] args) {
SpringApplication.run(MyBootApplication.class);
}
}
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan
public @interface SpringBootApplication {
}
其中最重要的就是EnableAutoConfiguration注解,开启自动配置。
代码语言:javascript复制@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
通过Import注解导入AutoConfigurationImportSelector。在这个类中加载/META-INF/spring.factories文件的信息,然后筛选出以EnableAutoConfiguration为key的数据,加载到IOC容器中,实现自动配置功能。
代码语言:javascript复制
代码语言:javascript复制@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
}
从表面看就是自动配置包,主要使用了Import注解,导入了Registrar类。这里Registrar类的registerBeanDefinitions方法导包,也就是导入当前main函数所在路径的包地址
怎么自动装配其他N个类
Import({AutoConfigurationImportSelector.class})该注解给当前配置类导入另外N个自动配置类。
这里既然导入N个自动配置类,那么都导入哪些类呢?
代码语言:javascript复制//AutoConfigurationImportSelector实现DeferredImportSelector接口,而DeferredImportSelector接口又继承了ImportSelector
public interface ImportSelector {
String[] selectImports(AnnotationMetadata var1);
}
AutoConfigurationImportSelector通过实现接口ImportSelector的selectImports方法返回需要导入的组件,selectImports方法返回一个全类名字符串数组。
代码语言:javascript复制//AutoConfigurationImportSelector.java
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),getBeanClassLoader());
return configurations;
}
这里又开始调用SpringFactoriesLoader.loadFactoryNames。 SpringFactoriesLoader.loadFactoryNames方法中关键的三步: (1)从当前项目的类路径中获取所有 META-INF/spring.factories 这个文件下的信息. (2)将上面获取到的信息封装成一个 Map 返回,EnableAutoConfiguration为key。 (3)从返回的Map中通过刚才传入的 EnableAutoConfiguration.class参数,获取该 key 下的所有值。
代码语言:javascript复制public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
if (result != null) {
return result;
} else {
try {
Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
LinkedMultiValueMap result = new LinkedMultiValueMap();
while(urls.hasMoreElements()) {
URL url = (URL)urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
Iterator var6 = properties.entrySet().iterator();
while(var6.hasNext()) {
Entry<?, ?> entry = (Entry)var6.next();
String factoryClassName = ((String)entry.getKey()).trim();
String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
int var10 = var9.length;
for(int var11 = 0; var11 < var10; var11) {
String factoryName = var9[var11];
result.add(factoryClassName, factoryName.trim());
}
}
}
cache.put(classLoader, result);
return result;
} catch (IOException var13) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
}
}
}
自动配置都有哪些内容呢?
代码语言:javascript复制# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,
...其他省略
XXXAutoConfiguration和XXProperties
在spring.factories文件中看到的都是自动配置类,那么自动配置用到的属性值在那里呢?我们拿出redis为例
代码语言:javascript复制Configuration
@ConditionalOnClass(RedisOperations.class) //判断当前项目有没有这个类RedisOperations.class
@EnableConfigurationProperties(RedisProperties.class) //启用配置属性,这里看到了熟悉的XXXProperties
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class }) //导入这两个类
public class RedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
//这里则保存redis初始化时的属性
@ConfigurationProperties(prefix = "spring.redis")
public class RedisProperties {
private int database = 0;
private String url;
private String host = "localhost";
private String password;
private int port = 6379;
private boolean ssl;
}
6. @RequestMapping 和 GetMapping的区别是什么
- @RequestMapping可以指定GET、POST请求方式
- @GetMapping等价于@RequestMapping的GET请求方式 @GetMapping = @RequestMapping(method = RequestMethod.GET) 除此之外还有PostMapping, PutMapping和DeleteMapping
7. 介绍一下ioc和aop
见下文。
8. spring bean的生命周期
1. 实例化Bean: Ioc容器通过获取BeanDefinition对象中的信息进行实例化,实例化对象被包装在BeanWrapper对象中
2. 设置对象属性(DI):通过BeanWrapper提供的设置属性的接口完成属性依赖注入;
3. 注入Aware接口(BeanFactoryAware, 可以用这个方式来获取其它 Bean,ApplicationContextAware):Spring会检测该对象是否实现了xxxAware接口,并将相关的xxxAware实例注入给bean
4. BeanPostProcessor:自定义的处理(分前置处理和后置处理)
5. InitializingBean和init-method:执行我们自己定义的初始化方法
6. 使用
7. destroy:bean的销毁
9. spring bean如何解决循环依赖问题
这里首先需要说明的一点是,Spring实例化bean是通过ApplicationContext.getBean()方法来进行的。如果要获取的对象依赖了另一个对象,那么其首先会创建当前对象,然后通过递归的调用ApplicationContext.getBean()方法来获取所依赖的对象,最后将获取到的对象注入到当前对象中。
这里我们以上面的首先初始化A对象实例为例进行讲解。首先Spring尝试通过ApplicationContext.getBean()方法获取A对象的实例,由于Spring容器中还没有A对象实例,因而其会创建一个A对象,然后发现其依赖了B对象,因而会尝试递归的通过ApplicationContext.getBean()方法获取B对象的实例,但是Spring容器中此时也没有B对象的实例,因而其还是会先创建一个B对象的实例。读者需要注意这个时间点,此时A对象和B对象都已经创建了,并且保存在Spring容器中了,只不过A对象的属性b和B对象的属性a都还没有设置进去。
在前面Spring创建B对象之后,Spring发现B对象依赖了属性A,因而此时还是会尝试递归的调用ApplicationContext.getBean()方法获取A对象的实例,因为Spring中已经有一个A对象的实例,虽然只是半成品(其属性b还未初始化),但其也还是目标bean,因而会将该A对象的实例返回。此时,B对象的属性a就设置进去了,然后还是ApplicationContext.getBean()方法递归的返回,也就是将B对象的实例返回,此时就会将该实例设置到A对象的属性b中。这个时候,注意A对象的属性b和B对象的属性a都已经设置了目标对象的实例了。
读者朋友可能会比较疑惑的是,前面在为对象B设置属性a的时候,这个A类型属性还是个半成品。但是需要注意的是,这个A是一个引用,其本质上还是最开始就实例化的A对象。而在上面这个递归过程的最后,Spring将获取到的B对象实例设置到了A对象的属性b中了,这里的A对象其实和前面设置到实例B中的半成品A对象是同一个对象,其引用地址是同一个,这里为A对象的b属性设置了值,其实也就是为那个半成品的a属性设置了值
10. 什么是spring boot starter?
spring启动器是一套方便的依赖,没有描述符,它可以放在自己的程序中。你可以放在自己的程序中,你可以一站式的获取你所需要的spring和相关技术,而不需要依赖描述符的通过实例代码搜索和复制粘贴的负载。
11. com.mysql.jdbc.Driver 和 com.mysql.cj.jdbc.Driver 的区别
com.mysql.jdbc.Driver 是 mysql-connector-java 5 中的。 com.mysql.cj.jdbc.Driver 是 mysql-connector-java 6 中的。
12. 什么是springboot actuator:
springboot actuator 是springboot的监视器,可以帮助我们在访问生产环境中正在运行的应用程序的当前状态,在生产环境中必须检查和监视几个指标。甚至一些外部应用程序也可能使用这些服务来出发对相关人员的报警功能。
13. springboot中如何实现aop
面向切面编程(AOP):允许程序员模块化横向业务逻辑,或定义核心部分的功能,例如日志管理和事务管理。
切面(Aspect) :AOP的核心就是切面,它将多个类的通用行为封装为可重用的模块。该模块含有一组API提供 cross-cutting功能。例如,日志模块称为日志的AOP切面。根据需求的不同,一个应用程序可以有若干切面。在Spring AOP中,切面通过带有@Aspect注解的类实现。
通知(Advice):通知表示在方法执行前后需要执行的动作。实际上它是Spring AOP框架在程序执行过程中触发的一些代码。Spring切面可以执行一下五种类型的通知:
- before(前置通知):在一个方法之前执行的通知。
- after(最终通知):当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。
- after-returning(后置通知):在某连接点正常完成后执行的通知。
- after-throwing(异常通知):在方法抛出异常退出时执行的通知。
- around(环绕通知):在方法调用前后触发的通知。
切入点(Pointcut):切入点是一个或一组连接点,通知将在这些位置执行。可以通过表达式或匹配的方式指明切入点。
引入:引入允许我们在已有的类上添加新的方法或属性。
目标对象:被一个或者多个切面所通知的对象。它通常是一个代理对象。也被称做被通知(advised)对象。
代理:代理是将通知应用到目标对象后创建的对象。从客户端的角度看,代理对象和目标对象是一样的。有以下几种代理:
- BeanNameAutoProxyCreator:bean名称自动代理创建器
- DefaultAdvisorAutoProxyCreator:默认通知者自动代理创建器
- Metadata autoproxying:元数据自动代理
织入:将切面和其他应用类型或对象连接起来创建一个通知对象的过程。织入可以在编译、加载或运行时完成。
14. springboot提供了哪些starter?
Spring-boot-starter-web
Spring-boot-starter-test
Spring-boot-starter-jdbc
Spring-boot-starter-dat-jpa
Spring-boot-starter-rest
Spring-boot-starter-actuator
15. 开启springboot特性有几种方式:
pom文件中
- 继承 spring-boot-starter-parent
- 在dependency-management 中引入 spring-boot-dependencies
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.1.RELEASE</version>
</parent>
或者
代码语言:javascript复制<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring.boot.version}</version>
<scope>import</scope>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>
16. 如何在springboot中配置多套配置文件
application-dev.yml application-test.yml application-prod.yml
通过 : spring.profiles.active= test 来指定
17. 如何在springboot中实现拦截器:
写一个类实现 HandlerInterceptor接口, 重新preHandle方法,里边为 拦截器的逻辑
代码语言:javascript复制@Slf4j
@Component
public class TokenInterceptor implements HandlerInterceptor {
@Resource
private UserCenterClient userCenterClient;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 如果资源来源是 app ,直接放行
String requestSourceType = request.getHeader("requestSourceType");
String token = request.getHeader("token");
log.debug("token拦截器开始执行, 访问url{}, token:{}, requestSourceType{}", request.getRequestURI(), token, requestSourceType);
Result<Boolean> result = userCenterClient.verifyToken(token, requestSourceType);
if (result.getCode() != Result.success().getCode()) {
if (result.getData() == null || !result.getData()) {
log.info("token {} ,校验失败,请重新登录");
throw new BizException(result.getCode(), result.getMsg());
}
}
return true;
}
}
然后在一个配置类中添加刚才这个类
代码语言:javascript复制@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Bean
public TokenInterceptor tokenInterceptor() {
return new TokenInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(tokenInterceptor()).addPathPatterns("/**");
}
}
18. spring中实现事务有几种方式:
编程式事务
声明式事务
19. spring事务的传播属性
- PROPAGATION_REQUIRED: 支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
- PROPAGATION_SUPPORTS: 支持当前事务,如果当前没有事务,就以非事务方式执行。
- PROPAGATION_MANDATORY: 支持当前事务,如果当前没有事务,就抛出异常。
- PROPAGATION_REQUIRES_NEW: 新建事务,如果当前存在事务,把当前事务挂起。
- PROPAGATION_NOT_SUPPORTED: 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
- PROPAGATION_NEVER: 以非事务方式执行,如果当前存在事务,则抛出异常。
- PROPAGATION_NESTED: 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。
20. Resource是如何被查找加载的?
Resource 接口是 Spring 资源访问策略的抽象,它本身并不提供任何资源访问实现,具体的资源访问由该接口的实现类完成——每个实现类代表一种资源访问策略。 Spring 为 Resource 接口提供了如下实现类:
- UrlResource:访问网络资源的实现类。
- ClassPathResource:访问类加载路径里资源的实现类。
- FileSystemResource:访问文件系统里资源的实现类。
- ServletContextResource:访问相对于 ServletContext 路径里的资源的实现类:
- InputStreamResource:访问输入流资源的实现类。
- ByteArrayResource:访问字节数组资源的实现类。 这些 Resource 实现类,针对不同的的底层资源,提供了相应的资源访问逻辑,并提供便捷的包装,以利于客户端程序的资源访问
21. AOP实现原理
实现AOP的技术,主要分为两大类:
- 一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;
- 二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码。
Spring AOP 的实现原理其实很简单:AOP 框架负责动态地生成 AOP 代理类,这个代理类的方法则由 Advice和回调目标对象的方法所组成, 并将该对象可作为目标对象使用。AOP 代理包含了目标对象的全部方法,但AOP代理中的方法与目标对象的方法存在差异,AOP方法在特定切入点添加了增强处理,并回调了目标对象的方法。
Spring AOP使用动态代理技术在运行期织入增强代码。使用两种代理机制:基于JDK的动态代理(JDK本身只提供接口的代理)和基于CGlib的动态代理。
- (1) JDK的动态代理 JDK的动态代理主要涉及java.lang.reflect包中的两个类:Proxy和InvocationHandler。其中InvocationHandler只是一个接口,可以通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态的将横切逻辑与业务逻辑织在一起。而Proxy利用InvocationHandler动态创建一个符合某一接口的实例,生成目标类的代理对象。 其代理对象必须是某个接口的实现, 它是通过在运行期间创建一个接口的实现类来完成对目标对象的代理.只能实现接口的类生成代理,而不能针对类
- (2)CGLib CGLib采用底层的字节码技术,为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类的调用方法,并顺势织入横切逻辑.它运行期间生成的代理对象是目标类的扩展子类.所以无法通知final、private的方法,因为它们不能被覆写.是针对类实现代理,主要是为指定的类生成一个子类,覆盖其中方法. 在spring中默认情况下使用JDK动态代理实现AOP,如果proxy-target-class设置为true或者使用了优化策略那么会使用CGLIB来创建动态代理.Spring AOP在这两种方式的实现上基本一样.以JDK代理为例,会使用JdkDynamicAopProxy来创建代理,在invoke()方法首先需要织入到当前类的增强器封装到拦截器链中,然后递归的调用这些拦截器完成功能的织入.最终返回代理对象.
22. spring事务底层原理
- 划分处理单元——IoC
由于spring解决的问题是对单个数据库进行局部事务处理的,具体的实现首先用spring中的IoC划分了事务处理单元。并且将对事务的各种配置放到了ioc容器中(设置事务管理器,设置事务的传播特性及隔离机制)。
- AOP拦截需要进行事务处理的类
Spring事务处理模块是通过AOP功能来实现声明式事务处理的,具体操作(比如事务实行的配置和读取,事务对象的抽象),用TransactionProxyFactoryBean接口来使用AOP功能,生成proxy代理对象,通过TransactionInterceptor完成对代理方法的拦截,将事务处理的功能编织到拦截的方法中。读取ioc容器事务配置属性,转化为spring事务处理需要的内部数据结构(TransactionAttributeSourceAdvisor),转化为TransactionAttribute表示的数据对象。
- 对事务处理实现(事务的生成、提交、回滚、挂起)
spring委托给具体的事务处理器实现。实现了一个抽象和适配。适配的具体事务处理器:DataSource数据源支持、hibernate数据源事务处理支持、JDO数据源事务处理支持,JPA、JTA数据源事务处理支持。这些支持都是通过设计PlatformTransactionManager、AbstractPlatforTransaction一系列事务处理的支持。 为常用数据源支持提供了一系列的TransactionManager。
- 结合
PlatformTransactionManager实现了TransactionInterception接口,让其与TransactionProxyFactoryBean结合起来,形成一个Spring声明式事务处理的设计体系
23. Springboot 配置文件的加载顺序如何
1) properties 2) yaml 3) 系统环境变量 4) 命令行参数
24. 如何解决跨域:
实现 WebMvcConfigurer 接口
代码语言:javascript复制/** 解决跨域问题 **/
public void addCorsMappings(CorsRegistry registry) ;
/** 添加拦截器 **/
void addInterceptors(InterceptorRegistry registry);
/** 这里配置视图解析器 **/
void configureViewResolvers(ViewResolverRegistry registry);
/** 配置内容裁决的一些选项 **/
void configureContentNegotiation(ContentNegotiationConfigurer configurer);
/** 视图跳转控制器 **/
void addViewControllers(ViewControllerRegistry registry);
/** 静态资源处理 **/
void addResourceHandlers(ResourceHandlerRegistry registry);
/** 默认静态资源处理器 **/
void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer);
SpringBoot 中怎么禁用某些自动配置特性?
exclude()
25. bootstrap.properties 和 application.properties 有何区别 ?
spring boot 核心的两个配置文件:
bootstrap (. yml 或者 . properties):boostrap 由父 ApplicationContext 加载的,比 applicaton 优先加载,配置在应用程序上下文的引导阶段生效。一般来说我们在 Spring Cloud Config 或者 Nacos 中会用到它。且 boostrap 里面的属性不能被覆盖; application (. yml 或者 . properties): 由ApplicatonContext 加载,用于 spring boot 项目的自动化配置。
26. 如何全局处理Controller异常
可以使用@ControllerAdvice 和 @ExceptionHadnler
代码语言:javascript复制@ControllerAdvice
@ResponseBody
public class GlobalExceptionHandler {
/**
* 请求参数异常处理
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<?> handleMethodArgumentNotValidException(MethodArgumentNotValidException ex, HttpServletRequest request) {
......
}
}
@ControllerAdvice: 会对所有的Controller做增强,可以做全局异常处理,全局数据绑定和全局数据预处理
ExceptionHandler用于指定对哪类异常做处理。