谈谈对IOC的理解
(是什么)
IOC也就是控制反转,要谈谈IOC,就要先说一下控制反转是什么。
传统的开发方式就是A依赖于B,那就是A 手动new一个B的对象出来;而IOC的方式是不需要new对象,而是通过IOC容器也就是Spring来帮助我们实例化对象,需要什么对象,就去IOC容器里取出来就行。
所以说控制反转的意思就是“把对象创建管理什么的 权利交给外部环境IOC”。
(为什么)
那为什么要使用IOC?解决了什么问题?
对象之间的耦合度变低,也可以说依赖程度;资源容易管理,比如使用spring容器很容易实现一个单例,而传统模式得自己写。
没有IOC是这样的:
Service 层想要使用 Dao 层的具体实现的话,需要通过 new 关键字在UserServiceImpl中手动 new 出IUserDao的具体实现类UserDaoImpl(不能直接 new 接口类)。
- 放图...
有IOC是这样的:
- 放图...
谈谈对AOP的理解
(是什么)
aop就是面向切面编程,主要的思想就是把横切关注的地方从核心业务逻辑分离出来,
切面Aspect:对关注点封装的类。
通知类型有哪些?
Before前置通知:在方法调用之前触发
After后置通知:在方法调用之后触发
AfterReturning返回通知:在方法调用完成,返回返回值之后触发
AfterThrowing异常通知:在方法调用中抛出异常后触发
Around环绕通知:可以在任意前后触发,还可以直接拿到目标对象,要执行的方法
(为什么)
为什么要用AOP?解决了什么问题?
比如日志记录、事务管理、权限控制、接口限流等,如果一直重复实现这些行为,代码会特别冗余,难以维护。
// 日志注解
@Target({ElementType.PARAMETER,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {
/**
* 描述
*/
String description() default "";
/**
* 方法类型 INSERT DELETE UPDATE OTHER
*/
MethodType methodType() default MethodType.OTHER;
}
// 日志切面
@Component
@Aspect
public class LogAspect {
// 切入点,所有被 Log 注解标注的方法
@Pointcut("@annotation(cn.annotation.Log)")
public void webLog() {
}
/**
* 环绕通知
*/
@Around("webLog()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
// 省略具体的处理逻辑
}
// 省略其他代码
}@Log(description = "method1",methodType = MethodType.INSERT)
public CommonResponse<Object> method1() {
// 业务逻辑
xxService.method1();
// 省略具体的业务处理逻辑
return CommonResponse.success();
}(怎么实现的?)
spring AOP就是基于动态代理实现的,我详细聊聊Spring AOP 的实现机制
Spring AOP 的实现机制
A.如何实现剥离?
一共有三个对象,真实对象(Target)、代理对象(Proxy)、增强逻辑(Advice)
工作流程:
- 接收到方法调用。
- 执行增强逻辑(如
@Before的通知)。 - 将调用转发给真实对象。
- 执行增强逻辑(如
@After的通知)。 - 返回结果。
B.机制一:JDK动态代理(基于接口)
条件:至少实现了一个接口
为什么至少实现了一个接口?因为JDK动态代理生成的类已经继承了Proxy类,由于单继承,所以它只能通过实现接口的方式实现代理
原理:利用了Java原生的API Proxy InvocationHandler
- 运行的时候动态生成一个代理类,这个类和真实对象是兄弟关系,实现同一个接口
- 通过实现InvocationHandler插入增强的逻辑
- 调用代理对象,走的增强逻辑
C.机制二:CGLIB动态代理(基于继承)
条件:无(没实现接口只能用它)
原理:利用字节码
- 运行的时候操作字节码,生成一个代理类,这个类和真实对象是父子关系
- 子类会重写目标中所有非final的方法
- 重写的方法中加入增强逻辑
为什么不能重写final方法?因为final方法不能重写
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 1. 执行前置增强 (如 @Before)
beforeAdvice();
try {
// 2. 调用真实目标对象的方法
result = method.invoke(targetObject, args);
// 3. 执行后置增强 (如 @AfterReturning)
afterReturningAdvice(result);
} catch (Exception e) {
// 4. 执行异常增强 (如 @AfterThrowing)
afterThrowingAdvice(e);
throw e;
} finally {
// 5. 执行最终增强 (如 @After)
afterFinallyAdvice();
}
D.总结
JDK动态代理限制在于只能代理接口定义的方法,CGLIB动态代理限制在于不能代理final类、方法。
Spring的默认:智能选择,有接口JDK动态代理,没有接口就CGLIB
SpringBoot2.x以上版本:默认CGLIB-->为了避免不必要的报错(比如注入实现类)
什么场景我们会强制使用CGLIB?
一、希望代理没在接口定义的方法
二、希望代理类就是目标类的类型@Autowired直接按照实现类类型注入
所以一般都用@resource按类型
如果使用JDK代理,生成的代理对象只实现了接口,并不是实现类类型,会导致注入失败。而CGLIB代理生成的代理对象是目标类的子类,可以同时匹配接口类型和具体类类型。
