为什么要写这篇文章:

如今各大平台能搜xx框架如何使用的一大堆,但提及如何利用写出优雅的代码的文章却少之又少。所以本文主要提供一个思路来优化代码,也算抛砖引玉。若各位有不同看法或意见,可以在评论区提出,或者私信。博主看到会及时回复。

本文介绍:

  1. 拿过来直接可以运行

  1. 没有多余的废话,不会涉及到原理。先看到效果,用起来再去探究原理。

  1. 对小白友好

场景:

一个线上需要登录的App,许多函数在使用之前都要校验用户是否已登录。于是最直接的方案就是在这些函数中增加大量的if else来进行判断用户是登录……如下所示:

//需要验证的登录状态的函数private void sendMsg(String msg){//Manger.getInstance().getState() 获取登录状态switch (Manger.getInstance().getState()){case -1://用户被强制下线//提醒用户 账号密码可能已泄漏//然后跳转到登录页面return;case 0://用户未登录//跳转登录页面return;case 1://用户已登录sendImMsg(msg);//验证通过发送消息break;}}

这种使用if else用来控制权限,着实不太优雅。且随着项目业务逐渐增多,管理登录状态的类(Manger)会跟多处代码耦合,浸入量极大。要解决这个问题,我们可以使用AOP思想来实现对登录模块的控制。(若不清楚AOP思想,可以先去了解一下大概意思)这里我们使用Aspectj这个框架来优化整段代码。

引入插件与依赖

  1. 在Project中的build.gradle中引入

