JFinal-aop

添加依赖

tio-boot 已经内并没有内置 jfinal-aop,需要自行添加依赖 jfinal-aop 源码:https://github.com/litongjava/jfinal-aop

概述

传统 AOP 实现需要引入大量繁杂而多余的概念,例如:Aspect、Advice、Joinpoint、Poincut、Introduction、Weaving、Around 等等,并且需要引入 IOC 容器并配合大量的 XML 或者 annotation 来进行组件装配。

传统 AOP 不但学习成本极高,开发效率极低,开发体验极差,而且还影响系统性能,尤其是在开发阶段造成项目启动缓慢,极大影响开发效率。

JFinal 采用极速化的 AOP 设计,专注 AOP 最核心的目标,将概念减少到极致,仅有三个概念:Interceptor、Before、Clear,并且无需引入 IOC 也无需使用啰嗦的 XML。

Aop 相关注解

  1. @AComponentScan: 用于指定 在初始化时要扫描的包。这个注解会查找标记有 @AComponent@AService@ARepository@AController 等注解的类,并注册为 Aop 容器中的 Bean。

  2. @AConfiguration: 表示该类是一个配置类,该类可以包含有 @ABean 注解的方法。jfinal 容器会服务器启动后,动时自动调用这些方法.

  3. @BeforeStartConfiguration:表示该类是一个配置类,该类可以包含有 @ABean 注解的方法。jfinal 容器会服务器启动前调用这些方法

  4. @ABean: 标记在方法上,该方法返回一个 Bean 对象,然后这个对象被 Aop 容器管理。通常在 @AConfiguration 注解的类中使用。

  5. @Initialization: 标记在方法上,该方法返回没有返回值,也不会添加到 bean 容器中,但是会在 Aop 容器初始化时执行该方法

  6. @AComponent: 基本的注解,标记一个类为组件。当使用基于注解的配置和类路径扫描时,这个注解的类会自动注册为 Spring Bean。

  7. @AController: 用于标记控制器组件,通常用在 MVC 模式的 Web 应用程序中。这个注解表明类的实例是一个控制器。

  8. @AService: 用于标记服务层组件,通常用于业务逻辑层。这个注解表明类的实例是一个“服务”,它可以包含业务逻辑,调用数据访问层等。

  9. @ARepository: 用于标记数据访问组件,即 DAO(Data Access Object)组件。这个注解表明类的实例是一个“仓库”,用于封装数据库访问和异常处理。

  10. @AHttpApi: 用于标记 Http 组件,例如用于 HttpClient 请求。

  11. @Inject: @AAutowired 类似,但它是来自 Java CDI(Contexts and Dependency Injection)的标准注解。用于依赖注入。

  12. @AAutowired: 用于自动注入依赖。它可以应用于字段、构造器、方法等,Spring 容器会在创建 Bean 时自动注入相应的依赖。

  13. @Clear: 用于清除 Aop 拦截器

  14. @Before: 这个注解与 AOP(面向切面编程)有关,用于标记一个方法在某操作之前执行。

  15. @AImport: 用于导入其他配置类。在一个配置类上使用 @AImport,可以将其他配置类中的 Bean 导入当前的配置类中。

Interceptor

1、基本用法

Interceptor 可以对方法进行拦截,并提供机会在方法的前后添加切面代码,实现 AOP 的核心目标。Interceptor 接口仅仅定义了一个方法 public void intercept(Invocation inv)。以下是简单示例:

public class DemoInterceptor implements Interceptor {
    public void intercept(Invocation inv) {
       System.out.println("Before method invoking");
       inv.invoke();
       System.out.println("After method invoking");
    }
}

以上代码中的 DemoInterceptor 将拦截目标方法,并且在目标方法调用前后向控制台输出文本。inv.invoke() 这一行代码是对目标方法的调用,在这一行代码的前后插入切面代码可以很方便地实现 AOP。

注意:必须调用 inv.invoke() 方法,才能将当前调用传递到后续的 Interceptor 与 Action。

常见错误:目前为止仍有很多同学忘了调用 inv.invoke() 方法,造成 不会被执行。在此再次强调一次,一定要调用一次 inv.invoke().

