Spring面试

发布于:2025-08-01 ⋅ 阅读:(13) ⋅ 点赞:(0)

@Autowired @Resource


@Autowired 是Spring 提供的注解,@Resource是JDK提供的注解。Autowired 默认的注入方式为 byType(根据类型进行匹配),@Resource 默认注入方式为byName(根据名称进行匹配)。
当一个接口存在多个实现类的情况下,@Autowired 和 @Resource 都需要通过名称才能正确匹配到对应的Bean。Autowired 可以通过 @0ualifier 注解来显式指定名称,@Resource 可以通过 name 属性来显式指定名称。


@Autowired 支持在构造函数、方法、字段和参数上使用。@Resource 主要用于字段和方法上的注入,不支持在构造函数或参数上使用。

Bean 的作用域有哪些?


Spring 中Bean 的作用域通常有下面几种:
singleton:loC容器中只有唯一的 bean 实例。Spring 中的 bean 默认都是单例的,是对单例设计模式的应用。
prototype:每次获取都会创建一个新的bean实例。也就是说,连续 getBean()两次,得到的是不同的 Bean 实例。
request(仅Web应用可用):每一次 HTTP 请求都会产生一个新的bean(请求 bean),该bean 仅在当前 HTTP request 内有效。
session(仅Web应用可用):每一次来自新session的 HTTP 请求都会产生一个新的bean(会话bean),该 bean 仅在当前 HTTP session 内有效。
application/global-session(仅Web应用可用):每个Web 应用在启动时创建一个Bean(应用Bean),该bean 仅在当前应用启动时间内有效。
websocket(仅Web 应用可用):每一次 WebSocket 会话产生一个新的 bean。

Spring 的生命周期

1. 实例化 (new)
   ↓
2. 属性注入 (set values)
   ↓
3. Aware 接口回调 (知道名字、工厂、上下文)
   ↓
4. 初始化前处理 (BeanPostProcessor.before)
   ↓
5. 初始化 (afterPropertiesSet / init-method)
   ↓
6. 初始化后处理 (BeanPostProcessor.after)
   ↓
7. Bean 可用了!正常使用
   ↓
8. 容器关闭 → 销毁 (destroy / destroy-method)

💡 和 BeanFactory 的区别?
ApplicationContextBeanFactory 的升级版,功能更多,比如支持国际化、事件发布等。


6️⃣ 第六步:初始化前的“美容”(postProcessBeforeInitialization)

🔔 如果有 BeanPostProcessor,Spring 会在初始化前对 Bean 做一些“加工”。

public class MyPostProcessor implements BeanPostProcessor {
    public Object postProcessBeforeInitialization(Object bean, String name) {
        System.out.println("准备初始化:" + name);
        return bean;
    }
}

📌 用途:可以用来做 AOP、日志、权限检查等“增强功能”。


7️⃣ 第七步:正式初始化(InitializingBean 或 init-method)

✅ 当所有属性都设置好了,Spring 会调用初始化方法。

有两种方式:

方式一:实现接口


java

深色版本

public class UserService implements InitializingBean {
    public void afterPropertiesSet() {
        System.out.println("初始化:连接数据库...");
    }
}

方式二:配置文件或注解指定

<bean class="UserService" init-method="myInit"/>
@PostConstruct
public void myInit() {
    System.out.println("初始化方法");
}

📌 用途:做一些启动时的准备工作,比如连接数据库、加载缓存。


8️⃣ 第八步:初始化后的“再加工”(postProcessAfterInitialization)

🔔 和第六步对应,这是初始化之后的处理。

public Object postProcessAfterInitialization(Object bean, String name) {
    System.out.println("已经初始化完成:" + name);
    return bean;
}

📌 用途:Spring 的 AOP(比如加事务)就是在这里实现的!


9️⃣ 第九步:Bean 正式上岗,一直使用

✅ 现在 Bean 已经完全准备好,可以被其他组件使用了,直到应用关闭。


🔟 最后一步:Bean 要“退休”了(DisposableBean 或 destroy-method)

🔔 当 Spring 容器关闭时,会调用销毁方法。

方式一:实现接口

public class UserService implements DisposableBean {
    public void destroy() {
        System.out.println("销毁:关闭数据库连接...");
    }
}

方式二:配置指定

<bean class="UserService" destroy-method="cleanup"/>