buildscript {repositories {google()mavenCentral()}dependencies {classpath "com.android.tools.build:gradle:7.0.2"//引入Aspectj插件 非常重要!!classpath 'org.aspectj:aspectjtools:1.8.9'classpath 'org.aspectj:aspectjweaver:1.8.9'// NOTE: Do not place your application dependencies here; they belong// in the individual module build.gradle files}}task clean(type: Delete) {delete rootProject.buildDir}

2.在Module中的build.gradle中引入

dependencies {//引入Aspectj框架implementation 'org.aspectj:aspectjrt:1.8.13'}import org.aspectj.bridge.IMessageimport org.aspectj.bridge.MessageHandlerimport org.aspectj.tools.ajc.Mainfinal def log = project.loggerfinal def variants = project.android.applicationVariantsvariants.all { variant ->if (!variant.buildType.isDebuggable()) {log.debug("Skipping non-debuggable build type '${variant.buildType.name}'.")return;}JavaCompile javaCompile = variant.javaCompilejavaCompile.doLast {String[] args = ["-showWeaveInfo", "-1.8", "-inpath", javaCompile.destinationDir.toString(), "-aspectpath", javaCompile.classpath.asPath, "-d", javaCompile.destinationDir.toString(), "-classpath", javaCompile.classpath.asPath, "-bootclasspath", project.android.bootClasspath.join(File.pathSeparator)]log.debug "ajc args: " + Arrays.toString(args)MessageHandler handler = new MessageHandler(true);new Main().run(args, handler);for (IMessage message : handler.getMessages(null, true)) {switch (message.getKind()) {case IMessage.ABORT:case IMessage.ERROR:case IMessage.FAIL:log.error message.message, message.thrownbreak;case IMessage.WARNING:log.warn message.message, message.thrownbreak;case IMessage.INFO:log.info message.message, message.thrownbreak;case IMessage.DEBUG:log.debug message.message, message.thrownbreak;}}}}

编写注解

每次调用函数时,我们需要先检查检查用户是否登录。如果用注解自动帮我们去检查,那么代码会优雅很多。这里我们定义一个注解,告诉Aspect 切入点在哪。

@CheckLogin 作用:加了@CheckLogin注解的函数运行之前都会自动去检查用户是否登录

//注意此处的包名 后面需要用,若你之间复制到自己项目中,包名改了,注解起不了作用package com.chj.chjaj.loginmode.annotation;import java.lang.annotation.ElementType;import java.lang.annotation.Repeatable;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;/*** * 作者:chj233 * 时间:2023/3/17 23:16 * 描述:用于检查登录 */@Target(ElementType.METHOD)//此注解只能写在函数上@Retention(RetentionPolicy.RUNTIME)//注解生效 运行时public @interface CheckLogin {}

@LoginOut 作用:当校验未通过时(Manger.getInstance().getState() == -1),运行加了@LoginOut注解的函数

//注意此处的包名 后面需要用,若你之间复制到自己项目中,包名改了,注解起不了作用package com.chj.chjaj.loginmode.annotation;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;/*** * 作者:chj233 * 时间:2023/3/17 23:15 * 描述:登录退出触发此注解的函数 */@Target(ElementType.METHOD)//此注解只能写在函数上@Retention(RetentionPolicy.RUNTIME)//注解生效 运行时public @interface LoginOut {}

@LoginTo 作用:当校验为通过时(Manger.getInstance().getState() == 0),运行加了@LoginTo注解的函数

//注意此处的包名 后面需要用,若你之间复制到自己项目中,包名改了,注解起不了作用package com.chj.chjaj.loginmode.annotation;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;/*** * 作者:chj233 * 时间:2023/3/17 23:15 * 描述:需要去登录 */@Target(ElementType.METHOD)//此注解只能写在函数上@Retention(RetentionPolicy.RUNTIME)//注解生效 运行时public @interface LoginTo {}

AspectLogin 作用:这个类最为关键,注解是否按照我们编写好的执行,就看这个类

package com.chj.chjaj.loginmode;import com.chj.chjaj.loginmode.annotation.LoginOut;import com.chj.chjaj.loginmode.annotation.LoginTo;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Pointcut;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;/*** * 作者:chj233 * 时间:2023/3/17 23:13 * 描述:注解控制 */@Aspectpublic class AspectLogin {//切点 com.chj.chjaj.loginmode.annotation包下的CheckLogin注解为切点所有函数@Pointcut("execution(@com.chj.chjaj.loginmode.annotation.CheckLogin* *(..))")public void loginCheck(){}//切面 loginCheck() 就是上面切点的函数名称 这个名称可以随便改 但两个地方需要保持一致@Around("loginCheck()")public void check(final ProceedingJoinPoint point) throws Throwable {switch (Manger.getInstance().getState()){case 1://已登录验证通过point.proceed();//往下执行,若不执行point.proceed(),那么 加了切点注解的函数都不会运行break;case -1://已退出登录invokeAnnotion(point.getThis(), LoginOut.class); //point.getThis() 获取注解所在的类的对象break;case 0://被强制下线invokeAnnotion(point.getThis(), LoginTo.class); //point.getThis() 获取注解所在的类的对象break;default:break;}}//通过对象反射public static void invokeAnnotion(Object object, Class annotionClass) {Class objectClass = object.getClass(); // 获取class对象// 遍历所有函数Method[] methods = objectClass.getDeclaredMethods(); // 得到所有的 对象中所有 公开 私有 函数for (Method method : methods) {//循环这些函数method.setAccessible(true); // 让虚拟机不要去检测 private的函数// 判断是否被 annotionClass 注解过的函数boolean annotationPresent = method.isAnnotationPresent(annotionClass); //若是被注解的函数if (annotationPresent) { //那么之间执行// 当前函数 annotionClass 注解过的函数try {method.invoke(object);//执行函数} catch (IllegalAccessException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();}}}}}

MainActivity 作用:就是用来测试的

package com.chj.chjaj;import android.os.Bundle;import android.widget.Toast;import androidx.appcompat.app.AppCompatActivity;import com.chj.chjaj.loginmode.Manger;import com.chj.chjaj.loginmode.annotation.CheckLogin;import com.chj.chjaj.loginmode.annotation.LoginOut;import com.chj.chjaj.loginmode.annotation.LoginTo;//简化业务层的代码public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);findViewById(R.id.denglu).setOnClickListener((view) ->{Manger.getInstance().setState(1);toast("已登录");});findViewById(R.id.tuichu).setOnClickListener((view) ->{Manger.getInstance().setState(-1);toast("已退出");});findViewById(R.id.xiaxian).setOnClickListener((view) ->{Manger.getInstance().setState(0);toast("已下线");});findViewById(R.id.fasong).setOnClickListener((view) ->{send();});findViewById(R.id.getinfo).setOnClickListener((view )-> {getInfo();});}@CheckLogin//只需要增加@CheckLogin注解 即可去检查当前的登录状态protected void send(){toast("发送消息");}@CheckLogin//只需要增加@CheckLogin注解 即可去检查当前的登录状态protected void getInfo(){toast("获取信息");}@LoginOut//用户已退出 执行此函数public void logout(){toast("登录已退出");}@LoginTo//需要用户登录 执行此函数public void loginTo(){toast("跳转登录.....");}protected void toast(String msg){if (msg == null) return;Toast.makeText(this,msg,Toast.LENGTH_LONG).show();}}

总结一下:

此方案使用Aspect 通过反射的方式来执行注解标记的函数,所以在性能上会略低,所以对性能要求非常高的函数并不太适用。