Solon

零、IOC/AOP?

</> markdown

此文通过演进的方式(基本概念可从网上了解),让大家对这两个概念有更具体的感受。

1、什么是 IOC?

IOC (控制反转),也称之:DI(依赖注入)。下面,借用网上一句描述:

你要的资源,是通过一个中介获得的(这个中介,一般称为“容器”)。这便是 IOC。

下面设计一个演进过程,希望更利于理解:

1)这是直接获得资源

public class DemoCom{
    DemoService demoService = new DemoServiceImpl();
    String demoTitle        = "demo";
}

2)通过中介获得资源。没错,这就是 IOC/DI (Solon 也支持这种风格)

public class DemoCom{
    //手动注入
    DemoService demoService = Media.getBean(DemoService.class);
    String demoTitle        = Media.getString("demoTtile");
}

获得的资源到底是什么?这就由中介决定了(算是被反向控制了)。那,中介的东西又是从哪来?比如这样:

Media.putBean(DemoService.class, ()-> new DemoServiceImpl());
Media.putString("demoTtile", "demo")

3)你觉得上面这个太土了?变成自动的,就时尚了:(Solon 主要用的就是这种风格)

常见的应该是下面这样的。但上面却是“原理”形态,在没有反射与注解的环镜里,应该是一种常见玩法。

@Component
public class DemoCom{
    //自动注入
    @Inject
    DemoService demoService;
    @Inject("${demoTitle}")
    String demoTitle;
}

怎么的就能自动了?一般是这样的:(具体要看下,“应用生命周期”)

  1. 应用启动时,找到所有带 "@Component" 注释的类,并生成实例
  2. 然后,找到带 "@Inject" 注释的字段
  3. 根据约定,再到 “Media” 里找对应的资源,为字段赋值
  4. ...

2、什么是 AOP ?

AOP(面向切面编程)。这种编程思想的重点有三个:

  • 要能切开
  • 怎么确定切口(或叫,切入点)
  • 切了之后,加点什么料进去

下面,也是一套演进过程:

1)怎么样才能切开?基础是代理

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
public class DemoService{
    @Tran
    public void saveUser(User user){
       ...
    }
}

@Component
public class DemoCom{
    @Inject
    DemoService demoService;
   
    public void addUser(User user){
        demoService.saveUser();
    }
}

2)现在,聊聊怎么确定切口

把具体的,改成抽象的,并且体系化。就可以根据“规则”确定切口了。关于代理的更多内容可以看下,“动态代理的本质”。

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 用的就是这个方案
  • 再或者别的方式

3)切了之后,加点料进去

体系化之后,相关处理都会具有框架特性了。详细内容看下,“切面与函数环绕拦截

//定义一个加料处理(就是个拦截处理)
public class TranInterceptor implements Interceptor {
    @Override
    public Object doIntercept(Invocation inv) throws Throwable {
        //加料了。。。为 @Tran 注解增加对应规则的处理
        TranUtil.tran(()->{
            inv.invoke();
        });
    }
}