Java岗大厂面试百日冲刺【Day9】 —— SpringBoot1

转载自: Java岗大厂面试百日冲刺【Day9】 —— SpringBoot1

本文已获得原作者 _陈哈哈 授权并经过重新整理规划后发布。

本栏目Java开发岗高频面试题主要出自以下各技术栈:Java基础知识、集合容器、并发编程、JVM、Spring全家桶、MyBatis等ORMapping框架、MySQL数据库、Redis缓存、RabbitMQ消息队列、Linux操作技巧等。

面试题1:简述一下Springboot相对SSM做了哪些提升?

首先,SpringBoot是采用“约定大于配置”(Convention over Configuration)的理念,让你的项目快速运行起来,简化了大量的XML配置。使用SpringBoot很容易创建一个独立运行(运行jar,内嵌Servlet容器)、准生产级别的基于Spring框架的项目,使用SpringBoot你可以不用或者只用很少的Spring配置即可。

以前,你不仅要实现业务需求,还要自己配置SpringMVC、Spring、Mybatis相关配置信息。而Spring Boot做到自动配置,让你腾出精力和时间更多的去关心核心业务。

某乎上有个很形象的比较:

SSM相当于一辆手动挡汽车,SpringBoot相当于把汽车变成自动挡,然后还加装了无钥匙进入、自动启停等功能,让你开车更省心。但是车的主体功能不变,你还是要用到SSM。

SpringBoot的优点?

  • Spring由于其繁琐的配置,各种XML、Annotation配置,让人眼花缭乱,而且如果出错了也很难找出原因,而SpringBoot友好的内置了很多通用配置信息。
  • SpringBoot帮助开发者快速启动一个Web容器
  • SpringBoot提供了更多的组合式注解,简化开发过程;
  • SpringBoot可以"零配置"整合很多第三方工具;
  • SpringBoot继承了原有Spring框架的优秀基因;
  • Springboot相比SSM有以下几个优势:

1、Spring 项目独立运行

SpringBoot 可以以jar 包的形式独立运行,运行一个SpringBoot 项目只需通过java –jar xxxxx.jar 来运行。

2、内嵌Servlet 容器

SpringBoot 可选择内嵌Tomcat、Jetty 或者Undertow ,这样我们无须以war 包形式部署项目。

3、简化Maven 配置(starter)

Spring 提供了一系列的starter pom 来简化Maven 的依赖加载,例如,当你使用了spring-bootstarter-web 时,会自动加入依赖包。

4、自动配置Spring

SpringBoot 会根据在类路径中的jar 包、类,为jar 包里的类自动配置Bean,这样会极大地减少我们要使用的配置。当然,SpringBoot 只是考虑了大多数的开发场景,并不是所有的场景,若在实际开发中我们需要自动配置Bean,而SpringBoot 没有提供支持,则可以自定义自动配置。

5、准生产的应用监控

SpringBoot 提供基于http、ssh、telnet 对运行时的项目进行监控。

6、无代码生成和xml 配置

SpringBoot 的神奇的不是借助于代码生成来实现的,而是通过条件注解来实现的,这是Spring4.x 提供的新特性。

Spring 4.x 提倡使用Java 配置和注解配置组合,而SpringBoot 不需要任何xml 配置即可实现Spring 的所有配置

说说你在使用SpringBoot时比较有印象的有哪些注解

1、@SpringBootApplication

这个注解是Spring Boot最核心的注解,用在 Spring Boot的主类上,标识这是一个 Spring Boot 应用,用来开启 Spring Boot 的各项能力。

实际上这个注解是@Configuration,@EnableAutoConfiguration,@ComponentScan三个注解的组合。由于这些注解一般都是一起使用,所以Spring Boot提供了一个统一的注解@SpringBootApplication。

@SpringBootApplication(exclude = {
        MongoAutoConfiguration.class,
        MongoDataAutoConfiguration.class,
        DataSourceAutoConfiguration.class,
        ValidationAutoConfiguration.class,
        MybatisAutoConfiguration.class,
        MailSenderAutoConfiguration.class,
})
public class API {
    public static void main(String[] args) {
        SpringApplication.run(API.class, args);
    }
}

