参考文章
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:
- 使用和配置方便
- 开发效率高,存在许多标签增强SQL表达,增加SQL复用性
- 参数化查询防止SQL注入
Q:了解Spring的IoC吗,能简单讲解一下你对它的理解吗?
IoC(Inversion of Control:控制反转) 是一种设计思想,而不是一个具体的技术实现。IoC 的思想就是将原本在程序中手动创建对象的控制权,交由 Spring 框架来管理。不过, IoC 并非 Spring 特有,在其他语言中也有应用。为什么叫控制反转?
- 控制:指的是对象创建(实例化、管理)的权力
- 反转:控制权交给外部环境(Spring 框架、IoC 容器)
要实现控制反转主要有两步:
- 我们需要指明哪些对象要被移交给Spring框架进行控制,这些对象被成为Spring bean
- 解决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是如何解决循环依赖的问题的?
最简单的场景就是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解决循环依赖的步骤:
- 创建Bean A :当Spring开始创建Bean A时,它会首先检查第三级缓存(单例工厂缓存)中是否已经有了Bean A的工厂对象。如果没有,就创建一个新的工厂对象并将其放入缓存中。
- 填充Bean A的属性 :在创建Bean A的过程中,Spring需要填充它的属性。如果Bean A的属性中有一个对Bean B的引用,那么Spring会开始创建Bean B。
- 创建Bean B :同样地,Spring会检查第三级缓存中是否已经有了Bean B的工厂对象。如果没有,就创建一个新的工厂对象并将其放入缓存中。
- 填充Bean B:Spring会检查第二级缓存(早期对象缓存)中是否已经有了Bean A的早期对象。如果有,就将这个早期对象注入到Bean B中,从而解决循环依赖问题。如果没有,到第三级缓存中(单例工厂缓存)调用工厂方法在二级缓存中创建Bean A的早期对象,然后再将这个工厂从三级缓存中移除,最后将早期对象注入到Bean B中。
- 完成Bean B的初始化 :在Bean B的初始化完成后,它的完全初始化好的对象会被放入第一级缓存(单例Bean池)中。
- 完成Bean A的初始化 :回到Bean A的创建过程,当所有属性都填充完毕后,Spring会调用Bean A的初始化方法。完成后,Bean A的完全初始化好的对象也会被放入第一级缓存中。
本博客文章除特别声明外,均可自由转载与引用,转载请标注原文出处:http://www.yelbee.top/index.php/archives/234/