MENU

Spring/SpringBoot

2024 年 10 月 22 日 • 访问: 377 次 • 云原生

参考文章
Spring框架的介绍
Spring基础 - Spring核心之面向切面编程(AOP)
Spring AOP 扫盲
字节码增强技术探索

Q: AOP是否有使用过,适用于哪些场景?
适用于审计日志、权限管理、性能统计这些通用功能逻辑,AOP能够将通用逻辑和业务逻辑分离,并且能动态的把这些功能添加到需要的代码中,这样各司其职,降低业务逻辑和通用功能的耦合。

如下面这段伪代码为例,我们用切面在所有的Controller处做权限校验和记录审计日志

@Aspect
@Component
public class ControllerAspect {
    /**
     * 使用切面对所有Controller做权限校验和记录审计日志
     *
     * @param joinPoint 切点
     * @return Object
     * @throws HttpException 校验异常
     */
    @Around("execution(public * com.xxx..*Controller.*(..))")
    public Object doAspect(ProceedingJoinPoint joinPoint) throws HttpException {

        Method method;
        if (!(joinPoint.getSignature() instanceof MethodSignature)) {
            throw new UnsupportedOperationException("Cannot get MethodSignature");
        } else {
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            method = signature.getMethod();
        }
        Permission permission = (Permission) method.getAnnotation(Permission.class); // 获取注解Permission
        checkAnnotations(permission); // 检查权限校验的注解是否配置了

        HttpServletRequest request = getHttpServletRequest(); // 拿到HTTP请求体
        recordAuditLog(request); // 记录请求体的审计日志

        responseEntity = joinPoint.proceed(joinPoint.getArgs());
        return responseEntity;
    }
}

Q: AOP的原理是什么?
A: SpringAOP的原理是JDK的动态代理(底层原理是反射)或者使用CGLib动态代理(底层原理是继承),切面是在运行期织入的

Q:Spring AOP 中织入的三种时期:
A:

  • 编译期: 切面在目标类编译时被织入,这种方式需要特殊的编译器。AspectJ 的织入编译器就是以这种方式织入切面的
  • 类加载期: 切面在目标类加载到 JVM 时被织入,这种方式需要特殊的类加载器( ClassLoader ),它可以在目标类引入应用之前增强目标类的字节码。javaagent的premain方式就是以这种方式增强字节码的。
  • 运行期: 切面在应用运行的某个时期被织入。一般情况下,在织入切面时,AOP容器会为目标对象动态创建一个代理对象,Spring AOP,采用的就是这种织入方式。另外,javaagent的agentmain方式采用了该织入方式。

Q:为什么要使用Mybatis,它有哪些优点?
A:

  1. 使用和配置方便
  2. 开发效率高,存在许多标签增强SQL表达,增加SQL复用性
  3. 参数化查询防止SQL注入

Q:了解Spring的IoC吗,能简单讲解一下你对它的理解吗?

IoC(Inversion of Control:控制反转) 是一种设计思想,而不是一个具体的技术实现。IoC 的思想就是将原本在程序中手动创建对象的控制权,交由 Spring 框架来管理。不过, IoC 并非 Spring 特有,在其他语言中也有应用。为什么叫控制反转?

  • 控制:指的是对象创建(实例化、管理)的权力
  • 反转:控制权交给外部环境(Spring 框架、IoC 容器)

要实现控制反转主要有两步:

  1. 我们需要指明哪些对象要被移交给Spring框架进行控制,这些对象被成为Spring bean
  2. 解决Spring bean对象之间的依赖问题,这里会用到我们常说的注入。

@Component, @Controller, @Service, @Repository这些注解是用于声明为Spring bean的注解,我们标注的对象会将其创建的控制权移交给了Spring框架,由IoC容器完成对象的创建。

而依赖注入是什么意思呢?实际上在开发过程中,我们经常会有某个类A需要依赖B的场景,那么我们称之为B需要注入到A中,以便在A中能够调用B中的属性和方法。我们将手动new一个对象的操作,交给Spring IoC容器来帮我们完成,这个就称之为Spring的注入。常用的注解是@Autowired(默认byType)和@Resource(默认byName)

public class A {
    public void execute() {
        B b = new B();
        b.singAndJump();
    }
}

public class B {
    public void singAndJump() {
        doSomething();
    }
}

当使用Spring IoC时,我们用@Component将class A和class B标识为一个Spring bean,并且使用@Autowired注解将B注入到了A中,让Spring框架帮我们完成了类初始化和依赖注入。

@Component
public class A {
    @Autowired
    private B b;

    public void execute() {
        b.singAndJump();
    }
}

@Component
public class B {
    public void singAndJump() {
        doSomething();
    }
}

Q: 循环依赖是如何产生的,Spring是如何解决循环依赖的问题的?

参考资料
Spring的循环依赖解决策略
面试必杀技,讲一讲Spring中的循环依赖
Spring的循环依赖

最简单的场景就是A和B的相互依赖

@Component
public class A {
    // A中注入了B
    @Autowired
    private B b;
}

@Component
public class B {
    // B中也注入了A
    @Autowired
    private A a;
}

我们接下来以上述A和B相互依赖作为例子,来说明Spring是如何解决循环依赖问题的。

我们首先要知道,Spring在创建Bean的过程中分为三步:

  • 实例化,简单理解就是new了一个对象
  • 属性注入,为实例化中new出来的对象填充属性
  • 初始化,执行aware接口中的方法,初始化方法,完成AOP代理

解决循环依赖问题的关键就是使用三级缓存的机制:

  • singletonObjects(单例Bean池):一级缓存,存储的是所有创建好了的单例Bean,已经实例化、属性注入、初始化
  • earlySingletonObjects(早期对象缓存):二级缓存,存储的是完成实例化,但是还未进行属性注入及初始化的对象
  • singletonFactories(单例工厂缓存):三级缓存,提前暴露的一个单例工厂,二级缓存中存储的就是从这个工厂中获取到的对象

Spring解决循环依赖的步骤:

  1. 创建Bean A :当Spring开始创建Bean A时,它会首先检查第三级缓存(单例工厂缓存)中是否已经有了Bean A的工厂对象。如果没有,就创建一个新的工厂对象并将其放入缓存中。
  2. 填充Bean A的属性 :在创建Bean A的过程中,Spring需要填充它的属性。如果Bean A的属性中有一个对Bean B的引用,那么Spring会开始创建Bean B。
  3. 创建Bean B :同样地,Spring会检查第三级缓存中是否已经有了Bean B的工厂对象。如果没有,就创建一个新的工厂对象并将其放入缓存中。
  4. 填充Bean B:Spring会检查第二级缓存(早期对象缓存)中是否已经有了Bean A的早期对象。如果有,就将这个早期对象注入到Bean B中,从而解决循环依赖问题。如果没有,到第三级缓存中(单例工厂缓存)调用工厂方法在二级缓存中创建Bean A的早期对象,然后再将这个工厂从三级缓存中移除,最后将早期对象注入到Bean B中。
  5. 完成Bean B的初始化 :在Bean B的初始化完成后,它的完全初始化好的对象会被放入第一级缓存(单例Bean池)中。
  6. 完成Bean A的初始化 :回到Bean A的创建过程,当所有属性都填充完毕后,Spring会调用Bean A的初始化方法。完成后,Bean A的完全初始化好的对象也会被放入第一级缓存中。
最后编辑于: 2024 年 10 月 23 日
返回文章列表 打赏
本页链接的二维码
打赏二维码
添加新评论