2、@EnableAutoConfiguration

允许 Spring Boot 自动配置注解,开启这个注解之后,Spring Boot 就能根据当前类路径下的包或者类来配置 Spring Bean。

如:当前类路径下有 Mybatis 这个 JAR 包,MybatisAutoConfiguration 注解就能根据相关参数来配置 Mybatis 的各个 Spring Bean。

@EnableAutoConfiguration实现的关键在于引入了AutoConfigurationImportSelector,其核心逻辑为selectImports方法,逻辑大致如下:

  1. 从配置文件META-INF/spring.factories加载所有可能用到的自动配置类;
  2. 去重,并将exclude和excludeName属性携带的类排除;
  3. 过滤,将满足条件(@Conditional)的自动配置类返回;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage//导入AutoConfigurationImportSelector的子类@Import({EnableAutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default {};

    String[] excludeName() default {};
}

3、@SpringBootConfiguration

这个注解就是 @Configuration 注解的变体,只是用来修饰是 Spring Boot 配置而已,或者可利于 Spring Boot 后续的扩展。

4、@ConditionalOnMissingBean

组合@Conditional注解,和@ConditionalOnBean注解相反,仅仅在当前上下文中不存在A对象时,才会实例化一个Bean。

@Bean
  //仅当当前环境上下文缺失RocketMQProducer对象时,才允许创建RocketMQProducer Bean对象
  @ConditionalOnMissingBean(RocketMQProducer.class)
  public RocketMQProducer mqProducer() {
      return new RocketMQProducer();
  }

5、@RestController

用于标注控制层组件(如struts中的action),组合注解,包含@Controller和@ResponseBody

下面还有从Spring延续下来的一些注解:

6、@ResponseBody

表示该方法的返回结果直接写入HTTP response body中

一般在异步获取数据时使用,在使用@RequestMapping后,返回值通常解析为跳转路径,加上@responsebody后返回结果不会被解析

为跳转路径,而是直接写入HTTP response body中。比如异步获取json数据,加上@responsebody后,会直接返回json数据。

7、@Repository

用于标注数据访问组件,即DAO组件。

8、@Service

用于标注业务层组件。

9、@Component

泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注。

10、@ComponentScan

组件扫描。相当于,如果扫描到有@Component @Controller @Service等这些注解的类,则把这些类注册为bean。

11、@Configuration

指出该类是 Bean 配置的信息源,相当于XML中的,一般加在主类上。

12、@Bean

相当于XML中的,放在方法的上面,而不是类,意思是产生一个bean,并交给spring管理。

13、@AutoWired

byType方式。把配置好的Bean拿来用,完成属性、方法的组装,它可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作。
当加上(required=false)时,就算找不到bean也不报错。

14、@RequestMapping

RequestMapping是一个用来处理请求地址映射的注解,可用于类或方法上。用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径。

@RequestMapping注解的属性有哪些?分别都是干什么用的?

RequestMapping接口源码解析

RequestMapping接口的源码如下,里面定义了八个属性(Spring4.3.8)。

注:SpringMVC在4.1版本对RequestMapping属性做了相应调整,去掉了path属性。

@Target({ElementType.METHOD, ElementType.TYPE}) // 可以在方法和类的声明中使用
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {

    String name() default "";// 指定映射的名称

    @AliasFor("path")
    String[] value() default {}; // 指定请求路径的地址

    @AliasFor("value")
    String[] path() default {}; // 指定请求路径的地址

	// 指定请求的方式,是一个RequsetMethod数组,可以配置多个方法
    RequestMethod[] method() default {};
	
	// 指定参数的类型
    String[] params() default {};
	
	// 指定请求头内容
    String[] headers() default {};
	
	// 指定数据请求的格式
    String[] consumes() default {};
	
	// 指定返回的内容类型
    String[] produces() default {};
}
  • 如上源码所示,在@Target中有两个属性,分别为 ElementType.METHOD 和 ElementType.TYPE ,也就是说@RequestMapping 可以在方法和类的声明中使用
  • 可以看到注解中的属性除了 name() 返回的字符串,其它的方法均返回数组,也就是可以定义多个属性值,例如 value() 和 path() 都可以同时定义多个字符串值来接收多个URL请求

