一、什么是AOP
AOP是Aspect Oriented Programming的缩写,即『面向切面编程』。它和我们平时接触到的OOP都是编程的不同思想,OOP,即『面向对象编程』,它提倡的是将功能模块化,对象化,而AOP的思想,则不太一样,它提倡的是针对同一类问题的统一处理,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
二、AspectJ
适用于Aop的兼容java的一套语言
三、Aop基础知识点
术语
通知、增强处理(Advice) 就是你想要的功能。你给先定义好,然后再想用的地方用一下。包含Aspect的一段处理代码
连接点(JoinPoint)
所有可以被通知的地方,基本每个方法的钱、后(两者都有也行),或抛出异常是时都可以是连接点,spring只支持方法连接点。其他如AspectJ还可以让你在构造器或属性注入时都行,不过那不是咱们关注的,只要记住,和方法有关的前前后后都是连接点。切入点(Pointcut) 假如你的一个类里,有15个方法,那就有十几个连接点,但是你并不想在所有方法附件都使用通知(使用叫织入),你只是想让其中几个,在调用这几个方法之前、之后或者抛出异常时干点什么,那么就用切入点来定义这几个方法,让切点来筛选连接点,选中那几个你想要的方法。
切面(Aspect) 切面是通知和切入点的结合。现在发现了吧,没连接点什么事,链接点就是为了让你好理解切点搞出来的。
织入(weaving) 把切面应用到目标对象来创建新的代理对象的过程。
Aop注解
@Aspect:声明切面,标记类
@Pointcut(切点表达式):定义切点,规则定义
@Before(切点表达式):前置通知,切点之前执行
@Around(切点表达式):环绕通知,切点前后执行
@After(切点表达式):后置通知,切点之后执行
@AfterReturning(切点表达式):返回通知,切点方法返回结果之后执行
@AfterThrowing(切点表达式):异常通知,切点抛出异常时执行
1.@AfterThrowing通知与@AfterReturning通知是互斥的,在同个切点上不可能同时出现。
Aop切点表达式
基本模式
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
这里问号表示当前项可以有也可以没有,其中各项的语义如下:
- modifiers-pattern:方法的可见性,如public,protected;
- ret-type-pattern:方法的返回值类型,如int,void等;
- declaring-type-pattern:方法所在类的全路径名,如com.spring.Aspect;
- name-pattern:方法名类型,如buisinessService();
- param-pattern:方法的参数类型,如java.lang.String;
- throws-pattern:方法抛出的异常类型,如java.lang.Exception;
eg
execution(public * com.spring.service.BusinessObject.businessService(java.lang.String,..))
上述切点表达式将会匹配使用public修饰,返回值为任意类型,并且是com.spring.BusinessObject类中名称为businessService的方法,方法可以有多个参数,但是第一个参数必须是java.lang.String类型的方法。
通配符:
- *通配符,该通配符主要用于匹配单个单词,或者是以某个词为前缀或后缀的单词。
- ..通配符,该通配符表示0个或多个项,主要用于declaring-type-pattern和param-pattern中,如果用于declaring-type-pattern中,则表示匹配当前包及其子包,如果用于param-pattern中,则表示匹配0个或多个参数。
详细参考:
Spring AOP切点表达式用法总结
四、登陆案例
1.引入AspectJ
在build.gradle中的配置
apply plugin: 'com.android.application'
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'org.aspectj:aspectjtools:1.8.9'
classpath 'org.aspectj:aspectjweaver:1.8.9'
}
}
repositories {
mavenCentral()
}
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;
}
}
}
}
android {
compileSdkVersion 28
defaultConfig {
applicationId "com.example.lcc.aoplogin"
minSdkVersion 15
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:28.0.0-rc01'
implementation 'com.android.support.constraint:constraint-layout:1.1.2'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
compile 'org.aspectj:aspectjrt:1.8.9'
}
2.切面类
@Aspect
public class LoginAspect {
//定义筛选的规则,所有添加了CheckLogin注解的方法都执行逻辑
@Pointcut("execution(@com.example.lcc.aoplogin.annotation.CheckLogin * *(..))")
public void executionCheckLogin() {
}
/**
* 处理切面
* @param joinPoint
* @return
*/
@Around("executionCheckLogin()")
public Object checkLogin(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
CheckLogin checkLogin = signature.getMethod().getAnnotation(CheckLogin.class);
if (checkLogin != null) {
Context context = (Context) joinPoint.getThis();
if (MyApplication.isLogin) {
MyApplication.loginSdk.mLoginSuccess();
return joinPoint.proceed();
} else {
MyApplication.loginSdk.mLoginFail();
return null;
}
}
return joinPoint.proceed();
}
}
3.注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CheckLogin {
}
4.MyApp中,主要是处理不同登陆状态下的逻辑
package com.example.lcc.aoplogin;
import android.app.Application;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;
public class MyApplication extends Application {
public static boolean isLogin;
public static LoginInterface loginSdk;
private Context mContext;
@Override
public void onCreate() {
super.onCreate();
loginSdk = new LoginSdk();
mContext = getApplicationContext();
}
class LoginSdk implements LoginInterface {
@Override
public void mLoginSuccess() {
Toast.makeText(mContext, "登陆成功", Toast.LENGTH_SHORT);
}
@Override
public void mLoginFail() {
Intent intent=new Intent(mContext, LoginActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
}
}
5.接口
package com.example.lcc.aoplogin;
public interface LoginInterface {
//登陆成功
public void mLoginSuccess();
//登陆失败
public void mLoginFail();
}
6.mainActivity
package com.example.lcc.aoplogin;
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import com.example.lcc.aoplogin.annotation.CheckLogin;
public class MainActivity extends AppCompatActivity {
private Button mButton1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mButton1 = findViewById(R.id.button_skip_one);
MyApplication.isLogin = false;
mButton1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
test();
}
});
}
@CheckLogin
public void test() {
startActivity(new Intent(MainActivity.this, TwoActivity.class));
}
}
五、其他
1.JoinPoint的作用
这个参数包含了切点的所有信息
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
String name = signature.getName(); // 方法名:test
Method method = signature.getMethod(); // 方法:public void com.example.lcc.aoplogin.MainActivity.test(android.view.View)
Class returnType = signature.getReturnType(); // 返回值类型:void
Class declaringType = signature.getDeclaringType(); // 方法所在类名:MainActivity
String[] parameterNames = signature.getParameterNames(); // 参数名:view
Class[] parameterTypes = signature.getParameterTypes(); // 参数类型:View
CheckLogin checkLogin = signature.getMethod().getAnnotation(CheckLogin.class);// 通过Method对象得到切点上的注解
六、参考资料
Android面向切面编程(AOP)
【翻译】Android中的AOP编程
Android AOP面向切面编程详解
git源码
~~喵印