|
2 | 2 |
|
3 | 3 | tio-boot 已经内置了 jfinal-aop 依赖
|
4 | 4 | jfinal-aop 源码:https://github.com/litongjava/jfinal-aop
|
5 |
| -jfinal-aop 文档:https://litongjava.github.io/jfinal-doc/zh/4%20AOP/4.1%20%E6%A6%82%E8%BF%B0.html |
6 | 5 |
|
7 |
| -### 7.1.Aop.get |
| 6 | +## 概述 |
8 | 7 |
|
9 |
| -```java |
10 |
| -import java.util.HashMap; |
11 |
| -import java.util.Map; |
| 8 | +传统 AOP 实现需要引入大量繁杂而多余的概念,例如:Aspect、Advice、Joinpoint、Poincut、Introduction、Weaving、Around 等等,并且需要引入 IOC 容器并配合大量的 XML 或者 annotation 来进行组件装配。 |
12 | 9 |
|
13 |
| -public class IndexService { |
| 10 | +传统 AOP 不但学习成本极高,开发效率极低,开发体验极差,而且还影响系统性能,尤其是在开发阶段造成项目启动缓慢,极大影响开发效率。 |
14 | 11 |
|
15 |
| - public Map<String, String> index() { |
16 |
| - Map<String, String> ret = new HashMap<>(); |
17 |
| - ret.put("data", "Hello 4"); |
18 |
| - return ret; |
19 |
| -} |
20 |
| -} |
| 12 | +JFinal 采用极速化的 AOP 设计,专注 AOP 最核心的目标,将概念减少到极致,仅有三个概念:Interceptor、Before、Clear,并且无需引入 IOC 也无需使用啰嗦的 XML。 |
21 | 13 |
|
22 |
| -``` |
| 14 | +## Aop 相关注解 |
23 | 15 |
|
24 |
| -``` |
| 16 | +1. **@AComponentScan**: 用于指定 在初始化时要扫描的包。这个注解会查找标记有 `@AComponent`、`@AService`、`@ARepository`、`@AController` 等注解的类,并注册为 Aop 容器中的 Bean。 |
25 | 17 |
|
26 |
| -import java.util.Map; |
| 18 | +2. **@AConfiguration**: 表示该类是一个配置类,该类可以包含有 `@ABean` 注解的方法。jfinal 容器会服务器启动后,动时自动调用这些方法. |
27 | 19 |
|
28 |
| -import com.litongjava.jfinal.aop.Aop; |
29 |
| -import com.litongjava.tio.http.server.annotation.RequestPath; |
30 |
| -import com.litongjava.tio.web.hello.service.IndexService; |
31 |
| -@AController |
32 |
| -@RequestPath("/") |
33 |
| -public class IndexController { |
34 |
| -@RequestPath() |
35 |
| -public Map<String,String> index() { |
36 |
| -return Aop.get(IndexService.class).index(); |
37 |
| -} |
38 |
| -} |
| 20 | +3. **@BeforeStartConfiguration**:表示该类是一个配置类,该类可以包含有 `@ABean` 注解的方法。jfinal 容器会服务器启动前调用这些方法 |
| 21 | + |
| 22 | +4. **@ABean**: 标记在方法上,该方法返回一个 Bean 对象,然后这个对象被 Aop 容器管理。通常在 `@AConfiguration` 注解的类中使用。 |
| 23 | + |
| 24 | +5. **@Initialization**: 标记在方法上,该方法返回没有返回值,也不会添加到 bean 容器中,但是会在 Aop 容器初始化时执行该方法 |
| 25 | + |
| 26 | +6. **@AComponent**: 基本的注解,标记一个类为组件。当使用基于注解的配置和类路径扫描时,这个注解的类会自动注册为 Spring Bean。 |
| 27 | + |
| 28 | +7. **@AController**: 用于标记控制器组件,通常用在 MVC 模式的 Web 应用程序中。这个注解表明类的实例是一个控制器。 |
| 29 | + |
| 30 | +8. **@AService**: 用于标记服务层组件,通常用于业务逻辑层。这个注解表明类的实例是一个“服务”,它可以包含业务逻辑,调用数据访问层等。 |
| 31 | + |
| 32 | +9. **@ARepository**: 用于标记数据访问组件,即 DAO(Data Access Object)组件。这个注解表明类的实例是一个“仓库”,用于封装数据库访问和异常处理。 |
| 33 | + |
| 34 | +10. **@AHttpApi**: 用于标记 Http 组件,例如用于 HttpClient 请求。 |
| 35 | + |
| 36 | +11. **@Inject**: `@AAutowired` 类似,但它是来自 Java CDI(Contexts and Dependency Injection)的标准注解。用于依赖注入。 |
| 37 | + |
| 38 | +12. **@AAutowired**: 用于自动注入依赖。它可以应用于字段、构造器、方法等,Spring 容器会在创建 Bean 时自动注入相应的依赖。 |
| 39 | + |
| 40 | +13. **@Clear**: 用于清除 Aop 拦截器 |
| 41 | + |
| 42 | +14. **@Before**: 这个注解与 AOP(面向切面编程)有关,用于标记一个方法在某操作之前执行。 |
| 43 | + |
| 44 | +15. **@AImport**: 用于导入其他配置类。在一个配置类上使用 `@AImport`,可以将其他配置类中的 Bean 导入当前的配置类中。 |
| 45 | + |
| 46 | +## Interceptor |
| 47 | + |
| 48 | +### 1、基本用法 |
| 49 | + |
| 50 | +Interceptor 可以对方法进行拦截,并提供机会在方法的前后添加切面代码,实现 AOP 的核心目标。Interceptor 接口仅仅定义了一个方法 public void intercept(Invocation inv)。以下是简单示例: |
39 | 51 |
|
40 | 52 | ```
|
| 53 | +public class DemoInterceptor implements Interceptor { |
| 54 | + public void intercept(Invocation inv) { |
| 55 | + System.out.println("Before method invoking"); |
| 56 | + inv.invoke(); |
| 57 | + System.out.println("After method invoking"); |
| 58 | + } |
| 59 | +} |
| 60 | +``` |
| 61 | + |
| 62 | +以上代码中的 DemoInterceptor 将拦截目标方法,并且在目标方法调用前后向控制台输出文本。inv.invoke() 这一行代码是对目标方法的调用,在这一行代码的前后插入切面代码可以很方便地实现 AOP。 |
41 | 63 |
|
42 |
| -执行后返回的数据是 |
| 64 | +注意:必须调用 inv.invoke() 方法,才能将当前调用传递到后续的 Interceptor 与 Action。 |
| 65 | + |
| 66 | +常见错误:目前为止仍有很多同学忘了调用 inv.invoke() 方法,造成 不会被执行。在此再次强调一次,一定要调用一次 inv.invoke(). |
| 67 | + |
| 68 | +Invocation 作为 Interceptor 接口 intercept 方法中的唯一参数,提供了很多便利的方法在拦截器中使用。以下为 Invocation 中的方法: |
| 69 | + |
| 70 | +| 方法 | 描述 | |
| 71 | +| --------------------------- | ---------------------------------------- | |
| 72 | +| void invoke() | 传递本次调用,调用剩下的拦截器与目标方法 | |
| 73 | +| <T> getTarget() | 获取被拦截方法所属的对象 | |
| 74 | +| Method getMethod() | 获取被拦截方法的 Method 对象 | |
| 75 | +| String getMethodName() | 获取被拦截方法的方法名 | |
| 76 | +| Object[] agetArgs() | 获取被拦截方法的所有参数值 | |
| 77 | +| setArg(int, Object) | 获取被拦截方法指定序号的参数值 | |
| 78 | +| <T> getReturnValue() | 获取被拦截方法的返回值 | |
| 79 | +| void setArg(int) | 设置被拦截方法指定序号的参数值 | |
| 80 | +| void setReturnValue(Object) | 设置被拦截方法的返回值 | |
| 81 | + |
| 82 | +### 2、 全局共享,注意线程安全问题 |
| 83 | + |
| 84 | +Interceptor 是全局共享的,所以如果要在其中使用属性需要保证其属性是线程安全的,如下代码将是错误的: |
43 | 85 |
|
44 | 86 | ```
|
| 87 | +public class MyInterceptor implements Interceptor { |
| 88 | +
|
| 89 | + private int value = 123; |
45 | 90 |
|
46 |
| -{"data":"Hello 4"} |
| 91 | + public void intercept(Invocation inv) { |
| 92 | + // 多线程将会并发访问 value 值,造成错乱 |
| 93 | + value++; |
47 | 94 |
|
| 95 | + inv.invoke(); |
| 96 | + } |
| 97 | +} |
48 | 98 | ```
|
49 | 99 |
|
50 |
| -这两个类构成了一个简单的 MVC (Model-View-Controller) 结构。 |
| 100 | +如上代码所示,其中的 value 属性将会被多线程访问到,从而引发线程安全问题。 |
| 101 | + |
| 102 | +## Before |
51 | 103 |
|
52 |
| -1. `IndexService` 类: |
| 104 | +Before 注解用来对拦截器进行配置,该注解可配置 Class、Method 级别的拦截器,以下是代码示例: |
53 | 105 |
|
54 |
| - - 这是一个服务类,包含一个 `index` 方法,用于创建并返回一个 `Map<String, String>` 类型的数据。在这个方法中,它向 `Map` 中添加了一个键值对 `"data": "Hello 4"`。 |
| 106 | +``` |
| 107 | +// 配置一个Class级别的拦截器,她将拦截本类中的所有方法 |
| 108 | +@Before(AaaInter.class) |
| 109 | +@RequestPath("/before") |
| 110 | +public class BlogController{ |
| 111 | +
|
| 112 | + // 配置多个Method级别的拦截器,仅拦截本方法 |
| 113 | + @Before({BbbInter.class, CccInter.class}) |
| 114 | + public void index() { |
| 115 | + } |
55 | 116 |
|
56 |
| -2. `IndexController` 类: |
57 |
| - - 这是一个控制器类,标记了 `@RequestPath("/")`,表明它处理根路径(`/`)的 HTTP 请求。 |
58 |
| - - 其中的 `index` 方法通过 `Aop.get(IndexService.class).index()` 调用 `IndexService` 的 `index` 方法。 |
| 117 | + // 未配置Method级别拦截器,但会被Class级别拦截器AaaInter所拦截 |
| 118 | + public void show() { |
| 119 | + } |
| 120 | +} |
| 121 | +``` |
59 | 122 |
|
60 |
| -`Aop.get` 方法的作用是从 AOP(面向切面编程)容器中获取 `IndexService` 类的实例。这意味着 `IndexService` 类可能被作为一个单例来管理,它的实例化与生命周期可能由 AOP 框架来控制,而非手动创建。这种做法允许 `IndexService` 拥有如依赖注入、拦截器等 AOP 功能。 |
| 123 | +如上代码所示,Before 可以将拦截器配置为 Class 级别与 Method 级别,前者将拦截本类中所有方法,后者仅拦截本方法。此外 Before 可以同时配置多个拦截器,只需用在大括号内用逗号将多个拦截器进行分隔即可。 |
61 | 124 |
|
62 |
| -当 Aop.get 方法时,如果荣器中不存在对于的对象,Aop 容器会创建后在返回 |
63 |
| -当 `IndexController` 的 `index` 方法被调用时,它会返回 `IndexService.index` 方法生成的 `Map`,即 `{"data": "Hello 4"}`。 |
| 125 | +除了 Class 与 Method 级别的拦截器以外,JFinal 还支持全局拦截器以及 Routes 拦截器,全局拦截器分为控制层全局拦截器与业务层全局拦截器,前者拦截控制 层所有 Action 方法,后者拦截业务层所有方法。 |
64 | 126 |
|
65 | 127 | ### 7.2.Aop 拦击器@Before
|
66 | 128 |
|
@@ -109,78 +171,147 @@ public class IndexController {
|
109 | 171 |
|
110 | 172 | 在 `IndexController` 类中,`@Before(IndexInteceptor.class)` 注解被应用于 `index` 方法。这表示当调用 `index` 方法时,`IndexInteceptor` 将被触发,执行其 `intercept` 方法。这允许在 `index` 方法执行之前和之后执行额外的逻辑,例如日志记录、验证等。
|
111 | 173 |
|
112 |
| -如果使用了 Hotswap-classloader 需要在启动类中添加 SwapClassPrefix,添加之后才可以支持 切面代理类的 热加载,否则会出现异常 |
| 174 | +## Clear |
113 | 175 |
|
114 |
| -``` |
115 |
| -
|
116 |
| -HotSwapResolver.addHotSwapClassPrefix("com.litongjava.jfinal"); |
| 176 | +拦截器从上到下依次分为 Global、Routes、Class、Method 四个层次,Clear 用于清除自身所处层次以上层的拦截器。 |
117 | 177 |
|
118 |
| -``` |
| 178 | +Clear 声明在 Method 层时将针对 Global、Routes、Class 进行清除。Clear 声明在 Class 层时将针对 Global、Routes 进行清除。Clear 注解携带参数时清除目标层中指定的拦截器。 |
119 | 179 |
|
120 |
| -### 7.3.Aop 相关注解 |
| 180 | +Clear 用法记忆技巧: |
121 | 181 |
|
122 |
| -1. **@AComponentScan**: 用于指定 在初始化时要扫描的包。这个注解会查找标记有 `@AComponent`、`@AService`、`@ARepository`、`@AController` 等注解的类,并注册为 Aop 容器中的 Bean。 |
| 182 | +- 一共有 Global、Routes、Class、Method 四层拦截器 |
123 | 183 |
|
124 |
| -2. **@AConfiguration**: 表示该类是一个配置类,该类可以包含有 `@ABean` 注解的方法。jfinal 容器会服务器启动后,动时自动调用这些方法. |
| 184 | +- 清除只针对 Clear 本身所处层的向上所有层,本层与下层不清除 |
125 | 185 |
|
126 |
| -3. **@BeforeStartConfiguration**:表示该类是一个配置类,该类可以包含有 `@ABean` 注解的方法。jfinal 容器会服务器启动前调用这些方法 |
| 186 | +- 不带参数时清除所有拦截器,带参时清除参数指定的拦截器 |
127 | 187 |
|
128 |
| -4. **@ABean**: 标记在方法上,该方法返回一个 Bean 对象,然后这个对象被 Aop 容器管理。通常在 `@AConfiguration` 注解的类中使用。 |
| 188 | +在某些应用场景之下,需要移除 Global 或 Class 拦截器。例如某个后台管理系统,配置了一个全局的权限拦截器,但是其登录 action 就必须清除掉她,否则无法完成登录操作,以下是代码示例: |
129 | 189 |
|
130 |
| -5. **@Initialization**: 标记在方法上,该方法返回没有返回值,也不会添加到 bean 容器中,但是会在 Aop 容器初始化时执行该方法 |
| 190 | +``` |
| 191 | +// login方法需要移除该权限拦截器才能正常登录 |
| 192 | +@Before(AuthInterceptor.class) |
| 193 | +@RequestPath("/user") |
| 194 | +public class UserController { |
| 195 | + // AuthInterceptor 已被Clear清除掉,不会被其拦截 |
| 196 | + @Clear |
| 197 | + public void login() { |
| 198 | + } |
| 199 | +
|
| 200 | + // 此方法将被AuthInterceptor拦截 |
| 201 | + public void show() { |
| 202 | + } |
| 203 | +} |
| 204 | +``` |
131 | 205 |
|
132 |
| -6. **@AComponent**: 基本的注解,标记一个类为组件。当使用基于注解的配置和类路径扫描时,这个注解的类会自动注册为 Spring Bean。 |
| 206 | +Clear 注解带有参数时,能清除指定的拦截器,以下是一个更加全面的示例: |
133 | 207 |
|
134 |
| -7. **@AController**: 用于标记控制器组件,通常用在 MVC 模式的 Web 应用程序中。这个注解表明类的实例是一个控制器。 |
| 208 | +``` |
| 209 | +@Before(AAA.class) |
| 210 | +public class UserController { |
| 211 | + @Clear |
| 212 | + @Before(BBB.class) |
| 213 | + public void login() { |
| 214 | + // Global、Class级别的拦截器将被清除,但本方法上声明的BBB不受影响 |
| 215 | + } |
135 | 216 |
|
136 |
| -8. **@AService**: 用于标记服务层组件,通常用于业务逻辑层。这个注解表明类的实例是一个“服务”,它可以包含业务逻辑,调用数据访问层等。 |
| 217 | + @Clear({AAA.class, CCC.class})// 清除指定的拦截器AAA与CCC |
| 218 | + @Before(CCC.class) |
| 219 | + public void show() { |
| 220 | + // 虽然Clear注解中指定清除CCC,但她无法被清除,因为清除操作只针对于本层以上的各层 |
| 221 | + } |
| 222 | +} |
| 223 | +``` |
137 | 224 |
|
138 |
| -9. **@ARepository**: 用于标记数据访问组件,即 DAO(Data Access Object)组件。这个注解表明类的实例是一个“仓库”,用于封装数据库访问和异常处理。 |
| 225 | +上面的清除都用在了 method 上,还可以将其用于 class 之上,例如: |
139 | 226 |
|
140 |
| -10. **@AHttpApi**: 用于标记 Http 组件,例如用于 HttpClient 请求。 |
| 227 | +``` |
| 228 | +@Clear(AAA.class) |
| 229 | +public class UserController { |
| 230 | + public void index() { |
| 231 | + ... |
| 232 | + } |
| 233 | +} |
| 234 | +``` |
141 | 235 |
|
142 |
| -11. **@Inject**: `@AAutowired` 类似,但它是来自 Java CDI(Contexts and Dependency Injection)的标准注解。用于依赖注入。 |
| 236 | +如上所示,@Clear(AAA.class) 将清除上层也就是 Global、Route 这两层中配置的 AAA.java 这个拦截器。 |
143 | 237 |
|
144 |
| -12. **@AAutowired**: 用于自动注入依赖。它可以应用于字段、构造器、方法等,Spring 容器会在创建 Bean 时自动注入相应的依赖。 |
| 238 | +## Inject 依赖注入 |
145 | 239 |
|
146 |
| -13. **@Clear**: 用于清除 Aop 拦截器 |
| 240 | +使用 @Inject 注解可以向 属性中 中注入依赖对象 |
147 | 241 |
|
148 |
| -14. **@Before**: 这个注解与 AOP(面向切面编程)有关,用于标记一个方法在某操作之前执行。 |
| 242 | +``` |
| 243 | +public class AccountController { |
149 | 244 |
|
150 |
| -15. **@AImport**: 用于导入其他配置类。在一个配置类上使用 `@AImport`,可以将其他配置类中的 Bean 导入当前的配置类中。 |
| 245 | + @Inject |
| 246 | + AccountService service; // 此处会注入依赖对象 |
151 | 247 |
|
152 |
| -### 7.4.Aop 其他方法 |
| 248 | + public void index() { |
| 249 | + service.justDoIt(); // 调用被注入对象的方法 |
| 250 | + } |
| 251 | +} |
| 252 | +``` |
153 | 253 |
|
154 |
| -获取 Aop 容器中的所有 bean |
| 254 | +@Inject 还可以用于拦截器的属性注入,例如: |
155 | 255 |
|
156 | 256 | ```
|
| 257 | +public class MyInterceptor implements Interceptor { |
157 | 258 |
|
158 |
| -String[] beans = Aop.beans(); |
| 259 | + @Inject |
| 260 | + Service service; // 此处会注入依赖对象 |
159 | 261 |
|
| 262 | + public void intercept(Invocation inv) { |
| 263 | + service.justDoIt(); // 调用被注入对象的方法 |
| 264 | + inv.invoke(); |
| 265 | + } |
| 266 | +} |
160 | 267 | ```
|
161 | 268 |
|
162 |
| -添加一个类到 Bean 容器中 |
| 269 | +特别注意:使用 Inject 注入的前提是使用 @Inject 注解的类的对象的创建是由 jfinal aop 接管的,这样 jfinal aop 才有机会对其进行注入。例如 Controller、Interceptor、的创建是 jfinal aop 接管的,所以这三种组件中可以使用 @Inject 注入。 |
| 270 | + |
| 271 | +此外:注入动作可以向下传递。例如在 Controller 中使用 @Inject 注入一个 AaaService,那么在 AaaService 中可以使用 @Inject 注入一个 BbbService,如此可以一直向下传递进行注入. |
| 272 | + |
| 273 | +如果需要创建的对象并不是 jfinal aop 接管的,那么可以使用 Aop.get(...) 方法进行依赖对象的创建以及注入,例如: |
163 | 274 |
|
164 | 275 | ```
|
| 276 | +public class MyKit { |
165 | 277 |
|
166 |
| -AopManager.me().addSingletonObject(bean); |
| 278 | + static Service service = Aop.get(Service.class); |
167 | 279 |
|
| 280 | + public void doIt() { |
| 281 | + service.justDoIt(); |
| 282 | + } |
| 283 | +} |
168 | 284 | ```
|
169 | 285 |
|
170 |
| -添加一个实现带有接口的实现类到 Bean 容器中 |
| 286 | +由于 MyKit 的创建并不是 jfinal aop 接管的,所以不能使用 @Inject 进行依赖注入。 而 Controller、Interceptor 的创建和组装是由 jfinal aop 接管的,所以可以使用 @Inject 注入依赖。 |
171 | 287 |
|
| 288 | +有了 Aop.get(...) 就可以在任何地方创建对象并且对创建的对象进行注入。此外还可以使用 Aop.inject(...) 仅仅向对象注入依赖但不创建对象。 |
| 289 | + |
| 290 | +@Inject 注解还支持指定注入的实现类,例如下面的代码,将为 Service 注入 MyService 对象: |
| 291 | + |
| 292 | +``` |
| 293 | +@Inject(MyService.class) |
| 294 | +Service service; |
172 | 295 | ```
|
173 | 296 |
|
174 |
| -AopManager.me().addMapping(SharedPreferences.class, sharedPreferences.getClass()); |
175 |
| -AopManager.me().addSingletonObject(sharedPreferences); |
| 297 | +### 添加映射来指定被注入的类型 |
176 | 298 |
|
| 299 | +当 @Inject(...) 注解不指定被注入的类型时,还可以通过 AopManager.me().addMapping(...) 事先添加映射来指定被注入的类型,例如: |
| 300 | + |
| 301 | +``` |
| 302 | +AopManager.me().addMapping(Service.class, MyService.class); |
177 | 303 | ```
|
178 | 304 |
|
179 |
| -1. **AopManager.me().addMapping(SharedPreferences.class, sharedPreferences.getClass());** |
| 305 | +通过上面的映射,下面的代码将会为 Service 注入 MyService |
180 | 306 |
|
181 |
| - - `AopManager.me()`:这通常获取 `AopManager` 的单例实例。`AopManager` 可能是一个管理 AOP 行为和配置的类。 |
182 |
| - - `addMapping(SharedPreferences.class, sharedPreferences.getClass())`:这个方法调用可能是在告诉 AOP 框架,当遇到 `SharedPreferences` 类型的依赖注入请求时,应该实例化 `sharedPreferences.getClass()` 返回的类。这里 `SharedPreferences` 是一个接口或类,而 `sharedPreferences.getClass()` 是具体的实现类。 |
| 307 | +``` |
| 308 | +public class IndexController { |
183 | 309 |
|
184 |
| -2. **AopManager.me().addSingletonObject(sharedPreferences);** |
185 |
| - - `addSingletonObject(sharedPreferences)`:这个方法调用可能是在告诉 AOP 框架,`sharedPreferences` 对象应该被视为单例,并且在 AOP 框架的上下文中管理。这意味着当有依赖注入请求 `SharedPreferences` 类型的实例时,框架会提供这个已经创建的 `sharedPreferences` 实例。 |
| 310 | + @Inject |
| 311 | + Service service; |
186 | 312 |
|
| 313 | + public void index() { |
| 314 | + service.justDoIt(); |
| 315 | + } |
| 316 | +} |
| 317 | +``` |
0 commit comments