那些年看过的 “Spring”

        本文主要举一些常见的关于Spring的经典面试题,看看我们对Spring了解有多深。

别的话就不多说了直接开整吧。(本文的解释参考官方和一些博客,描述不清请见谅!)

5efd8c51c7ac4d13b3d7c758bf3b1d31.jpeg

 

IoC控制反转

        IoC(Inverse of Control):是一种设计思想(模式),意思就是把原本在程序中需要我们手动创建对象的过程交给 Spring 来处理。

给出一个简单的例子:

public interface MessageService {
    String getMessage();
}

public class EmailService implements MessageService {
    @Override
    public String getMessage() {
        return "Email message";
    }
}

public class MyApplication {
    private MessageService messageService;

    // 通过构造函数注入依赖
    public MyApplication(MessageService messageService) {
        this.messageService = messageService;
    }

    public void processMessages() {
        System.out.println(messageService.getMessage());
    }
}

public class Main {
    public static void main(String[] args) {
        MessageService emailService = new EmailService();
        MyApplication app = new MyApplication(emailService);
        app.processMessages();
    }
}

        在Java中,IoC通常与依赖注入(DI)一起使用,而在Python中,IoC的实现通常更加轻量级,并且内置在语言中。在C#中,IoC通常与.NET框架一起使用。

DI(Dependancy Injection):依赖注入,站在容器的角度,将对象创建依赖的其它对象注入到对象中。有助于降低对象之间的耦合度,并使代码更易于测试和维护。

 还是一个例子:

public interface MessageService {
    String getMessage();
}

public class EmailService implements MessageService {
    @Override
    public String getMessage() {
        return "Email message";
    }
}

public class MyApplication {
    private MessageService messageService;

    // 通过构造函数注入依赖
    public MyApplication(MessageService messageService) {
        this.messageService = messageService;
    }

    public void processMessages() {
        System.out.println(messageService.getMessage());
    }
}

public class Main {
    public static void main(String[] args) {
        MessageService emailService = new EmailService();
        MyApplication app = new MyApplication(emailService);
        app.processMessages();
    }
}

 直接来八股吧,直接背把xdm8d833d6e1404411597304400becf9d01.png

        IoC 容器是 Spring⽤来实现 IoC 的载体, IoC 容器实际上就是个Map(key,value),Map 中存放的是各种对象。将对象之间的相互依赖关系交给 IoC 容器来管理,并由 IoC 容器完成对象的注⼊。这样可以很⼤程度上简化应⽤的开发,把应⽤从复杂的依赖关系中解放出来。IoC 容器就像是⼀个⼯⼚⼀样,当我们需要创建⼀个对象的时候,只需要配置好配置⽂件/注解即可,完全不⽤考虑对象是如何被创建出来的。

 

AOP 动态代理 

        AOP(Aspect Oriented Programming):面向切面编程,将与业务无关,但却被一些业务模块共同调用的逻辑或责任(事物、日志和权限控制等)分装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。

        AOP通过提供另一种思考程序结构的方式,对面向对象编程(OOP)进行补充。在OOP中,模块化的关键单元是类,而在AOP中,模块化的单元是切面(aspect)。Spring框架中的AOP模块提供了声明式企业服务和自定义切面的能力,从而增强了OOP的功能。

        Spring框架中提供了两种主要的自定义切面的方法:基于模式的方法和基于@AspectJ注解的方法。

  1. 基于模式的方法:

    • 基于模式的方法是通过在Spring配置文件中定义切面和通知的方式来实现的。这种方法需要在XML配置文件中声明切面和通知,然后将它们与目标bean进行关联。
    • 通知可以是“前置通知”、“后置通知”、“环绕通知”、“异常通知”和“最终通知”等。每种通知都可以在目标方法的不同执行阶段被触发。
    • 通过基于模式的方法,可以更加灵活地控制切面和通知的织入方式,但是需要在XML配置文件中进行繁琐的声明。
  1. 基于@AspectJ注解的方法:

    • 基于@AspectJ注解的方法是通过在普通的Java类中使用注解来定义切面和通知的方式来实现的。这种方法更加简洁和直观,使得切面的定义更加集中和易于维护。
    • 通过@AspectJ注解,可以在普通的Java类中定义切面和通知,而无需在XML配置文件中进行繁琐的声明。
    • 通知的类型和触发时机与基于模式的方法相同,包括“前置通知”、“后置通知”、“环绕通知”、“异常通知”和“最终通知”等。

