登录每个app都在写,很多人没有将登录写好。
这里不是写一个登录请求的需求,而是有没有登录,没有就跳转到登录界面的需求。
其实这是一个全局业务架构,全局业务抽取到一个切面来完成。
即AOP全局业务的登录架构。
OOP写法:从SharePreference中拿一个isLogined属性,判断它是否为true。
如为true,就跳转到我的优惠券、我的积分、我的专区等等。
如为false,就startActivity(new Intent())跳转到LoginActivity。
真的是这样来写吗?
集中式登录架构设计
方式一:动态代理方式切面

登录业务 是全局的共同业务,可以采用AOP面向切面的思想,把登录业务给切出来。

这里采用动态代理的方式将登录业务切面。还有没有其他方式呢?
方式二:预编译方式

看到纵向的业务,都有登录业务,采用AOP切面的方式,切出一个橫面来处理。
其实看到这样的业务,也让我们联想到用户行为统计,它也是全局业务。如:点击了登录按钮、点击了xx按钮等。
用OOP的方式做用户行为统计,大概就是这样:
void method(){
xxx; // 开始统计
yyy; // 业务逻辑
zzz; // 结束统计
}
但这样做可以吗?明显不可以,每个要求的方法都这样,那不是添乱吗。还是采用AOP方式。
AspectJ是一个面向切面的框架,它扩展了Java语言。
AspectJ定义了AOP语法,它有一个专门的编译器用来生成(遵守java字节编码规范的)class文件。
编译过程:通过javac将java文件转成可执行的class文件,这是个ReBuild过程。
AspectJ可以替代javac的工作(注:不是替代javac将java转成class的功能),即AspectJ有完成编译工作的能力。
有了AspectJ就可以在预编译期(生存class文件)的时候,在class中植入一些新的代码。那么我们就可以做到AOP。
AspectJ:切面(切入点和通知的集合)
- 切入点(PointCut):那些通过使用一些特定的表达式过滤出来的想要切入通知的连接点。
- 通知(Advice):是向切入点中注入的代码的一种实现方法。
- 连接点(Joint Point):所有的目标方法都是连接点。
简单讲:
- 切入点:你需要切面的点在哪里,从哪里切
- 通知:有几种方式
- Before:在切入的方法执行之前来运行我们的代码。
- After:在切入的方法执行之后来运行我们的代码,也就是切面里面要处理的代码。
- Around:在方法执行前后都运行我们的方法。
预编译方式AOP
AspectJ有坑:下面是一些临界版本
| AS | gradle | 坑 |
|---|---|---|
| 3.0.1 | 4.4-all | ndk r17 |
| 3.2.1 | 4.6-all | 无 |
| 3.4.0 | 5.1.1-all | 过时的API警告(2019年之后方法过时) |
版本界限:AS-3.0.1 + gradle-4.4-all (需配置r17的ndk环境) 或者 AS-3.2.1 + gradle-4.6-all (正常使用无警告)
AS项目中根目录下的build.gradle、gradle-wrapper.properties文件来修改。



代码展示之前期准备
搭建aspectj环境:


引入aspectj包:

下面这些代码也在app文件下的build.gradle文件中最后位置添加(即dependencies之后添加)。


代码展示之具体讲解
接下来要做两件事:1.自定义注解;2.创建Aspect类
集中式登录:
// 用户登录检测
@Target(ElementType.METHOD) // 目标作用在方法之上
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginCheck { }
@Aspect // 定义切面类
public class LoginCheckAspect {
// 1.应用中用到了那些注解,放到当前的切入点进行处理。(直白点:找到需要处理的切入点)这里是定义切面的规则
// execution : 以方法执行时作为切点,出发Aspect类
// * *(..) : 可以处理ClickBehavior这个类中的所有方法
@Pointcut("execution(@com.example.uml.annotation.LoginCheck * *(..))")
public void methodPointcut() { }
// 2.对切入点进行处理
// 返回值、参数、抛出异常
@Around("methodPointcut()")
public Object methodAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
Context context = (Context) joinPoint.getThis();
// 一般从SharedPreferences中读取
boolean isLogin = false;
if (isLogin) {
// 检测到已登录
return joinPoint.proceed(); // 切入点方法继续执行
} else {
// 检测到未登录
Toast.makeText(context, "请先登录!", Toast.LENGTH_SHORT).show();
context.startActivity(new Intent(context, LoginActivity.class));
return null; // 不再执行切入点方法
}
}
}
用户行为统计:
// 用户点击痕迹(用户行为统计)
@Target(ElementType.METHOD) // 目标作用在方法之上
@Retention(RetentionPolicy.RUNTIME)
public @interface ClickBehavior {
String value();
}
@Aspect // 定义切面类
public class ClickBehaviorAspect {
@Pointcut("execution(@com.example.uml.annotation.ClickBehavior * *(..))")
public void methodPointcut() { }
@Around("methodPointcut()")
public Object methodAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
// 获取签名方法
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
// 获取方法所属的类名
String className = methodSignature.getDeclaringType().getSimpleName();
// 获取方法名
String methodName = methodSignature.getMethod().getName();
// 获取方法的注解值(需要统计的用户行为)
String annotationValue = methodSignature.getMethod().getAnnotation(ClickBehavior.class).value();
// 统计方法的执行时间,统计用户点击某功能行为。(一般存储到本地,每过x天上传到服务器)
long timeStart = System.currentTimeMillis();
Object result = joinPoint.proceed(); // MainActivity中切面的方法
long duration = System.currentTimeMillis() - timeStart;
String content = String.format("统计了:%s功能,在%s类的%s方法,用时%d ms",
annotationValue, className, methodName, duration);
return result;
}
}
MainActivity :
public class MainActivity extends BaseActivity {
@Override
protected void initLayout() {
setContentView(R.layout.activity_main);
}
@ClickBehavior("登录")
public void login(View view) {
// 跳转到登录界面
}
@ClickBehavior("我的专区")
@LoginCheck
public void area(View view) {
// ... 逻辑代码
}
@ClickBehavior("优惠券")
@LoginCheck
public void coupon(View view) {
// ... 逻辑代码
}
@ClickBehavior("积分")
@LoginCheck
public void score(View view) {
// ... 逻辑代码
}
}
思考
如不采用这种预编译方式的AOP方法,统计行为怎么写呢?
public void score(View view) {
// 开始统计的代码
// ... 逻辑代码
// 结束统计的代码
}
估计就是这样。麻不麻烦?啰不啰嗦?
本文探讨了集中式登录架构的设计,包括动态代理方式的切面处理和预编译方式AOP的实现。通过AspectJ在预编译期植入代码,实现了全局登录业务的统一处理。同时,文章提到了预编译方式AOP的版本限制及环境配置,并给出了代码示例。
5万+

被折叠的 条评论
为什么被折叠?



