集中式登录架构设计

本文探讨了集中式登录架构的设计,包括动态代理方式的切面处理和预编译方式AOP的实现。通过AspectJ在预编译期植入代码,实现了全局登录业务的统一处理。同时,文章提到了预编译方式AOP的版本限制及环境配置,并给出了代码示例。

登录每个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):所有的目标方法都是连接点。

简单讲:

  • 切入点:你需要切面的点在哪里,从哪里切
  • 通知:有几种方式
    1. Before:在切入的方法执行之前来运行我们的代码。
    2. After:在切入的方法执行之后来运行我们的代码,也就是切面里面要处理的代码。
    3. Around:在方法执行前后都运行我们的方法。

预编译方式AOP

AspectJ有坑:下面是一些临界版本

ASgradle
3.0.14.4-allndk r17
3.2.14.6-all
3.4.05.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文件来修改。
AS_gradle
build.gradle
gradle-wrapper.properties

代码展示之前期准备

搭建aspectj环境:
项目build.gradle
app build.gradle
引入aspectj包:
app build.gradle
下面这些代码也在app文件下的build.gradle文件中最后位置添加(即dependencies之后添加)。
app build.gradle
app build.gradle

代码展示之具体讲解

接下来要做两件事: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) {
    // 开始统计的代码
    // ... 逻辑代码
    // 结束统计的代码
}

估计就是这样。麻不麻烦?啰不啰嗦?

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值