这里就给出通过注解的一个简单的实现:

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class LoggingAspect {

    @Before("execution(* com.example.service.*.*(..))")
    public void beforeServiceMethods() {
        System.out.println("Before executing service method");
    }
}

         定义了一个名为LoggingAspect的切面类,并使用了 @Aspect 注解来标识它是一个切面。在切面类中,使用了@Before注解来定义一个前置通知,它会在执行com.example.service包中的所有方法之前被触发。beforeServiceMethods 方法会在目标方法执行之前输出日志信息。

        Spring AOP使用动态代理,如果要代理的对象实现了接口,Spring AOP会使用JDK动态代理来创建代理对象。而对于没有实现接口的对象,Spring AOP会使用基于ASM框架的字节码的Cglib动态代理,生成一个被代理对象的子类来作为代理。荔枝如下:

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

public class CglibProxyExample {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(MyClass.class);
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
                System.out.println("Before method " + method.getName());
                Object result = proxy.invokeSuper(obj, args);
                System.out.println("After method " + method.getName());
                return result;
            }
        });
        MyClass proxy = (MyClass) enhancer.create();
        proxy.myMethod();
    }

    static class MyClass {
        public void myMethod() {
            System.out.println("Executing myMethod");
        }
    }
}

 

Bean 的生命周期

 单例:singleton    生命周期和容器相同

 多例:prototype    

出生:使用对象时 Spring 框架来创建

活着:对象只要在使用的过程中就一直是活着的

死亡:当对象长时间不用且没有其它的对象引用该对象时,会有 JAVA 的垃圾回收机制回收

下图是Bean生命周期的过程:

2d0af2483db54ad7b2e7ef078e73b0a5.png

什么!不看英文的吗?哦,好的,换个简单简单点的~~~~

 92e0879512bb44638de72b45b09d0a01.png

 IoC容器初始化加载Bean的流程(具体的可自行了解)

加载配置文件:
    从XML文件或注解配置中获取Bean的定义和依赖关系

创建Bean实例:
    根据配置文件中的定义,实例化对象并设置属性

注入依赖:
    确保各个Bean之间能够正确地相互调用和协作

调用初始化方法:
    如果Bean定义中指定了初始化方法,调用这些方法进行额外的初始化操作

将Bean注册到容器:
    将创建好的Bean实例注册到容器中,以便后续的使用

 四个阶段

  1. 实例化 Instantiation
  2. 属性赋值 Populate
  3. 初始化 Initialization
  4. 销毁 Destruction

多个扩展点

  1. 影响多个 Bean     (BeanPostProcessor、InstantiationAwareBeanPostProcessor)
  2. 影响单个 Bean     (Aware)

完整的流程

  1. 实例化 Bean -- new 个对象
  2. 按照 Spring 上下文对实例化的 Bean 进行配置 -- IoC注入
  3. 如果这个Bean已经实现了BeanNameAware接口,会调用它实现的setBeanName(String)方法,也就是根据就是Spring配置文件中Bean的id和name进行传递
  4. 如果这个Bean已经实现了BeanFactoryAware接口,会调用它实现setBeanFactory(BeanFactory)也就是Spring配置文件配置的Spring工厂自身进行传递

  5. 如果这个Bean已经实现了ApplicationContextAware接口,会调用setApplicationContext(ApplicationContext)方法,和4传递的信息一样但是因为ApplicationContext是BeanFactory的子接口,所以更加灵活

  6. 如果这个Bean关联了BeanPostProcessor接口,将会调用postProcessBeforeInitialization()方法,BeanPostProcessor经常被用作是Bean内容的更改,由于这个是在Bean初始化结束时调用那个的方法,也可以被应用于内存或缓存技术

  7. 如果Bean在Spring配置文件中配置了init-method属性会自动调用其配置的初始化方法。

  8. 如果这个Bean关联了BeanPostProcessor接口,将会调用postProcessAfterInitialization(),打印日志或者三级缓存技术里面的bean升级

  9. 以上工作完成以后就可以应用这个Bean了,那这个Bean是一个Singleton的,所以一般情况下我们调用同一个id的Bean会是在内容地址相同的实例,当然在Spring配置文件中也可以配置非Singleton,这里我们不做赘述

  10. 当Bean不再需要时,会经过清理阶段,如果Bean实现了DisposableBean这个接口,或者根据spring配置的destroy-method属性,调用实现的destroy()方法