RequestMapping属性介绍

1、name

此处name属性,相当于方法的注释,使方法更易理解

@RequestMapping(value = "login",name = "用户登录")
@ResponseBody
public String login() {
	return "success";
}

官方文档说:它能让你非常方便的在JSP页面上使用它,形如这样子可以直接通过静态页面调用:

<%@ taglib uri="http://www.springframework.org/tags" prefix="s" %>
...
<a href="${s:mvcUrl('PAC#getAddress').arg(0,'US').buildAndExpand('123')}">Get Address</a>

2、value

@Controller
@RequestMapping("user")   //此处如果不省略,则为@RequestMapping(value="user")
public class UserController {
 
    @RequestMapping("login")
    @ResponseBody
    public String login() {
		return "success";
	}
}

指定请求的实际地址,指定的地址可以是URI 模板模式(Template Pattern);

由于value属性是@RequestMapping注释的默认属性,因此如果只有唯一的属性,则可以省略该属性名,如果有超过一个属性,则必须写上value属性名称。即如下两个标注含义一样

@RequestMapping(value="login")
@RequestMapping("login")

value属性支持通配符匹配:

@RequestMapping(value="login/*");

即:http://localhost:8080/login/1 或者 http://localhost:8080/login/hahaha 都能够正常访问该接口

此处通过URL:http://localhost:8080/user/login 进行访问,将由login()方法进行处理

3、path

与value同义,path(value)(path 和 value 互相引用,参见RequestMapping接口源码)
path属性,和1中的value属性使用一致,两者都是用来作为映射使用的。

@RequestMapping(value=“login”)、@RequestMapping(path=“login”),这两种情况都能对login()方法进行访问

path属性支持通配符匹配:

@RequestMapping(path="login/*");

即:http://localhost:8080/login/1 或者 http://localhost:8080/login/abc 都能够正常访问

4、method

指定请求类型, 如GET、POST、PUT、DELETE等;

@RequestMapping(value = "login",method = {RequestMethod.GET,RequestMethod.POST})

说明该方法可以同时支持GET和POST请求。

如果没有method属性,则说明该方法支持全部的HTTP请求。

5、params

该属性指定,请求中必须包含params属性规定的参数时,才能执行该请求

@RequestMapping(value = "login",params = "flag")
@ResponseBody
public String login() {
	return "success";
}

以上方法,说明请求中必须包含flag参数才能执行该请求,flag参数值不做要求

  • http://localhost:8080/login?flag=xxx // 正常访问
  • http://localhost:8080/login // 无法访问
@RequestMapping(value = "login",params = "flag=true")
@ResponseBody
public String login() {
	return "success";
}

以上方法,说明请求中必须包含flag参数,而且参数值必须为true才能执行该请求

  • http://localhost:8080/login?flag=true // 正常访问
  • http://localhost:8080/login?flag=false // 无法访问
  • http://localhost:8080/login // 无法访问

6、headers

  • 用于HTTP协义交互的信息被称为HTTP报文,客户端发送的HTTP报文被称为请求报文,服务器发回给客户端的HTTP报文称为响应报文,报文由报文头部和报文体组成。
  • 请求头部(Request Headers):请求头包含许多有关客户端环境和请求正文的信息,例如浏览器支持的语言、请求的服务器地址、客户端的操作系统等。
  • 响应头部(Rsponse Headers):响应头也包含许多有用的信息,包括服务器类型、日期、响应内容的类型及编码,响应内容的长度等等。
  • 该属性指定,请求中必须包含某些指定的header值,才能够让该方法处理请求

如Chrome浏览器:

可以通过点击F12(进入开发者模式)---->Network---->Name中点击网页---->右侧查看Headers即可,下边是我电脑中的一个请求头部示例:

Request Headers
    Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
    Accept-Encoding:gzip, deflate, sdch
    Accept-Language:zh-CN,zh;q=0.8
    Cache-Control:max-age=0
    Connection:keep-alive
    Cookie:JSESSIONID=210075B5E521CWE3CDE938076295A57A
    Host:localhost:8080
    Upgrade-Insecure-Requests:1
    User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.93