📌 用途:释放资源,比如关闭连接、线程池、文件句柄等。


  1. 🌱 Spring Bean 的一生(生命周期通俗讲解)

    想象一下,Spring 就像一个“工厂”,而 Bean 就是这个工厂里生产出来的“产品”。我们来看看这个产品从出生到销毁经历了哪些阶段。


    1️⃣ 第一步:Spring 把 Bean “造出来”(实例化)

    ✅ 相当于 Java 中的 new User()
    Spring 容器通过反射,调用类的构造函数,创建一个空的对象。

    📌 举个例子:

    User user = new User(); // 此时 user 还没设置名字、年龄等属性

    2️⃣ 第二步:给 Bean “填资料”(属性注入)

    ✅ Spring 把配置好的值或引用(比如其他 Bean)设置到这个对象的属性中。

    📌 继续上面的例子:

    user.setName("张三");
    user.setAge(25);

    这时候,这个 Bean 才真正“完整”了。


    3️⃣ 第三步:告诉 Bean 它叫什么名字(BeanNameAware)

    🔔 如果你的 Bean 实现了 BeanNameAware 接口,Spring 会说:“嘿,你的名字是 userDAO”。

    public class UserDAO implements BeanNameAware {
        public void setBeanName(String name) {
            System.out.println("我的名字是:" + name); // 输出:userDAO
        }
    }

    📌 用途:一般很少用,除非你想在代码里知道自己这个 Bean 叫什么 ID。


    4️⃣ 第四步:告诉 Bean 工厂在哪(BeanFactoryAware)

    🔔 如果实现了 BeanFactoryAware,Spring 会把“工厂”(BeanFactory)给你。

    public class UserService implements BeanFactoryAware {
        private BeanFactory beanFactory;
    
        public void setBeanFactory(BeanFactory beanFactory) {
            this.beanFactory = beanFactory;
        }
    }

    📌 用途:你可以通过 beanFactory.getBean(...) 去拿别的 Bean,相当于“反向查找”。


    5️⃣ 第五步:告诉 Bean 整个上下文环境(ApplicationContextAware)

    🔔 比上一步更强大!如果实现了 ApplicationContextAware,Spring 会把“整个应用环境”给你。

    public class EmailService implements ApplicationContextAware {
        private ApplicationContext context;
    
        public void setApplicationContext(ApplicationContext ctx) {
            this.context = ctx;
        }
    }

    📌 用途:

  2. 发布事件(如用户注册成功后发邮件)
  3. 获取任意 Bean
  4. 访问资源文件

@Component 和 @Bean 的区别是什么?

@Component 注解作用于类,而@Bean注解作用于方法。 @Component通常是通过类路径扫描来自动侦测以及自动装配到 Spring 容器中(我们可以使用 @ComponentScan 注解定义要扫描的路径从中找出标识了需要装配的类自动装配到 Spring 的 bean 容器中)。@Bean 注解通常是我们在标有该注解的方法中定义产生这个 bean,@Bean告诉了 Spring 这是某个类的实例,当我需要用它的时候还给我。 @Bean 注解比 @Component 注解的自定义性更强,而且很多地方我们只能通过 @Bean 注解来注册 bean。比如当我们引用第三方库中的类需要装配到 Spring容器时,则只能通过 @Bean来实现。


springioc

SpringAOP

  1. 更多关于AspectJ

了解AspectJ应用到java代码的过程(这个过程称为织入),对于织入这个概念,可以简单理解为aspect(切面)应用到目标函数(类)的过程。

对于这个过程,一般分为动态织入静态织入

