普通登录流程图
集中式登录流程图
把共有的业务通过AOP统一管理
这里我们就需要使用AspectJ来实现面向切面的集中式登录
什么是AspectJ
AspectJ是一个面向切面的框架,它扩展了Java语言。AspectJ定义了AOP语法,它有一个专门的编译器用来生成遵守Java字节编码规范的Class文件。
Aspect是切面是切入点(PointCuts)和通知(Advice)的集合。
Pointcut 切入点
通过使用一些特定的表达式过滤出来想要切入Advice的连接点。需要切面的点在哪里,从哪里切入。
Advice通知
通知是向切入点注入的代码的实现方法。分为
Before:切入方法执行之前来运行切面代码
After:切入点方法执行之后来执行切面代码
Around:方法前后都执行切面代码
Joint Point连接点
所有的目标方法都是连接点。
Android studio引入AspectJ之前环境问题
1.在AS 3.0.1 gradle4.4-all 的时候需要配置NDK-r17的环境,配置后正常使用没有警告。
2.在AS 3.2.1 gradle4.6-all 可以正常使用无警告(官方开发文档的环境)。
3.在AS 3.4.0 gradle5.1.1-all 有过时的API警告,但是不影响使用。
Android studio引入AspectJ
1.在项目build.gradle中引入:
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.4.1'
classpath 'org.aspectj:aspectjtools:1.8.9'
classpath 'org.aspectj:aspectjweaver:1.8.9'
}
}
2.App module中的build.gradle中
apply plugin: 'com.android.application'
buildscript {//编译时用AspectJ的编译器,不在使用传统的javac
repositories {
mavenCentral()
}
dependencies {
classpath 'org.aspectj:aspectjtools:1.8.9'
classpath 'org.aspectj:aspectjweaver:1.8.9'
}
}
android {
compileSdkVersion 28
defaultConfig {
applicationId "com.login"
minSdkVersion 21
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'org.aspectj:aspectjrt:1.8.13'
}
// 版本界限:As-3.0.1 + gradle4.4-all (需要配置r17的NDK环境)
// 或者:As-3.2.1 + gradle4.6-all (正常使用,无警告)
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main
final def log = project.logger
final def variants = project.android.applicationVariants
variants.all { variant ->
if (!variant.buildType.isDebuggable()) {
log.debug("Skipping non-debuggable build type '${variant.buildType.name}'.")
return;
}
JavaCompile javaCompile = variant.javaCompile
javaCompile.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.thrown
break;
case IMessage.WARNING:
log.warn message.message, message.thrown
break;
case IMessage.INFO:
log.info message.message, message.thrown
break;
case IMessage.DEBUG:
log.debug message.message, message.thrown
break;
}
}
}
}
代码构建
简单的登录逻辑,点击非登录模块如果没有身份时,跳转到登录页面;如果有身份,则直接进入相关页面。
1.定义检查登录类注解LoginCheck
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LoginCheck {
}
2.定义aspect切面类LoginBehavoirAspect
- 应用中用到了那些注解,放到当前的切入点进行处理(找到需要处理的切入点)
execution 以方法执行时作为切点,触发Aspect类
@Pointcut("execution(@com.login.annotation.LoginCheck * *(..))")
public void methodPointCut() {
}
**(…)可以处理LoginCheck这个注解作用的所有方法.
- 对切入点方法进行统一逻辑处理处理
@Around("methodPointCut()")
public Object jointPoint(ProceedingJoinPoint joinPoint) throws Throwable {
Context context = (Context) joinPoint.getThis();
if (false) {
Log.e(TAG, "用户有身份,可以进行下一步 >>>");
return joinPoint.proceed();
} else {
Log.e(TAG, "用户无身份,需要跳转到登录页面 >>>");
context.startActivity(new Intent(context, LoginActivity.class));
return null;
}
}
- 如何使用,只需要在相关逻辑需要校验身份的方法添加LoginCheck注解即可。
@LoginCheck
public void area(View view) {
Log.e(TAG, "开始跳转到--->我的专区");
startActivity(new Intent(this,OtherActivity.class));
}
- 同理我们也可以利用该原理对方法的操作和执行之间添加监控,封装我们自己的用户行为统计库,无入侵零耦合。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ClickBehavior {
String value();
}
@Pointcut("execution(@com.login.annotation.ClickBehavior * *(..))")
public void methodPointCut() {
}
@Around("methodPointCut()")
public Object jointPoint(ProceedingJoinPoint joinPoint) throws Throwable {
//获取签名方法
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
//获取方法所属的类名
String clazzName = signature.getDeclaringType().getSimpleName();
//获取方法名
String methodName = signature.getMethod().getName();
//获取方法的注解值(需要添加用户行为统计)
String actionName = signature.getMethod().getAnnotation(ClickBehavior.class).value();
//统计方法的执行时间、统计用户点击某功能行为
long begin = System.currentTimeMillis();
Log.e(TAG, "ClickBehavior Method Start >>>");
Object result = joinPoint.proceed();
long duration = System.currentTimeMillis() - begin;
Log.e(TAG, "ClickBehavior Method End >>>");
Log.e(TAG, String.format("统计:%s功能,在%s类的%s方法,用时%d ms", actionName, clazzName, methodName, duration));
return result;
}
最后相关代码请移步Github中查看