AOP 介绍
OOP为 Object Oriented Programming,面向对象编程 把功能或问题模块化,每个模块处理自己的家务事。
AOP为 Aspect Oriented Programming 的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
- AOP的特点:
1、Aspect Oriented Programming 面向切面编程 通过预编译方式和运行期动态代理实现程序功能的统一维护
2、在运行时,编译时,类加载期,动态地将代码切入到类的指定方法、指定位置上的编程思想。
3、AOP在编程历史上可以说是里程碑式的,对OOP编程是一种十分有益的补充。
4、AOP像OOP一样,只是一种编程方法论,AOP并没有规定说,实现AOP协议的代码,要用什么方式去实现。
5、OOP侧重静态,名词,状态,组织,数据,载体是空间;
6、AOP侧重动态,动词,行为,调用,算法,载体是时间;
- 我们通过 AspectJ 来实现AOP编程的设计。
介绍:AspectJ是一个面向切面编程的框架。AspectJ是对java的扩展,而且是完全兼容java的,AspectJ定义了AOP语法,它有一个专门的编译器用来生成遵守Java字节编码规范的Class文件。AspectJ还支持原生的Java,只需要加上AspectJ提供的注解即可。在Android开发中,一般就用它提供的注解和一些简单的语法就可以实现绝大部分功能上的需求了。
- AOP 中的术语
AOP术语 | 解释 |
---|---|
Joinpoint(连接点) | 所谓连接点是指那些被拦截到的点 |
Pointcut(切入点) | 所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义 |
Advice(通知/增强) | 所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知 |
Introduction(引介) | 引介是一种特殊的通知在不修改类代码的前提下, Introduction 可以在运行期为类 动态地添加一些方法或 Field |
Target(目标对象) | 代理的目标对象 |
Weaving(织入) | 是指把增强应用到目标对象来创建新的代理对象的过程. AspectJ 采用编译期织入和类装在期织入 |
Proxy(代理) | 一个类被 AOP 织入增强后,就产生一个结果代理类 |
Aspect(切面) | 是切入点和通知(引介)的结合 |
- Advice分类
Advice分类 | 解释 |
---|---|
Before | 前置通知, 在目标执行之前执行通知 |
After | 后置通知, 目标执行后执行通知 |
Around | 环绕通知, 在目标执行中执行通知, 控制目标执行时机 |
AfterReturning | 后置返回通知, 目标返回时执行通知 |
AfterThrowing | 异常通知, 目标抛出异常时执行通知 |
- 切入点指示符
切入点指示符 | 解释 |
---|---|
execution | 用于匹配方法执行的连接点 |
within | 用于匹配指定类型内的方法执行 |
this | 用于匹配当前AOP代理对象类型的执行方法;注意是AOP代理对象的类型匹配,这样就可能包括引入接口也类型匹配 |
target | 用于匹配当前目标对象类型的执行方法;注意是目标对象的类型匹配,这样就不包括引入接口也类型匹配 |
args | 用于匹配当前执行的方法传入的参数为指定类型的执行方法 |
@within | 用于匹配所以持有指定注解类型内的方法 |
@target | 用于匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解 |
@args | 用于匹配当前执行的方法传入的参数持有指定注解的执行 |
@annotation | 用于匹配当前执行方法持有指定注解的方法 |
AOP实例
案例1:下面是通过 AOP 实现性能统计的实例。此例子为统计不同方法模块执行的时间统计
- 首先项目引入 AspectJ
添加依赖
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.core:core-ktx:1.2.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
implementation 'org.aspectj:aspectjrt:1.9.5'
}
//>>>>>>>>>>>>>>>>>>>Aspectjrt 在APP主工程下使用的配置>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
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.9.5'
}
}
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.5",
"-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 BehaviorTrace {
String value() default "";
}
- 新建切面类,用来处理统计的逻辑
// 这个注解说明了此类为切面
@Aspect
public class BehaviorTraceAspect {
//定义切面的规则
//1、就再原来的应用中那些注解的地方放到当前切面进行处理
//execution(注解名 注解用的地方) --- 固定写法 *(..) 为全部地方 的 全部注解
@Pointcut("execution(@com.example.myapplication.ann.BehaviorTrace * *(..))")
public void BehaviorTraceAspectMethod(){
}
//2、对进入切面的内容如何处理
//@Before 在切入点之前运行
//@After("methodAnnottatedWithBehaviorTrace()")
//@Around 在切入点前后都运行
@Around("BehaviorTraceAspectMethod()")
public Object weaveJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {
// 获取 MethodSignature
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
String className = methodSignature.getDeclaringType().getSimpleName();
String methodName = methodSignature.getName();
String value = methodSignature.getMethod().getAnnotation(BehaviorTrace.class).value();
Log.d("weaveJoinPoint","方法执行前");
long begin = System.currentTimeMillis();
// 这个方法调用后执行标记注解的方法(连接点的执行),否则在 @Around时方法内部不会执行
Object result = joinPoint.proceed();
long duration = System.currentTimeMillis() - begin;
Log.d("weaveJoinPoint", String.format("%s功能:%s类的%s方法执行完成,用时%d ms",
value, className, methodName, duration));
return result;
}
}
- 使用方法,假设统计几个按钮点击后方法的执行时间。
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
/**
* 摇一摇
*/
@BehaviorTrace("shark")
public void shark(View view) {
Log.d(TAG, "执行了shark方法");
SystemClock.sleep(new Random().nextInt(2000));
}
/**
* 扫一扫
*/
@BehaviorTrace("scan")
public void scan(View view) {
}
/**
* 朋友圈
*/
@BehaviorTrace("circleOfFriends")
public void circleOfFriends(View view) {
}
/**
* 小程序
*/
@BehaviorTrace("miniApp")
public void miniApp(View view) {
}
}
- 这就是aop编程简单的实例,这种编程方式有很多可以用的地方,比如我们做事件统计,只需要在需要统计的点击方法上添加自定义的注解,然后可以使用 @before 或者 @after 在方法调用前后者后去做统计操作。
只要再添加一个注解就可以同时统计时间和点击统计了,解析的时候和上面基本一直,定义切面,定义规则等。
/**
* 摇一摇
*/
@BehaviorTrace("shark")
@ClickBehaviorTrace("统计点击了摇一摇")
public void shark(View view) {
Log.d(TAG, "执行了shark方法");
SystemClock.sleep(new Random().nextInt(2000));
}
- 实际上是框架在内部给生成了一些代码。具体的在 对应的build下
-app/build/intermediates/javac/debug/classes/com/example/myapplication/MainActivity.class
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
public MainActivity() {
}
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(2131361820);
}
@BehaviorTrace("shark")
public void shark(View view) {
JoinPoint var3 = Factory.makeJP(ajc$tjp_0, this, this, view);
shark_aroundBody1$advice(this, view, var3, BehaviorTraceAspect.aspectOf(), (ProceedingJoinPoint)var3);
}
// 。。。。
}
案例2:一般应用都有点击一些功能的时候未登录的话跳转登录的需求,一般写法就是
public void scan(View view) {
if (isLogin){
toDoSomething()
}else{
toLogin()
}
}
下面用 aop 思想去实现。
实现方式1: 使用动态代理实现;
- 新建一个 ILogin 接口
public interface ILogin {
void login();
}
- 新建一个代理Handler类
public class LoginInvocationHandler implements InvocationHandler {
private Object target;
private Context context;
public LoginInvocationHandler( Context context) {
this.target = context;
this.context = context;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
// 判断是否登录的逻辑 我这里简单用全局静态变量去记录了一下 跳转到登录页面 状态改为 true 默认为false
if (ContactValue.isLogin){
// 登录了执行代理实现的方法
result = method.invoke(target,args);
}else{
// 未登录去登录
Intent intent = new Intent(context, LoginActivity.class);
context.startActivity(intent);
}
return result;
}
}
- 调用方式
public class MainActivity extends AppCompatActivity implements ILogin {
private static final String TAG = "MainActivity";
private ILogin loginProxy;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
/**
* 第一个参数:类加载器
* 第二个参数:代理对象的目标类
* 第三个参数:回调处理类
*/
loginProxy = (ILogin) Proxy.newProxyInstance(getClassLoader(), new Class[]{ILogin.class}, new LoginInvocationHandler(this));
}
public void shark(View view) {
loginProxy.login();
}
/**
* 登录后跳转扫描功能
*/
public void scan(View view) {
loginProxy.login();
}
@Override
public void login() {
// 区分跳转到哪里 登录后根据反射调用到这个方法 具体跳转逻辑
Intent intent = new Intent(this, SearchActivity.class);
startActivity(intent);
}
}
实现方式2 :采用注解的 aop 方式
原理同上面讲的一样这里直接贴代码了:
- 注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginFilter {
int userDefine() default 0;
}
- 切面
@Aspect
public class LoginAspect {
@Pointcut("execution(@com.example.myapplication.ann.LoginFilterAnn * *(..))")
public void loginAspect() {
}
@Around("loginAspect()")
public void LoginAround(ProceedingJoinPoint joinPoint) throws Throwable {
Context context = LoginAssistant.getInstance().getContext();
ILogin iLogin = LoginAssistant.getInstance().getiLogin();
if (iLogin == null || context == null) {
throw new Exception("LoginSDK 未进行初始化...");
}
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
LoginFilterAnn loginFilterAnn = methodSignature.getMethod().getAnnotation(LoginFilterAnn.class);
if (loginFilterAnn == null) {
return;
}
if (iLogin.isLogin(context)){
joinPoint.proceed();
}else{
iLogin.login(context,loginFilterAnn.userDefine());
}
}
}
- 定义登录接口
public interface ILogin {
/**
* 登录事件接收
* @param applicationContext
* @param userDefine
*/
void login(Context applicationContext, int userDefine);
/**
* 判断是否登录
* @param applicationContext
* @return
*/
boolean isLogin(Context applicationContext);
/**
* 清楚登录状态
* @return
*/
void clearLoginStatus(Context applicationContext);
}
- 定义变量简单记录登录状态
public class ContactValue {
// 简单记录下登录状态
public static boolean isLogin = false;
}
- 定义登录的辅助类
public class LoginAssistant {
public static LoginAssistant getInstance() {
return SingleHolder.instance;
}
private static final class SingleHolder {
private final static LoginAssistant instance = new LoginAssistant();
}
private Context context;
private ILogin iLogin;
public void setContext(Context context) {
this.context = context.getApplicationContext();
}
public Context getContext() {
return context;
}
public ILogin getiLogin() {
return iLogin;
}
public void setiLogin(ILogin iLogin) {
this.iLogin = iLogin;
}
}
- 定义 SDK用于初始化
public class LoginSDK {
private LoginSDK() {
}
public static LoginSDK getInstance() {
return InstanceHolder.INSTANCE;
}
private final static class InstanceHolder {
private static LoginSDK INSTANCE = new LoginSDK();
}
public void init(Context context,ILogin login){
LoginAssistant.getInstance().setContext(context.getApplicationContext());
LoginAssistant.getInstance().setiLogin(login);
}
}
- MainActivity调用
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
/**
* 需要登录的功能1 点击事件
*/
@LoginFilterAnn()
public void shark(View view) {
// 随便写个跳转的页面
Intent intent = new Intent(this, SearchActivity.class);
startActivity(intent);
}
/**
* 需要登录的功能2 点击事件
*/
@LoginFilterAnn(userDefine = 1)
public void scan(View view) {
// 随便写个跳转的页面
Intent intent = new Intent(this, SettingActivity.class);
startActivity(intent);
}
// 清除登录状态 点击事件
public void clear(View view) {
ContactValue.isLogin = false;
}
}
- Application中初始化
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
init();
}
private void init() {
ILogin iLogin = new ILogin() {
@Override
public void login(Context applicationContext, int userDefine) {
switch (userDefine) {
case 0:
Intent intent = new Intent(applicationContext, LoginActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
applicationContext.startActivity(intent);
break;
case 1:
Toast.makeText(applicationContext, "请登录", Toast.LENGTH_SHORT).show();
break;
case 2:
Toast.makeText(applicationContext, "2", Toast.LENGTH_SHORT).show();
break;
}
}
@Override
public boolean isLogin(Context applicationContext) {
return ContactValue.isLogin;
}
@Override
public void clearLoginStatus(Context applicationContext) {
ContactValue.isLogin = false;
}
};
LoginSDK.getInstance().init(this, iLogin);
}
}
效果就是直接在点击跳转事件上添加注解,就可以统一处理是否登录的跳转逻辑了。
最后那个例子下载源码点击这里