admin 管理员组

文章数量: 1184232

新版Java面试专题视频教程,java八股文面试全套真题+深度详解(含大厂高频面试真题)_哔哩哔哩_bilibili

SSM

Spring框架中的单例bean是线程安全的吗?

候选人

不是线程安全的。当多用户同时请求一个服务时,容器会给每个请求分配一个线程,这些线程会并发执行业务逻辑。如果处理逻辑中包含对单例状态的修改,比如修改单例的成员属性,就必须考虑线程同步问题。Spring框架本身并不对单例bean进行线程安全封装,线程安全和并发问题需要开发者自行处理。

通常在项目中使用的Spring bean是不可变状态(如Service类和DAO类),因此在某种程度上可以说Spring的单例bean是线程安全的。如果bean有多种状态(如ViewModel对象),就需要自行保证线程安全。最简单的解决办法是将单例bean的作用域由“singleton”变更为“prototype”。

无状态的意思是不能被修改

线程安全问题

public class Counter {
    private int count = 0; // 成员变量

    public void increment() {
        count++; // 非原子操作
    }

    public int getCount() {
        return count;
    }
}

如果我们创建了 Counter 类的一个实例,并从多个线程中调用 increment 方法,可能会遇到竞态条件(race condition)。这是因为 count++ 操作不是原子性的,实际上它包括三个步骤:

  1. 读取 count 的值。
  2. count 的值加 1。
  3. 将新的值写回 count

如果两个线程几乎同时执行这些步骤,就可能发生以下情况:

  • 线程 A 和线程 B 同时读取了相同的 count 值。
  • 它们都增加了这个值并试图将其写回。
  • 结果,count 只增加了一次,而不是两次。
public class Counter {
    private int count = 0;

    public synchronized void increment() {
        count++;
    }

    public synchronized int getCount() {
        return count;
    }
}

通过这种方式,我们可以确保在任何时刻只有一个线程可以执行 incrementgetCount 方法,从而避免了竞态条件和数据不一致的问题。

什么是AOP?

候选人

AOP,即面向切面编程,在Spring中用于将那些与业务无关但对多个对象产生影响的公共行为和逻辑抽取出来,实现公共模块复用,降低耦合。常见的应用场景包括公共日志保存和事务处理。

你们项目中有没有使用到AOP?

候选人

我们之前在后台管理系统中使用AOP来记录系统操作日志。主要思路是使用AOP的环绕通知和切点表达式,找到需要记录日志的方法,然后通过环绕通知的参数获取请求方法的参数,例如类信息、方法信息、注解、请求方式等,并将这些参数保存到数据库。

package com.itheima.annotation;


import java.lang.annotation.*;

@Target({ ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {

    /**
     * 模块名称
     */
    public String name() default "";
}
package com.itheima.aop;

import com.itheima.annotation.Log;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Date;

@Component
@Aspect   //切面类
public class SysAspect {

    @Pointcut("@annotation(com.itheima.annotation.Log)")
    private void pointcut() {

    }

    @Pointcut("execution(* com.itheima.service.*.*(..))")
    public void pointcut2(){}

    @Around("pointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        //获取用户名
        //需要通过解析seesion或token获取

        //获取被增强类和方法的信息
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        //获取被增强的方法对象
        Method method = methodSignature.getMethod();
        //从方法中解析注解
        if(method != null){
            Log logAnnotation = method.getAnnotation(Log.class);
            System.out.println(logAnnotation.name());
        }
        //方法名字
        String name = method.getName();
        System.out.println(name);

        //通过工具类获取Request对象
        RequestAttributes reqa = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes sra = (ServletRequestAttributes)reqa;
        HttpServletRequest request = sra.getRequest();
        //访问的url
        String url = request.getRequestURI().toString();
        System.out.println(url);
        //请求方式
        String methodName = request.getMethod();
        System.out.println(methodName);

        //登录IP
        String ipAddr = getIpAddr(request);
        System.out.println(ipAddr);

        //操作时间
        System.out.println(new Date());*/

        //保存到数据库(操作日志)
        //....

        return joinPoint.proceed();
    }

    /**
     * 获取ip地址
     * @param request
     * @return
     */
    public String getIpAddr(HttpServletRequest request){
        String ip = request.getHeader("x-forwarded-for");
        if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
            ip = request.getHeader("Proxy-Client-IP");
        }
        if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
            ip = request.getRemoteAddr();
        }

        return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip;
    }

}

