面向切面编程
JFinal AOP
参考文档:https://jfinal.com/doc/4-6
JBoot AOP
JBoot AOP 在 JFinal AOP 的基础上,新增了我们在分布式下常用的功能,同时借鉴了 Spring AOP 的一些特征,对 JFinal AOP 做了增强,但是又没有 Spring AOP 体系的复杂度。
@Inject
我们可以通过 @Inject 对任何 Bean 的属性进行注入,例如 Controller
@RequestMapping("/helloworld")
public class MyController extends Controller{
@Inject
private UserService userService;
public void index(){
renderJson(userService.findAll());
}
}
在以上的例子中,在默认情况下,JFinal AOP 会去实例化一个 UserService 实例,并注入到 MyController 的 userService 中,因此需要注意的是:UserService 必须是一个可以被实例化的类,不能是抽象类或者接口(Interface)。
如果说,UserService 是一个接口,它有实现类比如 UserServiceImpl.class
,JFinal 提供了另一种方案,代码如下:
@RequestMapping("/helloworld")
public class MyController extends Controller{
@Inject(UserServiceImpl.class)
private UserService userService;
public void index(){
renderJson(userService.findAll());
}
}
@Bean
在以上的例子中,我们认为 @Inject(UserServiceImpl.class)
这可能不是最好的方案,因此,JBoot 提供了可以通过注解 @Bean
给 UserServiceImpl.class
添加在类上,这样 Jboot 在启动的时候,会自动扫描到 UserServiceImpl.class
,并通过 JbootAopFactory 把 UserService 和 UserServiceImpl 添加上关联关系。
代码如下:
Controller:
@RequestMapping("/helloworld")
public class MyController extends Controller{
@Inject
private UserService userService;
public void index(){
renderJson(userService.findAll());
}
}
UserService:
public interface UserService{
List<User> findAll();
}
UserServiceImpl:
@Bean
public class UserServiceImpl implements UserService{
public List<User> findAll(){
//do sth
}
}
当一个接口有多个实现类,或者当在系统中存在多个实例对象,比如有两份 Cache 对象,一份可能是 Redis Server1,一份可能是 Redis Server2,或者有两份数据源 DataSource 等,在这种情况下,我们注入的时候就需要确定注入那个实例。
这时候,我们就需要用到 @Bean(name= "myName")
去给不同的子类去添加注释。
例如:
@RequestMapping("/helloworld")
public class MyController extends Controller{
@Inject
@Bean(name="userServieImpl1") //注入名称为 userServieImpl1 的实例
private UserService userService1;
@Inject
@Bean(name="userServieImpl2") //注入名称为 userServieImpl2 的实例
private UserService userService2;
public void index(){
renderJson(userService.findAll());
}
}
UserService:
public interface UserService{
List<User> findAll();
}
UserServiceImpl1:
@Bean(name="userServiceImpl1")
public class UserServiceImpl1 implements UserService{
public List<User> findAll(){
//do sth
}
}
UserServiceImpl2:
@Bean(name="userServiceImpl2")
public class UserServiceImpl2 implements UserService{
public List<User> findAll(){
//do sth
}
}
这种情况,只是针对一个接口有多个实现类的情况,那么,如果是一个接口只有一个实现类,但是有多个实例,如何进行注入呢?
参考 @Configuration 。
@BeanExclude
当我们使用 @Bean
给某个类添加上注解之后,这个类会做好其实现的所有接口,但是,很多时候我们往往不需要这样做,比如:
@Bean
public class UserServiceImpl implements UserService,
OtherInterface1,OtherInterface2...{
public List<User> findAll(){
//do sth
}
}
在某些场景下,我们可能只希望 UserServiceImpl 和 UserService 做好映射关系,此时,@BeanExclude
就派上用场了。
如下代码排除了 UserServiceImpl 和 OtherInterface1,OtherInterface2 的映射关系。
@Bean
@BeanExclude({OtherInterface1.cass,OtherInterface2.class})
public class UserServiceImpl implements UserService,
OtherInterface1,OtherInterface2...{
public List<User> findAll(){
//do sth
}
}
@Configuration
在 Jboot 中的 @Configuration
和 Spring 体系的 @Configuration
功能类似。
我们可以在一个普通类中添加注解 @Configuration
, 然后在其方法通过 @Bean
去对方法进行添加注解。
例如:
@Configuration
public class AppConfiguration {
@Bean(name = "myCommentServiceFromConfiguration")
public CommentService myCommentService1(){
CommentService commentService = new CommentServiceImpl();
return commentService;
}
@Bean
public CommentService myCommentService2(){
CommentService commentService = new CommentServiceImpl();
return commentService;
}
}
这样,在一个 Jboot 应用中,就会存在两份 CommentService
他们的名称分别为:myCommentServiceFromConfiguration 和 myCommentService2(当只用了注解 @Bean 但是未添加 name 参数时,name 的值为方法的名称)
这样,我们就可以在 Controller 里,通过 @Inject
配合 @Bean(name = ... )
进行注入,例如:
@RequestMapping("/aopcache")
public class AopCacheController extends JbootController {
@Inject
@Bean(name="myCommentService2")
private CommentService commentService;
@Inject
@Bean(name = "myCommentServiceFromConfiguration")
private CommentService myCommentService;
public void index() {
System.out.println("commentService:"+commentService);
System.out.println("myCommentService:"+myCommentService);
}
}
@ConfigValue
在 AOP 注入中,可能很多时候我们需要注入的只是一个配置内容,而非一个对象实例,此时,我们就可以使用注解 @ConfigValue
,例如:
@RequestMapping("/aop")
public class AopController extends JbootController {
@ConfigValue("undertow.host")
private String host;
@ConfigValue("undertow.port")
private int port;
@ConfigValue(value = "undertow.xxx")
private int xxx;
}
此时,配置文件 jboot.properties (包括分布式配置中心) 里配置的 undertow.host 的值自动赋值给 host 属性。其他属性同理。
@StaticConstruct
静态的构造方法。
在某些类中,这个类的创建方式并不是通过 new 的方式进行创建的,或者可能构造函数是私有的,或者这个类可能是一个单例示例的类,比如:
public class JbootManager {
private static JbootManager me = new JbootManager();
public static JbootManager me() {
return me;
}
private JbootManager(){
//do sth
}
}
我们在其他类注入 JbootManager 的时候,并不希望通过 new JbootManager() 的方式进行创建注入,还是希望通过其方法 me()
进行获取。
此时,我们就可以给 JbootManager 类添加 @StaticConstruct
注解,例如:
@StaticConstruct
public class JbootManager {
private static JbootManager me = new JbootManager();
public static JbootManager me() {
return me;
}
private JbootManager(){
//do sth
}
}
但如果 JbootManager 有多个返回自己对象的静态方法,我们就可以使用 @StaticConstruct
的 value 参数来指定。
例如:
@StaticConstruct("me")
public class JbootManager {
private static JbootManager me = new JbootManager();
public static JbootManager me() {
return me;
}
public static JbootManager create(){
return new JbootManager();
}
private JbootManager(){
//do sth
}
}
@PostConstruct
@PostConstruct
是 Java 自带的注解,用于在对 Java 对象初始化、并进行注入成功之后,进行初始化的工作。
比如:
@Bean
public class YourServiceImpl implements YourService{
@Inject
private OtherService1 otherService1;
@Inject
private OtherService2 otherService2;
private Service3 service3;
public YourServiceImpl(){
// 构造方法运行的时候, otherService1 和 otherService2 都为 null,
// 因为还没有开始注入
}
@PostConstruct
private void initService3() {
// 构造函数执行完毕后,Jboot 会自动立即执行此方法
// 此时 otherService1 和 otherService2 已经有值了
otherService1.doSth();
otherService2.doSth();
service3 = createService3(otherService1,otherService2);
}
public void doSomething(){
service3.xxx();
}
}
此时,我们就可以通过如下方法使用 YourService 了
YourService service = Aop.get(YourService.class);
service.doSomething();
@PostConstruct
的特点:
- 必须是
非静态
方法 - 必须是
无参数
方法 - 在同一个类中,只能存在一个方法被
@PostConstruct
修饰。 - 如果子类和父类都存在
@PostConstruct
修饰的方法,子类优先执行,父类后执行。
InterceptorBuilder
InterceptorBuilder : 拦截器构建器。他能够根据当前 Controller、Service 等的方法信息(比如方法注解、方法名称、方法参数),来对当前方法动态 添加 或者 删除 拦截器, 在之前 JFinal 的体系里,给某个方法添加拦截器只能通过 @Before({MyInterceptor.class}) 或者 configInterceptor() 配置方法里添加全局拦截器。
这样带来一个不够优雅的地方是,如果我们想给某个方法 ”增强“,只能以添加全局拦截器的方式来做,这样所有方法都会被该拦截器拦截, 然后再通过其判断进行 ”放行“ 。这样就带来了性能效率的下降,因为在我们整个系统中,有非常多的方法是没有必要走拦截器的。
有了 InterceptorBuilder 之后,我们可以通过 InterceptBuilder 给某个方法来构建其特定的拦截器,而不是全局拦截器, 这样,极大的提高了性能,同时减少了不必要的调用堆栈。
如何使用 InterceptorBuilder 呢?如下代码:
public class YourAppListener extends JbootAppListenerBase{
public void onInit() {
InterceptorBuilderManager.me().addInterceptorBuilder(new MyInterceptorBuilder());
}
}
或者 CacheInterceptorBuilder
添加主键 @AutoLoad
注意:使用注解
@Autoload
之后,就不要通过InterceptorBuilderManager
来添加了,@Autoload
的作用就是在其启动的时候自动添加进去。
比如 @Cacheable
的拦截器构建 CacheInterceptorBuilder 代码如下:
@AutoLoad //自动把当前的 CacheInterceptorBuilder 添加到 InterceptorBuilderManager 里去。
public class CacheInterceptorBuilder implements InterceptorBuilder {
/**
* 开始都某个方法进行构建
* @param serviceClass 方法所在的类
* @param method 方法信息
* @param interceptors 该方法已经有的拦截器
*/
@Override
public void build(Class<?> serviceClass, Method method, Interceptors interceptors) {
Cacheable cacheable = method.getAnnotation(Cacheable.class);
if (cacheable != null) {
interceptors.add(new CacheableInterceptor());
// 或者使用如下的方式添加,这样,所有的方法都会被同一个 "实例" 拦截
// interceptors.add(CacheableInterceptor.class)
}
}
}
在 build()
方法中的 Interceptors,我们不仅仅可以通过 Interceptors 来添加拦截器,我们还可以通过其来删除拦截器、或者修改拦截器的顺序等等操作。