Invocation 作为 Interceptor 接口 intercept 方法中的唯一参数,提供了很多便利的方法在拦截器中使用。以下为 Invocation 中的方法:

方法描述
void invoke()传递本次调用,调用剩下的拦截器与目标方法
<T> getTarget()获取被拦截方法所属的对象
Method getMethod()获取被拦截方法的 Method 对象
String getMethodName()获取被拦截方法的方法名
Object[] agetArgs()获取被拦截方法的所有参数值
setArg(int, Object)获取被拦截方法指定序号的参数值
<T> getReturnValue()获取被拦截方法的返回值
void setArg(int)设置被拦截方法指定序号的参数值
void setReturnValue(Object)设置被拦截方法的返回值

2、 全局共享,注意线程安全问题

Interceptor 是全局共享的,所以如果要在其中使用属性需要保证其属性是线程安全的,如下代码将是错误的:

public class MyInterceptor implements Interceptor {

   private int value = 123;

   public void intercept(Invocation inv) {
       // 多线程将会并发访问 value 值,造成错乱
       value++;

       inv.invoke();
   }
}

如上代码所示,其中的 value 属性将会被多线程访问到,从而引发线程安全问题。

Before

@Before 注解

Before 注解用来对拦截器进行配置,该注解可配置 Class、Method 级别的拦截器,以下是代码示例:

// 配置一个Class级别的拦截器,她将拦截本类中的所有方法
@Before(AaaInter.class)
@RequestPath("/before")
public class BlogController{

  // 配置多个Method级别的拦截器,仅拦截本方法
  @Before({BbbInter.class, CccInter.class})
  public void index() {
  }

  // 未配置Method级别拦截器,但会被Class级别拦截器AaaInter所拦截
  public void show() {
  }
}

如上代码所示,Before 可以将拦截器配置为 Class 级别与 Method 级别,前者将拦截本类中所有方法,后者仅拦截本方法。此外 Before 可以同时配置多个拦截器,只需用在大括号内用逗号将多个拦截器进行分隔即可。

除了 Class 与 Method 级别的拦截器以外,JFinal 还支持全局拦截器以及 Routes 拦截器,全局拦截器分为控制层全局拦截器与业务层全局拦截器,前者拦截控制 层所有 Action 方法,后者拦截业务层所有方法。

Aop 拦击器@Before

import com.litongjava.jfinal.aop.Interceptor;
import com.litongjava.jfinal.aop.Invocation;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class IndexInteceptor implements Interceptor {

  @Override
  public void intercept(Invocation inv) {
    log.info("1before");
    inv.invoke();
    log.info("after");
  }

}

import java.util.Map;

import com.litongjava.jfinal.aop.Aop;
import com.litongjava.jfinal.aop.Before;
import com.litongjava.tio.http.server.annotation.RequestPath;
import com.litongjava.tio.web.hello.service.IndexService;
@AController
@RequestPath("/")
public class IndexController {
  @RequestPath()
  @Before(IndexInteceptor.class)
    public Map<String, String> index() {
    return Aop.get(IndexService.class).index();
  }
}

IndexInteceptor 是一个拦截器类,实现了 Interceptor 接口。它定义了 intercept 方法,该方法在被拦截的方法执行前后添加了日志记录。通过调用 inv.invoke(),它允许继续执行链中的下一个拦截器或目标方法。

IndexController 类中,@Before(IndexInteceptor.class) 注解被应用于 index 方法。这表示当调用 index 方法时,IndexInteceptor 将被触发,执行其 intercept 方法。这允许在 index 方法执行之前和之后执行额外的逻辑,例如日志记录、验证等。

Clear

拦截器从上到下依次分为 Global、Routes、Class、Method 四个层次,Clear 用于清除自身所处层次以上层的拦截器。

Clear 声明在 Method 层时将针对 Global、Routes、Class 进行清除。Clear 声明在 Class 层时将针对 Global、Routes 进行清除。Clear 注解携带参数时清除目标层中指定的拦截器。

