零、IOC/AOP?
此文通过演进的方式(基本概念可从网上了解),让大家对这两个概念有更具体的感受。
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;
}
怎么的就能自动了?一般是这样的:(具体要看下,“应用生命周期”)
- 应用启动时,找到所有带 "@Component" 注释的类,并生成实例
- 然后,找到带 "@Inject" 注释的字段
- 根据约定,再到 “Media” 里找对应的资源,为字段赋值
- ...
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();
});
}
}