// 表示只接收本机发来的请求
@RequestMapping(path = "/login", headers="Referer=http://localhost:8080")
public String login() {
	return "success";
}

以上方法 ,必须满足请求的header中包含了指定的"Referer"请求头和值为"http://localhost:8080"时,才能执行该请求

7、consumes

指定处理请求的提交内容类型(Content-Type),例如:application/json、text/html时,才能够让该方法处理请求

@RequestMapping(value = "login",consumes = "application/json")
@ResponseBody
public String login() {
	return "success";
}

8、produces

指定返回的内容类型,返回的内容类型必须是request请求头(Accept)中所包含的类型

@RequestMapping(value = "login",produces = "application/json")
@ResponseBody
public String login() {
	return "success";
}

此外,produces属性还可以指定返回值的编码

@RequestMapping(value = "login",produces = "application/json,charset=utf-8")

如上,则指明返回utf-8编码

面试题2:SpringBoot 打成的 jar 包和普通的 jar 包有什么区别

Spring Boot 中默认打包成的 jar 叫做可执行 jar,这种jar包可以通过可以通过命令(java -jar xxx.jar)来运行的,但这种jar包不能被其他项目所依赖,因为它和普通 jar 的结构不同,即使被依赖了也不能直接使用其中的类。

InterviewForJavaByThreeQuestionsADay09-1.webp

普通的jar包,解压后直接就是包名,包里就是我们的代码,而 Spring Boot 打包成的可执行 jar 解压后,在 \BOOT-INF\classes 目录下才是我们的代码,因此无法被直接引用。如果非要引用,可以在 pom.xml 文件中增加配置,将 Spring Boot 项目打包成两个 jar ,一个可执行,一个可引用。

InterviewForJavaByThreeQuestionsADay09-2.webp

如何让SpringBoot打的jar包可依赖?

在pom文件中增加以下配置:

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <executions>
                <execution>
                    <goals>
                        <!--可以把依赖的包都打包到生成的Jar包中 -->
                        <goal>repackage</goal>
                    </goals>
                    <!--可以生成不含依赖包的不可执行Jar包 -->
                    <configuration>
                        <classifier>exec</classifier>
                    </configuration>
                </execution>
            </executions>
        </plugin>
        <plugin>
            <artifactId>maven-compiler-plugin</artifactId>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
            </configuration>
        </plugin>
    </plugins>
</build>

如下图,一次性打包生成两个jar,其中XXX.jar可被其它工程依赖,XXX-exec.jar可执行。

InterviewForJavaByThreeQuestionsADay09-3.webp

面试题3:CORS跨域问题是怎么引起的呢?

Springboot跨域问题,是当前主流web开发人员都绕不开的难题。但我们首先要明确以下几点

  • 跨域只存在于浏览器端,不存在于安卓/ios/Node.js/python/ java等其它环境
  • 跨域请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了。
  • 之所以会跨域,是因为受到了同源策略的限制,同源策略要求源相同才能正常进行通信,即协议、域名、端口号都完全一致。

浏览器出于安全的考虑,使用 XMLHttpRequest对象发起 HTTP请求时必须遵守同源策略,否则就是跨域的HTTP请求,默认情况下是被禁止的。换句话说,浏览器安全的基石是同源策略。

同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。

InterviewForJavaByThreeQuestionsADay09-4.webp

报错内容:

Access to XMLHttpRequest at 'http://192.168.1.1:8080/app/easypoi/importExcelFile' 
from origin 'http://localhost:8080' has been blocked by CORS policy: 
No 'Access-Control-Allow-Origin' header is present on the requested resource.
什么是CORS?

CORS是一个W3C标准,全称是”跨域资源共享”(Cross-origin resource sharing),允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。

它通过服务器增加一个特殊的Header[Access-Control-Allow-Origin]来告诉客户端跨域的限制,如果浏览器支持CORS、并且判断Origin通过的话,就会允许XMLHttpRequest发起跨域请求。