Clear 用法记忆技巧:

  • 一共有 Global、Routes、Class、Method 四层拦截器

  • 清除只针对 Clear 本身所处层的向上所有层,本层与下层不清除

  • 不带参数时清除所有拦截器,带参时清除参数指定的拦截器

在某些应用场景之下,需要移除 Global 或 Class 拦截器。例如某个后台管理系统,配置了一个全局的权限拦截器,但是其登录 action 就必须清除掉她,否则无法完成登录操作,以下是代码示例:

// login方法需要移除该权限拦截器才能正常登录
@Before(AuthInterceptor.class)
@RequestPath("/user")
public class UserController {
    // AuthInterceptor 已被Clear清除掉,不会被其拦截
    @Clear
    public void login() {
    }

    // 此方法将被AuthInterceptor拦截
    public void show() {
    }
}

Clear 注解带有参数时,能清除指定的拦截器,以下是一个更加全面的示例:

@Before(AAA.class)
public class UserController {
  @Clear
  @Before(BBB.class)
  public void login() {
     // Global、Class级别的拦截器将被清除,但本方法上声明的BBB不受影响
  }

  @Clear({AAA.class, CCC.class})// 清除指定的拦截器AAA与CCC
  @Before(CCC.class)
  public void show() {
     // 虽然Clear注解中指定清除CCC,但她无法被清除,因为清除操作只针对于本层以上的各层
  }
}

上面的清除都用在了 method 上,还可以将其用于 class 之上,例如:

@Clear(AAA.class)
public class UserController {
   public void index() {
      ...
   }
}

如上所示,@Clear(AAA.class) 将清除上层也就是 Global、Route 这两层中配置的 AAA.java 这个拦截器。

Inject 依赖注入

Inject 注解

使用 @Inject 注解可以向 属性中 中注入依赖对象

public class AccountController {

   @Inject
   AccountService service;    // 此处会注入依赖对象

   public void index() {
       service.justDoIt();    // 调用被注入对象的方法
   }
}

@Inject 还可以用于拦截器的属性注入,例如:

public class MyInterceptor implements Interceptor {

    @Inject
    Service service;    // 此处会注入依赖对象

    public void intercept(Invocation inv) {
        service.justDoIt();    // 调用被注入对象的方法
        inv.invoke();
    }
}

特别注意:使用 Inject 注入的前提是使用 @Inject 注解的类的对象的创建是由 jfinal aop 接管的,这样 jfinal aop 才有机会对其进行注入。例如 Controller、Interceptor、的创建是 jfinal aop 接管的,所以这三种组件中可以使用 @Inject 注入。

此外:注入动作可以向下传递。例如在 Controller 中使用 @Inject 注入一个 AaaService,那么在 AaaService 中可以使用 @Inject 注入一个 BbbService,如此可以一直向下传递进行注入.

如果需要创建的对象并不是 jfinal aop 接管的,那么可以使用 Aop.get(...) 方法进行依赖对象的创建以及注入,例如:

public class MyKit {

   static Service service = Aop.get(Service.class);

   public void doIt() {
      service.justDoIt();
   }
}

由于 MyKit 的创建并不是 jfinal aop 接管的,所以不能使用 @Inject 进行依赖注入。 而 Controller、Interceptor 的创建和组装是由 jfinal aop 接管的,所以可以使用 @Inject 注入依赖。

有了 Aop.get(...) 就可以在任何地方创建对象并且对创建的对象进行注入。此外还可以使用 Aop.inject(...) 仅仅向对象注入依赖但不创建对象。

@Inject 注解还支持指定注入的实现类,例如下面的代码,将为 Service 注入 MyService 对象:

@Inject(MyService.class)
Service service;

添加映射来指定被注入的类型

当 @Inject(...) 注解不指定被注入的类型时,还可以通过 AopManager.me().addMapping(...) 事先添加映射来指定被注入的类型,例如:

AopManager.me().addMapping(Service.class, MyService.class);

通过上面的映射,下面的代码将会为 Service 注入 MyService

public class IndexController {

    @Inject
    Service service;

    public void index() {
        service.justDoIt();
    }
}