Spring中的事务是如何实现的?

候选人

Spring实现事务的本质是利用AOP完成的。它对方法前后进行拦截,在执行方法前开启事务,在执行完目标方法后根据执行情况提交或回滚事务。

Spring中事务失效的场景有哪些?

候选人

在项目中,我遇到过几种导致事务失效的场景:

  1. 如果方法内部捕获并处理了异常,没有将异常抛出,会导致事务失效。因此,处理异常后应该确保异常能够被抛出。
  2. 如果方法抛出检查型异常(checked exception),并且没有在@Transactional注解上配置rollbackFor属性为Exception,那么异常发生时事务可能不会回滚。
  3. 如果事务注解的方法不是公开(public)修饰的,也可能导致事务失效。

Spring的bean的生命周期?

候选人

Spring中bean的生命周期包括以下步骤:

  1. 通过BeanDefinition获取bean的定义信息。
  2. 调用构造函数实例化bean。
  3. 进行bean的依赖注入,例如通过setter方法或@Autowired注解。
  4. 处理实现了Aware接口的bean。
  5. 执行BeanPostProcessor的前置处理器。
  6. 调用初始化方法,如实现了InitializingBean接口或自定义的init-method
  7. 执行BeanPostProcessor的后置处理器,可能在这里产生代理对象。
  8. 最后是销毁bean。

package com.itheima.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan("com.itheima.lifecycle")
public class SpringConfig {
}
package com.itheima.lifecycle;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.InvocationHandler;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

@Component
public class MyBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if (beanName.equals("user")) {
            System.out.println("postProcessBeforeInitialization方法执行了->user对象初始化方法前开始增强....");
        }
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (beanName.equals("user")) {
            System.out.println("postProcessAfterInitialization->user对象初始化方法后开始增强....");
            //cglib代理对象
            Enhancer enhancer = new Enhancer();
            //设置需要增强的类
            enhancer.setSuperclass(bean.getClass());
            //执行回调方法,增强方法
            enhancer.setCallback(new InvocationHandler() {
                @Override
                public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
                    //执行目标方法
                    return method.invoke(method,objects);
                }
            });
            //创建代理对象
            return enhancer.create();
        }
        return bean;
    }

}
package com.itheima.lifecycle;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

@Component
public class User implements BeanNameAware, BeanFactoryAware, ApplicationContextAware, InitializingBean {

    //实例化对象
    public User() {
        System.out.println("User的构造方法执行了.........");
    }

    private String name ;

    //依赖注入 也可以是@Autowired
    @Value("张三")
    public void setName(String name) {
        System.out.println("setName方法执行了.........");
    }

    @Override
    public void setBeanName(String name) {
        System.out.println("setBeanName方法执行了.........");
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        System.out.println("setBeanFactory方法执行了.........");
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        System.out.println("setApplicationContext方法执行了........");
    }

    @PostConstruct
    public void init() {
        System.out.println("init方法执行了.................");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("afterPropertiesSet方法执行了........");
    }

    @PreDestroy
    public void destory() {
        System.out.println("destory方法执行了...............");
    }

}
package com.itheima.lifecycle;


import com.itheima.config.SpringConfig;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;


public class UserTest {

    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
        User user = ctx.getBean(User.class);
        System.out.println(user);
    }

}

Spring中的循环引用?

候选人

循环依赖发生在两个或两个以上的bean互相持有对方,形成闭环。Spring框架允许循环依赖存在,并通过三级缓存解决大部分循环依赖问题:

  1. 一级缓存:单例池,缓存已完成初始化的bean对象。
  2. 二级缓存:缓存尚未完成生命周期的早期bean对象。
  3. 三级缓存:缓存ObjectFactory,用于创建bean对象。

三级缓存解决的是初始化赋值阶段的问题,所以说是解决大部分循环引用问题,不能解决构造方法阶段的问题,构造方法之前没有循环引用的,有问题的是有循环引用的。
ObjectFactory 如果需要的是原始对象就创建原始对象,如果需要的是代理对象就创建代理对象,然后放入二级缓存

那具体解决流程清楚吗?

候选人

