Android开发之AOP编程

Android开发之AOP编程

一、简介:

AOP(Aspect-Oriented Programming,面向切面编程),可谓是OOP(Object-Oriented Programing,面向对象编程)的补充和完善。AOP技术利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”,即方面。所谓“方面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低 模块间的耦合度,并有利于未来的可操作性和可维护性。Android相关AOP常用的方法有 JNI HOOK 和 静态织入,本文以静态织入的方式之一AspectJ来进行讲解使用。(在编译期织入,切面直接以字节码的形式编译到目标字节码文件中,这要求使用特殊的 Java 编译器。)

二、使用场景

对于Java后端来说最常见的场景应该就是日志记录、检查用户权限、参数校验、事务处理、缓存等等了,而对于Android来说也是比较广泛的(适用与Java的同样都适用于Android),例如常见的有记录日志、检查权限申请、判断用户登录状态、检查网络状态、过滤重复点击等等,可以根据项目实际需要的一些业务场景来自定义你所需要的。

三、实践

1、添加依赖
apply plugin: 'android-aspectjx'

dependencies {
    ...
    compile 'org.aspectj:aspectjrt:1.8.9'
}
复制代码

项目跟目录的gradle脚本中加入

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        //此处推荐该库,由沪江出品,可免去配置各种复杂任务
        classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.4'
    }
}
复制代码
2、AOP注解与使用
@Aspect:声明切面,标记类
@Pointcut(切点表达式):定义切点,标记方法
@Before(切点表达式):前置通知,切点之前执行
@Around(切点表达式):环绕通知,切点前后执行
@After(切点表达式):后置通知,切点之后执行
@AfterReturning(切点表达式):返回通知,切点方法返回结果之后执行
@AfterThrowing(切点表达式):异常通知,切点抛出异常时执行
复制代码

切点表达式的示例:

如:execution(@com.xxx.aop.TimeLog *.(..))
切点表达式的组成:
execution(@注解 访问权限 返回值的类型 包名.函数名(参数))
复制代码

下面简单列举两个简单例子:

@Before("execution(@com.xxx.aop.activity * *(..))")
public void before(JoinPoint point) {
    Log.i(TAG, "method excute before...");
}
匹配activity包下的所有方法,在方法执行前输出日志
复制代码
@Around("execution(@cn.com.xxx.Async * *(..))")
public void doAsyncMethod(ProceedingJoinPoint joinPoint) {
    Log.i(TAG, "method excute before...");
    joinPoint.proceed();
    Log.i(TAG, "method excute after...");
}
匹配带有Async注解的方法,在方法执行前后分别输出日志
复制代码
@Around("execution(@cn.com.xxx.Async * *(..)) && @annotation(async)")
public void doAsyncMethod(ProceedingJoinPoint joinPoint, Async async) {
    Log.i(TAG, "value>>>>>"+async.value());
    <!--to do somethings-->
    joinPoint.proceed();
    <!--to do somethings-->
}
//注意:@annotation(xxx)必须与下面的的参数值对应
匹配带有Async注解的方法,并获取注解中的值,去做相应处理。
复制代码
3、实际应用举例
场景一、利用注解实现缓存处理

1、定义注解Cache如下:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Cache {

    String key(); //缓存的key

    int expiry() default -1; // 过期时间,单位是秒
}
复制代码

2、编写Aspect实现

@Aspect
public class CacheAspect {
    private static final String POINTCUT_METHOD = "execution(@cn.com.xxx.annotation.Cache * *(..))";

    @Pointcut(POINTCUT_METHOD)
    public void onCacheMethod() {
    }

    @Around("onCacheMethod() && @annotation(cache)")
    public Object doCacheMethod(ProceedingJoinPoint joinPoint, Cache cache) throws Throwable {
        //获取注解中的key
        String key = cache.key();
        //获取注解中的过期时间
        int expiry = cache.expiry();
        //执行当前注解的方法(放行)
        Object result = joinPoint.proceed();
        //方法执行后进行缓存(缓存对象必须是方法返回值)
        ACache aCache = ACache.get(AopArms.getContext());
        if (expiry>0) {
            aCache.put(key,(Serializable)result,expiry);
        } else {
            aCache.put(key,(Serializable)result);
        }
        return result;
    }
}
复制代码

此处引用ACache该项目中的缓存实现,仅一个Acache文件,个人觉得还是比较好用的,当然你也可以自己去实现。 3、测试

    public static void main(String[] args) {
        initData();
        getUser();
    }
    
    //缓存数据
    @Cache(key = "userList")
    private ArrayList<User> initData() {
        ArrayList<User> list = new ArrayList<>();
        for (int i=0; i<5; i++){
            User user = new User();
            user.setName("艾神一不小心:"+i);
            user.setPassword("密码:"+i);
            list.add(user);
        }
        return list;
    }
    
    //获取缓存
    private void getUser() {
        ArrayList<User> users = ACache.get(this).getAsList("userList", User.class);
        Log.e(TAG, "getUser: "+users);
    }
    
复制代码
场景二、利用注解实现缓存移除

1、定义注解CacheEvict如下:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface CacheEvict {

    //需要移除的key
    String key();

    // 缓存的清除是否在方法之前执行, 默认代表缓存清除操作是在方法执行之后执行;如果出现异常缓存就不会清除
    boolean beforeInvocation() default false;
    
    //是否清空所有缓存
    boolean allEntries() default false;
}
复制代码

