这篇文章讲的是面向切面思想之集中式登录架构设计,也就是AOP。
什么是面向切面思想之集中式登录架构设计呢,这里我觉得可以联系一下拦截功能,就好理解了。
比如一个电商的App,有三个功能模块,分别是我的专区,我的优惠券,我的积分。且这三个功能模块有一个共同的特点就是,他们必须要登录之后,才能进到具体的页面去得到更多的服务。
作为资深工程师的我们一定不会在每个入口处手写登录校验逻辑这种不符合我们资深工程师身份的写法。
这时候我们AOP,面向切面的编程思想就出来了。
既然好几个模块都需要走登录这个流程,那我们就将登录页面一刀切,进入三个模块的具体页面时都需要经过登录校验,这就是我们这边文章将的核心内容了。
录屏
日志:
本文中的切面设计使用aspectj来实现:
buildscript {
dependencies {
。。。
// 版本界限:As-3.0.1 + gradle4.4-all (需要配置r17的NDK环境)
// 或者:As-3.2.1 + gradle4.6-all (正常使用,无警告)
classpath 'org.aspectj:aspectjtools:1.8.9'
classpath 'org.aspectj:aspectjweaver:1.8.9'
}
}
implementation 'org.aspectj:aspectjrt:1.8.13'
关键代码:
1、定义ClickBehavior和LoginCheck两个注解。
// 用户点击痕迹(行为统计) IoC容器
@Target(AnnotationTarget.FUNCTION) // 目标作用在方法之上
@Retention(value = AnnotationRetention.RUNTIME)
annotation class ClickBehavior(val value: String)
// 用户登录检测
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class LoginCheck()
2、在Activity中定义了一个登录按钮模拟登录,定义“我的专区”,“我的优惠券”,“我的积分”,将定义好的两个注解加到对应的点击事件上去。
MainActivity.kt
class MainActivity : AppCompatActivity() {
private val TAG = "lr >>> "
companion object {
var isLogin = false
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
// 登录点击事件(用户行为统计)
@ClickBehavior("登录")
fun login(view: View) {
Log.e(TAG, "模拟接口请求……验证通过,登录成功!")
isLogin = true
}
// 用户行为统计(友盟统计?!后台要求自己统计)
@ClickBehavior("我的专区")
@LoginCheck
fun area(view: View) {
Log.e(TAG, "开始跳转到 -> 我的专区 Activity")
startActivity(Intent(this, OtherActivity::class.java))
}
// 用户行为统计
@ClickBehavior("我的优惠券")
@LoginCheck
fun coupon(view: View) {
Log.e(TAG, "开始跳转到 -> 我的优惠券 Activity")
startActivity(Intent(this, OtherActivity::class.java))
}
// 用户行为统计
@ClickBehavior("我的积分")
@LoginCheck
fun score(view: View) {
Log.e(TAG, "开始跳转到 -> 我的积分 Activity")
startActivity(Intent(this, OtherActivity::class.java))
}
}
3、接下来就是如何对拦截到的事件做处理了,这里就用到了Aspect,参考代码如下。
@Aspect
class ClickBehaviorAspect {
private val TAG = "lr >>> "
// 1、应用中用到了哪些注解,放到当前的切入点进行处理(找到需要处理的切入点)
// execution,以方法执行时作为切点,触发Aspect类
// * *(..)) 可以处理ClickBehavior这个类所有的方法
@Pointcut("execution(@pers.owen.aop_login.annotation.ClickBehavior * *(..))")
public fun methodPointCut() {}
// 2、对切入点如何处理
@Around("methodPointCut()")
@Throws(Throwable::class)
open fun jointPotin(joinPoint: ProceedingJoinPoint): Any? {
// 获取签名方法
val methodSignature = joinPoint.signature as MethodSignature
// 获取方法所属的类名
val className = methodSignature.declaringType.simpleName
// 获取方法名
val methodName = methodSignature.name
// 获取方法的注解值(需要统计的用户行为)
val funName: String =
methodSignature.method.getAnnotation(ClickBehavior::class.java).value
// 统计方法的执行时间、统计用户点击某功能行为。(存储到本地,每过x天上传到服务器)
val begin = System.currentTimeMillis()
Log.e(TAG, "ClickBehavior Method Start >>> ")
val result = joinPoint.proceed() // MainActivity中切面的方法
val duration = System.currentTimeMillis() - begin
Log.e(TAG, "ClickBehavior Method End >>> ")
Log.e(
TAG, String.format("统计了:%s功能,在%s类的%s方法,用时%d ms",
funName, className, methodName, duration
)
)
return result
}
}
@Aspect // 定义切面类
class LoginCheckAspect {
private val TAG = "lr >>> "
// 1、应用中用到了哪些注解,放到当前的切入点进行处理(找到需要处理的切入点)
// execution,以方法执行时作为切点,触发Aspect类
// * *(..)) 可以处理ClickBehavior这个类所有的方法
@Pointcut("execution(@pers.owen.aop_login.annotation.LoginCheck * *(..))")
fun methodPointCut(){}
// 2、对切入点如何处理
@Around("methodPointCut()")
@Throws(Throwable::class)
open fun jointPotin(joinPoint: ProceedingJoinPoint): Any? {
val context = joinPoint.getThis() as Context
return if (MainActivity.isLogin) { // 从SharedPreferences中读取
Log.e(TAG, "检测到已登录!")
joinPoint.proceed()
} else {
Log.e(TAG, "检测到未登录!")
Toast.makeText(context, "请先登录!", Toast.LENGTH_SHORT).show()
context.startActivity(Intent(context, LoginActivity::class.java))
null // 不再执行方法(切入点)
}
}
}
About