CORS Header

  • Access-Control-Allow-Origin: http://www.xxx.com
  • Access-Control-Max-Age:86400
  • Access-Control-Allow-Methods:GET, POST, OPTIONS, PUT, DELETE
  • Access-Control-Allow-Headers: content-type
  • Access-Control-Allow-Credentials: true

含义解释:

CORS Header属性 解释
Access-Control-Allow-Origin 允许http://www.xxx.com域(自行设置,这里只做示例)发起跨域请求
Access-Control-Max-Age 设置在86400秒不需要再发送预校验请求
Access-Control-Allow-Methods 设置允许跨域请求的方法
Access-Control-Allow-Headers 允许跨域请求包含content-type
Access-Control-Allow-Credentials 设置允许Cookie

处理过Springboot的CORS跨域问题么?怎么解决的?

方法一: 直接采用SpringBoot的注解@CrossOrigin(也支持SpringMVC)

简单粗暴的方式,Controller层在需要跨域的类或者方法上加上该注解即可

/**
 * Created with IDEA
 *
 * @Author Chensj
 * @Date 2020/5/8 10:28
 * @Description xxxx控制层
 * @Version 1.0
 */
@RestController
@CrossOrigin
@RequestMapping("/situation")
public class SituationController extends PublicUtilController {
 
    @Autowired
    private SituationService situationService;
    // log日志信息
    private static Logger LOGGER = Logger.getLogger(SituationController.class);

}

但每个Controller都得加,太麻烦了,怎么办呢,加在Controller公共父类(PublicUtilController)中,所有Controller继承即可。

/**
 * Created with IDEA
 *
 * @Author Chensj
 * @Date 2020/5/6 10:01
 * @Description
 * @Version 1.0
 */
@CrossOrigin
public class PublicUtilController {
 
    /**
     * 公共分页参数整理接口
     *
     * @param currentPage
     * @param pageSize
     * @return
     */
    public PageInfoUtil proccedPageInfo(String currentPage, String pageSize) {
 
        /* 分页 */
        PageInfoUtil pageInfoUtil = new PageInfoUtil();
        try {
            /*
             * 将字符串转换成整数,有风险, 字符串为a,转换不成整数
             */
            pageInfoUtil.setCurrentPage(Integer.valueOf(currentPage));
            pageInfoUtil.setPageSize(Integer.valueOf(pageSize));
        } catch (NumberFormatException e) {
        }
        return pageInfoUtil;
    }
 
}

当然,这里虽然指SpringBoot,SpringMVC也是同样的,但要求在Spring4.2及以上的版本。另外,如果SpringMVC框架版本不方便修改,也可以通过修改tomcat的web.xml配置文件来处理,请参照另一篇博文(nginx同理)

SpringMVC使用@CrossOrigin使用场景要求

* jdk1.8+
* Spring4.2+

方法二: 处理跨域请求的Configuration

增加一个配置类,CrossOriginConfig.java。继承WebMvcConfigurerAdapter或者实现WebMvcConfigurer接口,其他都不用管,项目启动时,会自动读取配置。

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
 
/**
 * AJAX请求跨域
 * @author Mr.W
 * @time 2018-08-13
 */
@Configuration
public class CorsConfig extends WebMvcConfigurerAdapter {
    static final String ORIGINS[] = new String[] { "GET", "POST", "PUT", "DELETE" };
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**").allowedOrigins("*").allowCredentials(true).allowedMethods(ORIGINS).maxAge(3600);
    }

方法三: 采用过滤器(filter)的方式

同方法二加配置类,增加一个CORSFilter 类,并实现Filter接口即可,其他都不用管,接口调用时,会过滤跨域的拦截。

@Component
public class CORSFilter implements Filter {
 
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletResponse res = (HttpServletResponse) response;
        res.addHeader("Access-Control-Allow-Credentials", "true");
        res.addHeader("Access-Control-Allow-Origin", "*");
        res.addHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT");
        res.addHeader("Access-Control-Allow-Headers", "Content-Type,X-CAF-Authorization-Token,sessionToken,X-TOKEN");
        if (((HttpServletRequest) request).getMethod().equals("OPTIONS")) {
            response.getWriter().println("ok");
            return;
        }
        chain.doFilter(request, response);
    }
    @Override
    public void destroy() {
    }
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }
}