2、编写Aspect实现

@Aspect
public class CacheEvictAspect {
    private static final String POINTCUT_METHOD = "execution(@cn.com.xxx.annotation.CacheEvict * *(..))";

    //切点位置,所有的CacheEvict处
    @Pointcut(POINTCUT_METHOD)
    public void onCacheEvictMethod() {
    }
    
    //环绕处理,并拿到CacheEvict注解值
    @Around("onCacheEvictMethod() && @annotation(cacheEvict)")
    public Object doCacheEvictMethod(ProceedingJoinPoint joinPoint, CacheEvict cacheEvict) throws Throwable {
        String key = cacheEvict.key();
        boolean beforeInvocation = cacheEvict.beforeInvocation();
        boolean allEntries = cacheEvict.allEntries();
        ACache aCache = ACache.get(AopArms.getContext());
        Object result = null;
        if (allEntries){
            //如果是全部清空,则key不需要有值
            if (!TextUtils.isEmpty(key))
                throw new IllegalArgumentException("Key cannot have value when cleaning all caches");
            aCache.clear();
        }
        if (beforeInvocation){
            //方法执行前,移除缓存
            aCache.remove(key);
            result = joinPoint.proceed();
        }else {
            //方法执行后,移除缓存,如果出现异常缓存就不会清除(推荐)
            result = joinPoint.proceed();
            aCache.remove(key);
        }
        return result;
    }
}
复制代码

3、测试

public static void main(String[] args) {
        removeUser();
        getUser();
    }
    
    //移除缓存数据
    @CacheEvict(key = "userList")
    private void removeUser() {
        Log.e(TAG, "removeUser: >>>>");
    }
    
    //获取缓存
    private void getUser() {
        ArrayList<User> users = ACache.get(this).getAsList("userList", User.class);
        Log.e(TAG, "getUser: "+users);
    }
复制代码

可以看到执行结果:

最后推荐本人写的一个基于AOP的注解框架AopArms,用法及其简单,参考一款基于AOP的Android注解框架,里面编写了Android开发中常用的一套注解,如日志、异步处理、缓存、SP、延迟操作、定时任务、重试机制、try-catch安全机制、过滤频繁点击等,后续还会有更多更强大的注解功能加入,欢迎star。

转载于:https://juejin.im/post/5ce749b6f265da1bba58de15

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/100886.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • Java详解:淘宝秒杀脚本java

    Java详解:淘宝秒杀脚本java造成雪崩的真实场景1.4.1服务提供者不可用硬件故障:如网络故障、硬盘损坏等。程序的bug:如算法需要占用大量CPU的计算时间导致CPU使用率过高。缓存击穿:比如应用刚重启,短时间内缓存是失效的,导致大量请求直接访问到了数据库,数据库不堪重负,服务不可用。秒杀和大促:服务短时间承载不了那么多请求量。1.4.2重试加大流量用户连续重试:比如用户看到界面上没有响应,所以又操作了一遍,结果又增加了一倍请求量。程序重试机制:比如代码中有多次重试的逻辑,一次失

    2022年6月1日
    31
  • plsqldeveloper汉化包_plsql汉化包

    plsqldeveloper汉化包_plsql汉化包PLSQLDeveloper汉化补丁下载地址http://download.csdn.net/download/rxtanlian/10141249一、双击运行补丁二、选择你PLSQLDeveloper的安装目录看图三、点击蓝色三角形按钮四、继续下一步五、下一步,完成汉化六、完成七、重启你的PLSQLDeveloper效果就出来了,哈哈

    2022年10月12日
    0
  • windows2003 dns 414错误「建议收藏」

    windows2003 dns 414错误「建议收藏」原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。http://rainbird.blog.51cto.com/211214/121697       因为原来在Linux下实现过域根dns.所以朋友也想做域名用自己的dns服务器的时候肯定是一马当先的帮着做了。操作系统这回用的是2003。用windows配置服务就一个字:“简单”。

    2022年6月11日
    49
  • 网页光栅化_光栅净化

    网页光栅化_光栅净化资料:https://blog.csdn.net/luoshengyang/article/details/51348829总结来说,分块的光栅化过程包含了以下三个主要的步骤:1.根据分块的可见性,将它们划分到不同的Bin中。2.根据内存限制策略,从优先级较高的Bin中选集出需要光栅化的分块。3.为每一个需要光栅化的分块分配光栅化内存…

    2022年10月21日
    0
  • Python学习笔记_05:使用Flask+MySQL实现用户登陆注册以及增删查改操作「建议收藏」

    Python学习笔记_05:使用Flask+MySQL实现用户登陆注册以及增删查改操作

    2022年3月12日
    204
  • MPEG4 MP4和AVC H264 MP4有什么不同

    MPEG4 MP4和AVC H264 MP4有什么不同H264  一、H.264与其他标准的比较  1.1在画质上  H.264概述随着市场的需求,在尽可能低的存储情况下获得好的图像质量和低带宽图像快速传输已成为视频压缩的两大难题。为此IEO/IEC/和ITU-T两大国际标准化组织联手制定了新一代视频压缩标准H.264。  MPEG4H.264标准LOGO1.2在编码上  H.264和以前的标准一样,也是DPCM加变换编码

    2022年10月17日
    0

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号