SpringBoot面试题

1.谈谈你对SpringBoot的理解

Spring Boot是基于Spring框架的快速开发框架,它通过自动配置、起步依赖和内嵌服务器等特性,简化Spring应用的创建、配置和部署过程。

2.为什么使用springBoot或springBoot的优点

(1)版本锁定: 解决maven依赖版本冲突问题
(2)起步依赖: 解决jar过多问题,比如spring-boot-starter-web为构建Web应用(包括RESTful应用)提供了所需的基本配置
(3)自动配置: 解决配置文件过多问题
(4)内置tomcat: 内嵌了如Tomcat、Jetty等Servlet容器,使得Web应用可以独立运行。

3. springBoot与springCloud 区别

SpringBoot是快速开发的Spring框架,SpringCloud是完整的微服务框架,SpringCloud依赖于SpringBoot。

4.SpringBoot的核心配置文件有哪些,作用是什么

包括application.propertiesapplication.yml,它们用于配置应用的各种属性,如服务器端口、数据库连接信息等。此外,还有bootstrap.propertiesbootstrap.yml,用于一些不能被覆盖的属性和加密或解密的场景。比application要先加载,比如配置中心的连接信息。

  1. application.properties 或 application.yml

    • 作用:这是Spring Boot最核心的配置文件,可以用来设置各种属性值,包括但不限于:

      • 服务器配置(如Tomcat的端口号server.port)
      • 数据源配置(如数据库URL、用户名、密码spring.datasource.*)
      • Spring MVC配置(如视图解析器prefix, suffix)
      • 日志配置
      • 自定义属性
    • 使用场景:适用于几乎所有的配置需求,开发者可以根据需要覆盖默认设置或者添加自定义配置。

  2. bootstrap.properties 或 bootstrap.yml

    • 作用:通常在使用Spring Cloud时会被用到,特别是在你需要从配置中心(如Spring Cloud Config)加载外部化配置的情况下。它会在application.properties之前被加载,因此可以用来配置一些早期初始化需要的属性,比如连接配置中心的信息。

    • 使用场景:主要用于微服务架构中,当你的服务需要从远程配置中心获取配置时。由于它的加载优先级高于application.properties,所以也适合于那些需要尽早确定的配置项。

5.springBoot配置文件有几种类型

.yml和.properties

server.port = 8088 //.properties方式 

server:           //.yml方式
	port:8088

6.什么是热部署?springBoot怎么实现热部署

热部署: 修改代码不需要重启,就可以实现编译并部署到服务器上。
①使用devtools。在配置文件中把devtools.restart.enabled 设置为 true。每次都需要build一下才行。
②idea设置实现热部署。

7.Spring Boot 的核心注解是哪个?它主要由哪几个注解组成的

@SpringBootApplication
包含了三个注解:
①SpringBootConfiguration: 实现了@Configuration注解,实现配置文件的功能。
②@EnableAutoConfiguration: 打开自动配置的功能,也可以关闭某个自动配置的选项, 例
如: java 如关闭数据源自动配置功能: @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })。
③@ComponentScan: Spring组件扫描。

8.springBoot的启动器starter

可以通过启动器去集成其他技术:web、redis、mybatis等。
在pom配置 spring-boot-starter-web,就可以进行web开发。

原理:会加载这些配置类,放进spring容器中,就可以从容器中获取这些类。

9.如何在 Spring Boot 启动的时候运行一些特定的代码?

可以通过实现CommandLineRunner或ApplicationRunner接口来完成。这两个接口都提供了一个run方法,该方法会在Spring Boot应用上下文加载完毕后执行。区别在于ApplicationRunner的run方法接收的是ApplicationArguments对象,而CommandLineRunner的run方法直接接收命令行参数。

10.springBoot的全局异常处理

通过创建一个类并使用@ControllerAdvice注解来定义全局异常处理器。在这个类中,使用@ExceptionHandler注解的方法来捕获特定类型的异常,并返回自定义的响应信息

@ControllerAdvice
public class ExceptionUtil{

    @ExceptionHandler(value =Exception.class)
    @ResponseBody
    public String exceptionHandler(Exception e){
        System.out.println("全局异常捕获:"+e);
        return "全局异常捕获,错误原因:"+e.getMessage();
    }
}

11.spring Boot常用注解