解决循环依赖的流程如下:

  1. 实例化A对象,并创建ObjectFactory存入三级缓存。
  2. A在初始化时需要B对象,开始B的创建逻辑。
  3. B实例化完成,也创建ObjectFactory存入三级缓存。
  4. B需要注入A,通过三级缓存获取ObjectFactory生成A对象,存入二级缓存。
  5. B通过二级缓存获得A对象后,B创建成功,存入一级缓存。
  6. A对象初始化时,由于B已创建完成,可以直接注入B,A创建成功存入一级缓存。
  7. 清除二级缓存中的临时对象A。

构造方法出现了循环依赖怎么解决?

候选人

由于构造函数是bean生命周期中最先执行的,Spring框架无法解决构造方法的循环依赖问题。可以使用@Lazy懒加载注解,延迟bean的创建直到实际需要时。

SpringMVC的执行流程?

候选人

SpringMVC的执行流程包括以下步骤:

  1. 用户发送请求到前端控制器DispatcherServlet
  2. DispatcherServlet调用HandlerMapping找到具体处理器。
  3. HandlerMapping返回处理器对象及拦截器(如果有)给DispatcherServlet
  4. DispatcherServlet调用HandlerAdapter
  5. HandlerAdapter适配并调用具体处理器(Controller)。
  6. Controller执行并返回ModelAndView对象。
  7. HandlerAdapterModelAndView返回给DispatcherServlet
  8. DispatcherServlet传给ViewResolver进行视图解析。
  9. ViewResolver返回具体视图给DispatcherServlet
  10. DispatcherServlet渲染视图并响应用户。

handler就是对应的方法

Springboot自动配置原理?

候选人

Spring Boot的自动配置原理基于@SpringBootApplication注解,它封装了@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan@EnableAutoConfiguration是核心,它通过@Import导入配置选择器,读取META-INF/spring.factories文件中的类名,根据条件注解决定是否将配置类中的Bean导入到Spring容器中。

导入了起步依赖才有字节码文件

如果容器中有该对象就不创建了,一般都是@ConditionalOnMissingBean(MyService.class),这样子写。

SpringBoot自定义starter

Spring 的常见注解有哪些?

候选人

Spring的常见注解包括:

  1. 声明Bean的注解:@Component@Service@Repository@Controller
  2. 依赖注入相关注解:@Autowired@Qualifier@Resource
  3. 设置作用域的注解:@Scope
  4. 配置相关注解:@Configuration@ComponentScan@Bean
  5. AOP相关注解:@Aspect@Before@After@Around@Pointcut

SpringMVC常见的注解有哪些?

候选人

SpringMVC的常见注解有:

  • @RequestMapping:映射请求路径。
  • @RequestBody:接收HTTP请求的JSON数据。
  • @RequestParam:指定请求参数名称。
  • @PathVariable:从请求路径中获取参数。
  • @ResponseBody:将Controller方法返回的对象转化为JSON。
  • @RequestHeader:获取请求头数据。
  • @PostMapping@GetMapping等。

Springboot常见注解有哪些?

候选人

Spring Boot的常见注解包括:

  • @SpringBootApplication:由@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan组成。
  • 其他注解如@RestController@GetMapping@PostMapping等,用于简化Spring MVC的配置。

MyBatis执行流程?

候选人

MyBatis的执行流程如下:

  1. 读取MyBatis配置文件mybatis-config.xml
  2. 构造会话工厂SqlSessionFactory
  3. 会话工厂创建SqlSession对象。
  4. 操作数据库的接口,Executor执行器。
  5. Executor执行方法中的MappedStatement参数。
  6. 输入参数映射。
  7. 输出结果映射。

Mybatis是否支持延迟加载?

候选人

MyBatis支持延迟加载,即在需要用到数据时才加载。可以通过配置文件中的lazyLoadingEnabled配置启用或禁用延迟加载。

延迟加载是查询订单信息才去查询订单信息

‘’

不用就删除

延迟加载的底层原理知道吗?

候选人

延迟加载的底层原理主要使用CGLIB动态代理实现:

  1. 使用CGLIB创建目标对象的代理对象。
  2. 调用目标方法时,如果发现是null值,则执行SQL查询。
  3. 获取数据后,设置属性值并继续查询目标方法。

本文标签: 黑马 代码 图文