Aspect J之登录权限处理
背景
对于项目中,有些业务场景需要在登录的条件下才允许进行,如果未登录则需要先跳转到登录界面,进行登录操作。登录完成后再进行后续业务流程处理。常规写法为每次执行业务逻辑代码之前,都需要做如下代码判断:
if (!isLogined){
goToLoginActivity();
}else {
doSomething();
}
这么写在代码逻辑上没什么问题,但是会存在各种if else判断,不符合代码解耦原则。有没有方法可以不用做这些判断,能自动完成这种登录的业务逻辑处理 呢? 接下来该Aspect J出场了.
AOP
面向切面编程(AOP,Aspect-oriented programming):是一种可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的技术。AOP是OOP的延续,是软件开发中的一个热点,是函数式编程的一种衍生范型,将代码切入到类的指定方法、指定位置上的编程思想。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
AOP编程的主要用途有:日志记录,行为统计,安全控制,事务处理,异常处理,系统统一的认证、权限管理等。可以使用AOP技术将这些代码从业务逻辑代码中划分出来,通过对这些行为的分离,可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。
AOP编程的常见的使用场景:
- 登录状态判断
- 日志记录
- 持久化
- 行为监测
- 数据验证
- 缓存
- 事件防抖
- ...
以下以Aspect 为例,介绍用AOP的方式处理登录权限问题
接入步骤
- 在工程主目录下添加
repositories {
}
dependencies {
classpath 'org.aspectj:aspectjtools:1.8.9'
classpath 'org.aspectj:aspectjweaver:1.8.9'
}
- app的build.gradle
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main
final def log = project.logger
android.applicationVariants.all{ variant ->
if (!variant.buildType.isDebuggable()) {
log.debug("Skipping non-debuggable build type '${variant.buildType.name}'.")
return
}
JavaCompile javaCompile = variant.javaCompiler
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
}
}
}
}
- 定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CheckLogin {
String value() default "";
}
- 编写AspectJ切面代码
@Aspect
public class CheckLoginAspectJ {
/**
*
* Advice:就是我们插入的代码可以以何种方式插入,有Before、 After、Around 三种类型
*
*
* execution()是表达式主体
第一个*号代表返回类型,*号代表所有的类型。
包名 表示需要拦截的包名,这里使用*.代表匹配所有的包名。
第二个*号表示类名,后面跟.MainActivity是指具体的类名叫MainActivity。
*(..) 最后这个星号表示方法名,+.代表具体的函数名,*号通配符,包括括弧号里面表示方法的参数,两个dot代表任意参数。
*/
/**
* 第一个*表示返回值,*表示返回值为任意类型,后面这个就是典型的包名路径
*/
@Pointcut("execution(@com.apache.fastandroid.annotations.CheckLogin * *(..))")
public void executeCheckLogin(){
}
@Around("executeCheckLogin()")
public Object checkLogin111(ProceedingJoinPoint point) throws Throwable {
MainLogUtil.d("checkLogin Around----->"+point.getSignature().toString() +", target = "+point.getTarget() +", args = "+ point.getArgs());
MethodSignature methodSignature = (MethodSignature) point.getSignature();
CheckLogin checkLogin = methodSignature.getMethod().getAnnotation(CheckLogin.class);
if (checkLogin != null){
if (AppContext.isLogined()){
MainLogUtil.d("已登录,直接跳转 value = %s", checkLogin.value());
Object result = point.proceed();
MainLogUtil.d("登录后干点啥事.....");
return result;
}else {
Object object = point.getThis();
MainLogUtil.d("未登录,先跳转登录页面 object = "+ object);
Context context = (Context) point.getThis();
context.startActivity(new Intent(context,LoginActivity.class));
return null;
}
}else {
MainLogUtil.d("未添加CheckLogin注解,啥也不用干");
Object result = point.proceed();
return result;
}
}
}
- 在需要做切面处理的地方加上注解
/**
* 加上这个注解就会自动判断是否已登录,如果未登录则会先跳转到登录页面
* @param itemId
* @param title
*/
@CheckLogin("")
public void goToTopicActivity(int itemId, String title){
MainLogUtil.d("goToTopicActivity");
Fragment fragment = WallPaperFragment.newFragment();
if (fragment != null && selecteId != itemId){
getSupportActionBar().setTitle(title);
getSupportFragmentManager().beginTransaction().replace(R.id.lay_content,fragment, "MainFragment").commit();
}
closeDrawer();
selecteId = itemId;
}
运行app,当点击某个按钮执行goToTopicActivity()方法的时候,就会去执行CheckLoginAspectJ中的检查是否已登录的代码, so easy!
Gradle插件接入方式
大家应该发现aspect配置还是比较麻烦的,需要在build.gradle文件中写一大堆代码,不过别担心,网上已经有大牛把这些模板化代码做成了插件,作为开发者来说直接引入gradle插件就可以了.
- 在工程主目录下添加
repositories {
}
dependencies {
classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.0'
}
- app的build.gradle
apply plugin: 'android-aspectjx'
搞定!
网友评论