@SpringBootApplication: SpringBootConfiguration配置类、componentScan扫描包、EnableAutoConfiguration导入其他配置类
@RestController: @ResponseBody和@Controller的作用。
@Component,@Service,@Controller,@Repository: 将类注入容器。
@GetMapping、@PostMapping、@PutMapping、@DeleteMapping: 映射请求,只能接收的对应的请求。
@AutoWired: 按照类型匹配注入。
@Qualifier: 和AutoWired联合使用,在按照类型匹配的基础上,在按照名称匹配。
@Resource: 按照名称匹配依赖注入。
@Bean: 用于将方法返回值对象放入容器。
@RequestParam: 获取查询参数。即url?name=这种形式
@RequestBody: 该注解用于获取请求体数据(body),get没有请求体,故而一般用于post请求。@PathVariable: 获取路径参数。即url/{id}这种形式。
@Value: 将外部的值动态注入到 Bean 中。
    @Value(“${}”):可以获取配置文件的值。
    @Value(“#{}”):表示SpEl(Spring Expression Language是Spring表达式语言,可以在运行时查询和操作数据。)表达式通常用来获取 bean 的属性,或者调用 bean 的某个方法。

12.JDK动态代理和CGLib有什么区别?

JDK动态代理和CGLib都是实现AOP(面向切面编程)的代理技术。
JDK Proxy是Java自带的,基于接口进行代理,要求目标类至少实现一个接口;
CGLib不需要目标类实现任何接口,基于ASM(一种字节码操作框架)实现的,通过生成目标类的子类来实现代理,所以要求代理类不能被final修饰。
但在性能上,JDK动态代理通常优于CGLib。

13.SpringAOP默认使用的是JDK动态代理还是CGLib?

Spring AOP默认使用的是JDK动态代理来创建代理对象。但如果目标类没有实现任何接口,则会自动切换到使用CGLib来生成代理。

14.Bean有几种注入方式?

  1. 构造器注入(Constructor Injection)
  2. 设值注入(Setter Injection)
  3. 字段注入(Field Injection)
  4. 方法注入(Method Injection)
    基于守护架构原则@Autowired, @Inject, @Resource 等注解可使用 Lombok 的@RequiredArgsConstructor + final 成员变量的方式,转为构造函数注入
    @Value 使用 @ConfigurationProperties 标记的配置类 替换

区别:

  • 构造器注入
    • 在创建Bean时就进行依赖注入,确保Bean在完全初始化之前就已经注入了所需的依赖。
    • 强制性更强,一旦声明了构造器注入,就必须在创建Bean时提供所有依赖。
    • 使得Bean的依赖关系在构造时就是确定的,有利于保证Bean的不可变性和线程安全。
  • 设值注入
    • 在Bean创建之后,通过setter方法进行依赖注入。
    • 灵活性更高,可以随时修改依赖。
    • 但如果忘记调用setter方法,可能会导致Bean状态不一致。
  • 字段注入
    • 直接在字段上使用@Autowired等注解进行依赖注入。
    • 代码更简洁,但破坏了封装性,依赖于反射,不利于单元测试。
    • 在多线程环境下可能存在线程安全问题。
  • 方法注入
    • 在方法上使用@Autowired等注解进行依赖注入,可以是任意方法,不限于setter方法。
    • 提供了更大的灵活性,可以在注入时执行一些额外的操作。

代码示例:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
// 服务类
@Component
class MyService {
}
// 构造器注入
@Component
class ConstructorInjectionExample {
    private final MyService myService;
    @Autowired
    public ConstructorInjectionExample(MyService myService) {
        this.myService = myService;
    }
}
// 设值注入
@Component
class SetterInjectionExample {
    private MyService myService;
    @Autowired
    public void setMyService(MyService myService) {
        this.myService = myService;
    }
}
// 字段注入
@Component
class FieldInjectionExample {
    @Autowired
    private MyService myService;
}
// 方法注入
@Component
class MethodInjectionExample {
    private MyService myService;
    @Autowired
    public void injectMyService(MyService myService) {
        this.myService = myService;
    }
}

15.说一下Bean的生命周期?

Bean的生命周期是指在Spring(IoC)中从实例化开始,到初始化,最后到销毁的过程。这一过程中,Spring允许通过各种回调方法和配置来定制每个阶段的行为。

  1. 实例化:Spring容器首先根据Bean的定义(如XML配置或注解)创建Bean的实例。这一步骤只是简单地创建对象,并未进行任何属性填充或其他处理。

  2. 属性赋值:在实例化之后,Spring会将Bean定义中指定的所有属性值注入到Bean实例中。这包括了依赖注入的过程。

  3. 设置Bean名称(可选):如果Bean实现了BeanNameAware接口,那么Spring会在属性赋值完成后调用setBeanName(String name)方法,传入Bean的ID或名称。

  4. 设置Bean工厂(可选):对于实现了BeanFactoryAware接口的Bean,Spring会调用setBeanFactory(BeanFactory beanFactory)方法,提供对当前BeanFactory的引用。

  5. 前置处理(可选):如果存在BeanPostProcessor,它的postProcessBeforeInitialization(Object bean, String beanName)方法会被调用。这是一个扩展点,允许在任何初始化逻辑之前执行自定义代码。

  6. 初始化

    • 如果Bean实现了InitializingBean接口,则afterPropertiesSet()方法会被调用。
    • 另外,还可以通过指定init-method属性或者使用@PostConstruct注解来定义一个自定义的初始化方法。
  7. 后置处理(可选):在所有初始化工作完成之后,BeanPostProcessorpostProcessAfterInitialization(Object bean, String beanName)方法会被调用。这是另一个扩展点,用于在Bean完全准备好服务之前做最后的调整。

  8. 使用阶段:此时,Bean已经完全初始化并可以被应用程序使用。

  9. 销毁

    • 当应用上下文关闭时,如果Bean实现了DisposableBean接口,其destroy()方法会被调用。
    • 同样地,可以通过destroy-method属性或@PreDestroy注解来指定自定义的销毁逻辑。

16.Bean是线程安全的吗?实际工作中怎么保证其线程安全?

默认情况下,Bean是非线程安全的。因为默认情况下Bean的作用域是单例模式,那么此时,所有的请求都会共享一个Bean实例,这意味着如果这个Bean实例在多线程下,会被同时修改(成员变量),就可能出现线程安全问题。

  • 单例模式就是所有线程可见共享的,而原型模式则是每次请求都创建一个新的原型对象。
单例Bean一定是非线程安全的吗?

并不是,单例Bean主要是分为以下两种类型:

  • 无状态Bean(线程安全):

    • 定义:无状态的Bean通常是指不包含成员变量或者所有成员变量都是常量的Bean。这意味着每个方法调用都是独立的,不依赖于之前的调用结果。
    • 线程安全性:由于没有可变的状态,无状态Bean在多线程环境中是线程安全的。多个线程可以同时调用这个Bean的方法而不会相互干扰。无状态Bean的方法执行不依赖于共享的状态。
      public class StatelessBean {
          public int add(int a, int b) {
              return a + b;
          }
      }
      
  • 有状态Bean(非线程安全):

    • 定义:有状态的Bean包含了可变的成员变量。多个方法调用可能会依赖于之前的调用结果,因为它们共享相同的状态。
    • 线程安全性:有状态Bean在多线程环境中通常是非线程安全的,因为多个线程可能同时修改Bean的状态,导致数据不一致或竞态条件。需谨慎处理:如果使用有状态Bean,需要确保在多线程环境下进行正确的同步或加锁操作,以防止竞态条件。对于有状态Bean的设计和使用需要更谨慎,确保在多线程环境中能够正确地处理状态。
      public class StatefulBean {
          private int count = 0;
       
          public int increment() {
              return count++;
          }
      }
      
如何保证Bean线程安全
  • 使用ThreadLocal

    • 描述:通过 ThreadLocal,每个线程都拥有自己的变量副本,从而避免了线程安全问题。
    public class MyThreadLocalBean {
        private static final ThreadLocal<Integer> counter = new ThreadLocal<>();
     
        public int increment() {
            counter.set(counter.get() == null ? 1 : counter.get() + 1);
            return counter.get();
        }
    }
    
  • 使用锁机制

    • 描述:使用 synchronized 或 ReentrantLock 等锁机制,确保对有状态Bean的修改操作是原子的,从而保证线程安全。
    public class MySynchronizedBean {
        private int counter = 0;
        private final Object lock = new Object();
     
        public synchronized int increment() {
            return ++counter;
        }
    }
    
  • 设置Bean为原型作用域(Prototype)

    • 描述:将Bean的作用域设置为原型,确保每次请求该Bean都会创建一个新的实例,从而避免不同线程之间的数据冲突。
    @Scope("prototype")
    public class MyPrototypeBean {
        private int counter = 0;
     
        public int increment() {
            return ++counter;
        }
    }
    
  • 使用线程安全容器(Atomic)

    • 描述:使用 Atomic 类,如 AtomicInteger,来保证线程安全。这些类提供了一些原子操作,避免了使用锁的复杂性。
    import java.util.concurrent.atomic.AtomicInteger;
     
    public class MyAtomicBean {
        private AtomicInteger counter = new AtomicInteger(0);
     
        public int increment() {
            return counter.incrementAndGet();
        }
    }
    
实际工作中会使用那种方案来保证Bean的线程安全

实际工作中,通常会根据具体业务来选择合适的线程安全方案,但是以上解决线程安全的方案中:

  • ThreadLocal和原型作用域会使用更多的资源,占用更多的空间来保证线程安全,所以在使用时通常不会作为最佳的考虑方案。
  • 而锁机制和线程安全容器通常会优先考虑,但是需要注意的是AtomicInteger底层是乐观锁CAS实现的,因此存在乐观锁的典型问题ABA问题(如果有状态的Bean中既有++操作,又有–操作的时候,可能出现ABA问题),此时就要使用锁机制,或者AtomicStampedReference来解决ABA问题。

17.介绍下SpringBoot中的自动装配

自动装配是SpringBoot的核心特性之一,它指的是SpringBoot在启动时会自动配置项目所需的各项依赖,使得开发者无需手动配置即可使用这些依赖。这种自动配置是基于Spring的依赖注入(DI)和反转控制(IoC)实现的。
工作原理

  1. 启动类注解:SpringBoot项目通常有一个启动类,该类上标注有@SpringBootApplication注解。这个注解包含了@EnableAutoConfiguration,它是自动装配的关键。
  2. 条件注解:SpringBoot使用了一系列条件注解(如@ConditionalOnClass@ConditionalOnProperty等)来决定是否进行自动配置。这些注解会检查类路径、配置文件等条件,以确定是否满足自动配置的条件。
  3. 自动配置类:SpringBoot提供了一系列自动配置类(通常位于spring-boot-autoconfigure模块中),这些类包含了默认的配置。当满足条件时,这些配置类会被自动加载并应用。
  4. 自定义配置:开发者可以通过在application.propertiesapplication.yml文件中设置属性来覆盖默认的自动配置。

18.如何实现自定义注解?实际工作中哪些地方使用到了自定义注解?

实现自定义注解的步骤如下:

  1. 定义注解接口
    使用@interface关键字定义一个注解接口。
public @interface MyAnnotation {
    // 注解属性
    String value() default "default";
}
  1. 添加注解属性
    在注解接口中定义属性,可以设置默认值。
public @interface MyAnnotation {
    String value() default "default";
    int number() default 0;
}
  1. 使用元注解
    使用元注解(如@Retention@Target@Inherited等)来指定注解的生命周期和适用范围。
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface MyAnnotation {
    String value() default "default";
    int number() default 0;
}
  1. 使用注解
    在代码中使用定义好的注解。
@MyAnnotation(value = "example", number = 10)
public class MyClass {
    // ...
}
  1. 解析注解
    通过反射机制在运行时读取注解信息。
import java.lang.reflect.Method;
public class AnnotationProcessor {
    public static void main(String[] args) throws NoSuchMethodException {
        Class<MyClass> clazz = MyClass.class;
        if (clazz.isAnnotationPresent(MyAnnotation.class)) {
            MyAnnotation annotation = clazz.getAnnotation(MyAnnotation.class);
            System.out.println("Value: " + annotation.value());
            System.out.println("Number: " + annotation.number());
        }
    }
}

实际工作中使用自定义注解的例子
权限控制: 可以创建一个自定义注解来标记哪些方法或资源需要进行权限检查。然后,在AOP切面中拦截这些注解,执行具体的权限验证逻辑。
日志记录: 自定义注解可用于标记需要记录日志的方法。在切面中,我们可以根据注解的存在与否决定是否记录方法调用的日志信息。
输入校验: 对于一些特定的业务规则,可以创建自定义注解并结合Hibernate Validator等框架来进行数据校验。
事务管理: 虽然Spring已经提供了@Transactional注解用于声明式事务管理,但在某些情况下,你可能希望为特定场景定制自己的事务注解。

19.什么是拦截器?如何实现拦截器?

拦截器概述

拦截器(Interceptor)是一种设计模式,广泛应用于各种框架中,比如Java的Spring框架。它允许你在请求到达控制器之前或响应返回给客户端之前对请求和响应进行处理。拦截器通常用于日志记录、权限验证、性能监控等场景。

在Spring MVC框架中,拦截器是通过实现HandlerInterceptor接口或者继承HandlerInterceptorAdapter类来定义的。它提供了三个主要的方法:

  • preHandle():在处理器方法执行之前调用。如果此方法返回false,则流程被中断。
  • postHandle():在处理器方法执行之后,但在视图渲染之前调用。
  • afterCompletion():在整个请求完成之后调用,即在视图渲染完成之后。
实现拦截器

下面是一个简单的实现示例:

  1. 创建自定义拦截器:

    import org.springframework.web.servlet.HandlerInterceptor;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    public class MyInterceptor implements HandlerInterceptor {
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            // 在请求处理之前进行调用(Controller方法调用之前)
            System.out.println("Pre-handle");
            return true; // 如果返回false,则请求被停止
        }
    
        @Override
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
            // 请求处理之后进行调用,但是在视图被渲染之前(Controller方法调用之后)
            System.out.println("Post-handle");
        }
    
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
            // 在整个请求结束之后被调用,也就是在DispatcherServlet 渲染了对应的视图之后执行(主要是用于进行资源清理工作)
            System.out.println("After completion");
        }
    }
    
  2. 注册拦截器: 需要在配置类中将自定义的拦截器注册到Spring MVC的拦截器链中。

    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    
    @Configuration
    public class WebConfig implements WebMvcConfigurer {
    
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
             registry.addInterceptor(new MyCustomInterceptor())
                .addPathPatterns("/my/**")  // 设置拦截路径
                .excludePathPatterns("/my/exclude");  // 设置排除拦截路径
        }
    }
    