动态织入的方式是在运行时动态将要增强的代码织入到目标类中,这样往往是通过动态代理技术完成的,如Java JDK的动态代理(Proxy,底层通过反射实现)或者CGLIB的动态代理(底层通过继承实现),Spring AOP采用的就是基于运行时增强的代理技术 ApectJ采用的就是静态织入的方式。ApectJ主要采用的是编译期织入,在这个期间使用AspectJ的acj编译器(类似javac)把aspect类编译成class字节码后,在java目标类编译时织入,即先编译aspect类再编译目标类。

    3️⃣ Spring 如何知道要应用这些“特殊服务”?

    这就涉及到 Spring 的 AOP 自动代理机制了。Spring 需要知道哪些类需要应用这些“特殊服务”。为此,我们需要告诉 Spring 使用 <aop:aspectj-autoproxy> 标签来启用 AOP 功能。

    <aop:aspectj-autoproxy />

    这个标签背后的工作流程如下:

    解析配置:Spring 会找到 <aop:aspectj-autoproxy> 标签,并使用 AopNamespaceHandler 来处理它。 注册解析器AopNamespaceHandler 注册了一个 AspectJAutoProxyBeanDefinitionParser,用于解析和创建代理。 创建代理AspectJAutoProxyBeanDefinitionParser 使用 AspectJAwareAdvisorAutoProxyCreator 来创建代理对象。

    这两个接口对应的两个重要方法是:

    4️⃣ 代理是如何工作的?

    AspectJAwareAdvisorAutoProxyCreator 实现了两个关键接口:

    1. BeanFactoryAware:让代理能够访问 Spring 容器中的其他 Bean。
    2. BeanPostProcessor:允许代理在 Bean 生命周期的不同阶段插入自定义逻辑。
    3. postProcessBeforeInstantiation:在这个阶段,Spring 会检查是否有任何带有 @Aspect 注解的类,并将它们的切面方法(如 addSugar()addMilkFoam())封装成 Advisor 对象。每个 Advisor 包含:

      • Advice(通知):即具体的操作(如 addSugar())。
      • Pointcut(切入点):决定哪些方法应该应用这些操作。
    4. postProcessAfterInitialization:一旦所有 Bean 初始化完成,Spring 会根据 Advisor 信息决定是否为某个 Bean 创建代理对象。如果需要,Spring 会选择合适的代理方式(CGLIB 或 JDK 动态代理)并生成代理对象。

    BeanFactoryAware:让代理能够访问 Spring 容器中的其他 Bean。 BeanPostProcessor:允许代理在 Bean 生命周期的不同阶段插入自定义逻辑。

    这两个接口对应的两个重要方法是:

    # Cglib代理的案例

    Spring AOP 的 CGLIB 代理 (CglibAopProxy),通过继承目标类生成子类,在 getCallbacks 中装配一个“拦截器武器库”(callbacks 数组)。核心是 DynamicAdvisedInterceptor(索引0),它实现 MethodInterceptor,在 intercept 方法中,同样依据 Advisor 构建 MethodInterceptor 责任链,驱动 MethodInvocation.proceed() 递归执行,将增强逻辑与目标方法调用编织。CallbackFilter 则像调度员,根据方法决定启用哪个“武器”(如主战武器0号或直调武器1号),从而实现对类的代理

    Spring AOP 的 JDK 代理 (JdkDynamicAopProxy),通过实现 InvocationHandler 接口,在 invoke 方法中,根据配置的 Advisor 动态生成一个 MethodInterceptor 责任链,利用 MethodInvocation.proceed() 的递归调用机制,将增强逻辑(通知)和目标方法的调用编织在一起,从而实现了 AOP。

    1. postProcessBeforeInstantiation:在这个阶段,Spring 会检查是否有任何带有 @Aspect 注解的类,并将它们的切面方法(如 addSugar()addMilkFoam())封装成 Advisor 对象。每个 Advisor 包含:

      • Advice(通知):即具体的操作(如 addSugar())。
      • Pointcut(切入点):决定哪些方法应该应用这些操作。
    2. postProcessAfterInitialization:一旦所有 Bean 初始化完成,Spring 会根据 Advisor 信息决定是否为某个 Bean 创建代理对象。如果需要,Spring 会选择合适的代理方式(CG

      什么是Cglib? SpringAOP和Cglib是什么关系?

      Cglib是一个强大的、高性能的代码生成包,它广泛被许多AOP框架使用,为他们提供方法的拦截。

    3. 最底层是字节码,字节码相关的知识请参考 JVM基础 - 类字节码详解
    4. ASM是操作字节码的工具
    5. cglib基于ASM字节码工具操作字节码(即动态生成代理,对方法进行增强)
    6. SpringAOP基于cglib进行封装,实现cglib方式的动态代理

    DispatcherServlet和ApplicationContext有何关系?

    DispatcherServlet 作为一个 Servlet,需要根据 Servlet 规范使用 Java 配置或 web.xml 声明和映射。反过来,DispatcherServlet 使用 Spring 配置来发现请求映射、视图解析、异常处理等等所需的委托组件。那它和ApplicationContext有和关系呢?如下内容可以参考官网-SpringMVC文档在新窗口打开

    DispatcherServlet 需要 WebApplicationContext(继承自 ApplicationContext) 来配置。WebApplicationContext 可以链接到ServletContext 和 Servlet。因为绑定了 ServletContext,这样应用程序就可以在需要的时候使用 RequestContextUtils 的静态方法访问 WebApplicationContext。

    大多数应用程序只有一个WebApplicationContext,除此之外也可以一个Root WebApplicationContext 被多个 Servlet实例,然后各自拥有自己的Servlet WebApplicationContext 配置。

    Root WebApplicationContext 包含需要共享给多个 Servlet 实例的数据源和业务服务基础 Bean。这些 Bean 可以在 Servlet 特定的范围被继承或覆盖。