Solon v3.0.6

IOC/AOP 概念

</> markdown

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());
}

自动形态下的“媒介”,一般就称作“容器”了(显得,高大上些)。那,怎么的就能自动呢?一般是这样的:

  1. 应用启动时,容器会遍历一定范围的所有类(一般也称为:扫描)
  2. 找到带 "@Component" 注解的类,便会完成对象“推入”
  3. 然后,找到带 "@Inject" 注解的字段,便会“获取”对象,为字段赋值
  4. ...

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());