这段是借鉴的,感觉写的比我写的“荔枝”多了,很好!05b830475f534ff3a8c515ad6b7165b6.png

 

Bean 的作用域

  • singleton:IoC容器中只有唯一的Bean实例。Spring 中的 Bean 默认都是单例的
  • prototype:每次获取都会创建一个新的bean实例。
  • request:(web页面可用)每次Http请求都会产生一个新的bean(请求bean),在当前的Http request有效
  • session:(web页面可用)每次新来一个session的Http请求都会生成一个新的bean(会话bean),在当前的Http session 内有效
  • application/global-session:(仅 Web 应用可用):每个 Web 应用在启动时创建一个 Bean(应用 Bean),该 bean 仅在当前应用启动时间内有效
  • websocket:每一次 WebSocket 会话产生一个新的 bean

 

配置bean的作用域有两种方式:

xml方式:

<bean id="..." class="..." scope="singleton"></bean>

注解:

@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Person personPrototype() {
    return new Person();
}

拐个弯,扯一点其它的!!!

        默认的是 singleton的,多个线程同时访问同一个bean可能会存在线程安全的问题

那么如何解决这个问题呢?

  1. 在Bean对象中尽量避免定义可变的成员变量(不太现实)

  2. 在类中定义⼀个ThreadLocal成员变量,将需要的可变成员变量保存在 ThreadLocal 中

简单提一嘴ThreadLocal,后续会详细讲讲!

        每个线程中都有一个自己的ThreadLocalMap类对象,可以将线程自己的对象保持到其中,各管各的,线程可以正确的访问到自己的对象。

        将一个共用的ThreadLocal静态实例作为key,将不同对象的引用保存到不同线程的ThreadLocalMap中,然后在线程执行的各处通过这个静态ThreadLocal实例的get()方法取得自己线程保存的那个对象,避免了将这个对象作为参数传递的麻烦。

 好了,拐回来了,扯远了,这里又涉及到了Bean的线程安全问题,就不在多说了。

 

循环依赖

循环依赖其实就是循环引用,也就是两个或者两个以上的 Bean 互相持有对方,最终形成闭环。比如A 依赖于B,B又依赖于A

Spring中循环依赖场景有: 

  • prototype 原型 bean循环依赖

  • 构造器的循环依赖(构造器注入)

  • Field 属性的循环依赖(set注入)

    其中,构造器的循环依赖问题无法解决,在解决属性循环依赖时,可以使用懒加载,spring采用的是提前暴露对象的方法。

懒加载@Lazy解决循环依赖问题

Spring 启动的时候会把所有bean信息(包括XML和注解)解析转化成Spring能够识别的BeanDefinition并存到Hashmap里供下面的初始化时用,然后对每个 BeanDefinition 进行处理。普通 Bean 的初始化是在容器启动初始化阶段执行的,而被lazy-init=true修饰的 bean 则是在从容器里第一次进行context.getBean() 时进行触发

三级缓存解决循环依赖问题

 

422365232ea553a29fdba9eebf583091.png

  1. Spring容器初始化ClassA通过构造器初始化对象后提前暴露到Spring容器中的singletonFactorys(三级缓存中)。

  2. ClassA调用setClassB方法,Spring首先尝试从容器中获取ClassB,此时ClassB不存在Spring 容器中。

  3. Spring容器初始化ClassB,ClasssB首先将自己暴露在三级缓存中,然后从Spring容器一级、二级、三级缓存中一次中获取ClassA 。

  4. 获取到ClassA后将自己实例化放入单例池中,实例 ClassA通过Spring容器获取到ClassB,完成了自己对象初始化操作。

  5. 这样ClassA和ClassB都完成了对象初始化操作,从而解决了循环依赖问题。

 

不想搞了,放到下一篇吧,脑阔疼!!散会!!

406ef7ac6c86410d85dcc5217062d9ed.jpeg

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

計贰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值