20.什么是过滤器?如何实现过滤器?

过滤器概述

过滤器(Filter)是Java Servlet规范中的一部分,主要用于对请求和响应进行预处理和后处理。它可以在请求到达Servlet之前对其进行修改,也可以在响应返回给客户端之前对其进行调整。过滤器常用于日志记录、数据压缩、字符编码转换、权限验证等场景。

一个过滤器需要实现javax.servlet.Filter接口,并重写以下三个方法:

  • init(FilterConfig filterConfig):用于过滤器的初始化。
  • doFilter(ServletRequest request, ServletResponse response, FilterChain chain):核心方法,用于处理请求和响应。
  • destroy():用于释放过滤器占用的资源。
实现过滤器

下面是一个简单的过滤器实现示例:

  1. 创建自定义过滤器:

    import javax.servlet.*;
    import java.io.IOException;
    
    public class MyFilter implements Filter {
    
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
            // 初始化过滤器时调用
            System.out.println("Initializing filter");
        }
    
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
                throws IOException, ServletException {
            // 在请求被处理之前执行
            System.out.println("Before request processing");
    
            // 将请求传递给下一个过滤器或目标servlet
            chain.doFilter(request, response);
    
            // 在响应被发送回客户端之前执行
            System.out.println("After request processing");
        }
    
        @Override
        public void destroy() {
            // 销毁过滤器时调用
            System.out.println("Destroying filter");
        }
    }
    
  2. 配置过滤器: 在传统的Servlet应用中,你需要在web.xml文件中配置过滤器。而在Spring Boot这样的现代框架中,你可以直接通过注解来配置过滤器。

    • 使用注解配置(适用于Spring Boot):

      import org.springframework.stereotype.Component;
      import javax.servlet.Filter;
      import javax.servlet.FilterChain;
      import javax.servlet.FilterConfig;
      import javax.servlet.ServletException;
      import javax.servlet.ServletRequest;
      import javax.servlet.ServletResponse;
      import java.io.IOException;
      
      @Component
      public class MyFilter implements Filter {
      
          @Override
          public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
                  throws IOException, ServletException {
              // 你的过滤逻辑
              System.out.println("Processing in MyFilter");
              chain.doFilter(request, response);
          }
      }
      
    • 使用@WebFilter注解(如果需要更细粒度的控制):

      import javax.servlet.annotation.WebFilter;
      
      @WebFilter(urlPatterns = "/*") // 应用于所有URL模式
      public class MyFilter {
          // 同上MyFilter类的内容
      }
      

过滤器会在每个HTTP请求到达你的应用程序时触发,并根据你在doFilter方法中定义的行为来进行相应的处理。过滤器非常适合用来实现横切关注点,如日志记录、性能监控、安全检查等。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值