IOC/AOP 概念
1、什么是 IOC?
IOC (控制反转),也称之:DI(依赖注入),是一种设计原则,也是一套控制体系。可以通俗的理解为:
通过一个“媒介”中转获取对象的方式(媒介,一般补称为“容器”)。便是 IOC。
下面设计一个演进过程,希望更利于理解:
- 这是直接获取对象
public class DemoCom{
DemoService demoService = new DemoServiceImpl();
String demoTitle = "demo";
}
这种方式获取的对象,1就是1,2就是2。(就是不会再变了)
- 通过“媒介”(内部是个黑盒)获取对象
//获取侧
public class DemoCom{
DemoService demoService = Media.getBean(DemoService.class);
String demoTitle = Media.getString("demoTtile");
}
这种方式获得的对象到底是什么?这就由“媒介”决定了。那,“媒介”的东西又是从哪来?比如这样:
//推入侧
Media.putBean(DemoService.class, ()-> new DemoServiceImpl());
Media.putString("demoTtile", "demo")
相关的对象,是由另外一侧推入。这一套控制体系,就称为:控制反转。
- 你觉得上面这个太土了?变成自动后,就时尚了:
上面的算是“原理”形态(也可称为手动形态),常见的应该是下面这样的(可称为自动形态):
//获取侧
@Component
public class DemoCom{
//自动注入
@Inject
DemoService demoService; //自动获取:Media.getBean(DemoService.class);
@Inject("${demoTitle}")
String demoTitle;
}
//推入侧
@Component
public class DemoService{ //自动推入:Media.putBean(DemoService.class, ()-> new DemoServiceImpl());
}
自动形态下的“媒介”,一般就称作“容器”了(显得,高大上些)。那,怎么的就能自动呢?一般是这样的:
- 应用启动时,容器会遍历一定范围的所有类(一般也称为:扫描)
- 找到带 "@Component" 注解的类,便会完成对象“推入”
- 然后,找到带 "@Inject" 注解的字段,便会“获取”对象,为字段赋值
- ...
2、什么是 AOP ?
AOP(面向切面编程),是一种编程范式。允许我们为对象“附加”额外能力。这种编程思想的重点有三个:
- 要 “能” 切开对象
- 怎么 “确定” 切口(或叫,切入点)
- 切开之后,“附加” 额外能力
下面,也是一套演进过程:
- 怎么样才能切开?基础是构建“代理”层(静态代理,或动态代理)
//原始类
public class DemoService{
public void saveUser(User user){
...
}
}
//代理类(用静态代理的方式演示)
public class DemoService$Proxy extends DemoService{
DemoService real;
public DemoService$Proxy(DemoService real){
this.real = real;
}
@Override
public void saveUser(User user){
//todo: 这算是切开了! //假装给它加个存储事务
TranUtil.tran(()->{
real.saveUser(user);
});
}
}
public class DemoCom{
//用代理类进行赋值
DemoService demoService = new DemoService$Proxy(new DemoService());
public void addUser(User user){
demoService.saveUser();
}
}
如果有个 “媒介” 偷偷把中间的活干掉了(即,容器提供自动化处理),代码可以美化成:
@Component //todo: 会自动构建代理层,提供 “能” 切开的基础
public class DemoService{
@Tran //todo: 这是“切口”标识(即注解)
public void saveUser(User user){
...
}
}
@Component
public class DemoCom{
@Inject
DemoService demoService;
public void addUser(User user){
demoService.saveUser();
}
}
- 现在,聊聊怎么确定切口
把具体的改成抽象的,并且体系化。就可以根据“规则”确定切口了。关于代理的更多内容可以看下,“动态代理的本质”。
public class DemoService$Proxy extends DemoService{
InvocationHandler handler;
Method saveUser1;
public DemoService$Proxy(InvocationHandler handler){
//...略
}
public void saveUser(User user){
handler.invoke(this, saveUser1, new Object[]{user});
}
}
public class InvocationHandler$Proxy implements InvocationHandler{
Object real;
public InvocationHandlerImpl(Object real){
this.real = real;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//todo: 所有的代理,最终汇到这里;就可以按一个规则确定切口了(比如某个注解)
if(method.isAnnotationPresent(DbTran.class)){
TranUtil.tran(()->{
method.invoke(real, args);
});
}else{
method.invoke(real, args);
}
}
}
public class DemoCom{
DemoService demoService = new DemoService$Proxy(new InvocationHandler$Proxy(new DemoService()));
public void addUser(User user){
demoService.saveUser();
}
}
把具体的规则,改成体系化的规则
public class InvocationHandler$Proxy implements InvocationHandler{
AppContext context;
Object real;
public InvocationHandlerImpl(AppContext context, Object real){
this.context = context;
this.real = real;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//内部代码就略了。可以参考 solon 的源码
context.methodGet(method).invokeByAspect(real, args);
}
}
有了体系化的规则支持后,我们就可以聊怎么切开了(即确定切口的方式):
- 比如按“表达式”切开:“@annotation(org.example.annotation.OperationAnno)”
- 比如按“注解”切开:"OperationAnno"(Solon 用的就是这个方案)
- 再或者别的方式
体系化之后,相关处理(“附加”额外能力)就具有框架特性了。详细内容看下,“切面与函数环绕拦截”
//定义一个“附加”额外能力的处理(就是个拦截处理)
public class TranInterceptor implements MethodInterceptor {
@Override
public Object doIntercept(Invocation inv) throws Throwable {
//加料了。。。为 @Tran 注解增加对应规则的处理
TranUtil.tran(()->{
inv.invoke();
});
}
}
//注册“切口”的处理能力
context.beanInterceptorAdd(DbTran.class, new TranInterceptor());