JFinal-aop
添加依赖
jfinal-aop 源码:https://github.com/litongjava/jfinal-aop
<dependency>
<groupId>com.litongjava</groupId>
<artifactId>jfinal-aop</artifactId>
<version>${jfinal-aop.version}</version>
</dependency>
概述
传统 AOP 实现需要引入大量繁杂而多余的概念,例如:Aspect、Advice、Joinpoint、Poincut、Introduction、Weaving、Around 等等,并且需要引入 IOC 容器并配合大量的 XML 或者 annotation 来进行组件装配。
传统 AOP 不但学习成本极高,开发效率极低,开发体验极差,而且还影响系统性能,尤其是在开发阶段造成项目启动缓慢,极大影响开发效率。
JFinal 采用极速化的 AOP 设计,专注 AOP 最核心的目标,将概念减少到极致,仅有三个概念:Interceptor、Before、Clear,并且无需引入 IOC 也无需使用啰嗦的 XML。
Aop 相关注解
@AComponentScan: 用于指定 在初始化时要扫描的包。这个注解会查找标记有
@AComponent
、@AService
、@ARepository
、@AController
等注解的类,并注册为 Aop 容器中的 Bean。@AConfiguration: 表示该类是一个配置类,该类可以包含有
@ABean
注解的方法。jfinal 容器会服务器启动后,动时自动调用这些方法.@BeforeStartConfiguration:表示该类是一个配置类,该类可以包含有
@ABean
注解的方法。jfinal 容器会服务器启动前调用这些方法@ABean: 标记在方法上,该方法返回一个 Bean 对象,然后这个对象被 Aop 容器管理。通常在
@AConfiguration
注解的类中使用。@Initialization: 标记在方法上,该方法返回没有返回值,也不会添加到 bean 容器中,但是会在 Aop 容器初始化时执行该方法
@AComponent: 基本的注解,标记一个类为组件。当使用基于注解的配置和类路径扫描时,这个注解的类会自动注册为 Spring Bean。
@AController: 用于标记控制器组件,通常用在 MVC 模式的 Web 应用程序中。这个注解表明类的实例是一个控制器。
@AService: 用于标记服务层组件,通常用于业务逻辑层。这个注解表明类的实例是一个“服务”,它可以包含业务逻辑,调用数据访问层等。
@ARepository: 用于标记数据访问组件,即 DAO(Data Access Object)组件。这个注解表明类的实例是一个“仓库”,用于封装数据库访问和异常处理。
@AHttpApi: 用于标记 Http 组件,例如用于 HttpClient 请求。
@Inject:
@AAutowired
类似,但它是来自 Java CDI(Contexts and Dependency Injection)的标准注解。用于依赖注入。@AAutowired: 用于自动注入依赖。它可以应用于字段、构造器、方法等,Spring 容器会在创建 Bean 时自动注入相应的依赖。
@Clear: 用于清除 Aop 拦截器
@Before: 这个注解与 AOP(面向切面编程)有关,用于标记一个方法在某操作之前执行。
@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.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();
}
}