1,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP在编程历史上可以说是里程碑式的,对OOP编程是一种十分有益的补充。AOP像OOP一样,只是一种编程方法论,AOP并没有规定说,实现AOP协议的代码,要用什么方式去实现。OOP侧重静态,名词,状态,组织,数据,载体是空间;AOP侧重动态,动词,行为,调用,算法,载体是时间;
2,介绍:AspectJ是一个面向切面编程的框架。AspectJ是对java的扩展,而且是完全兼容java的,AspectJ定义了AOP语法,它有一个专门的编译器用来生成遵守Java字节编码规范的Class文件。AspectJ还支持原生的Java,只需要加上AspectJ提供的注解即可。在Android开发中,一般就用它提供的注解和一些简单的语法就可以实现绝大部分功能上的需求了。
在Java文件转化为class文件的时候,可以对这个步骤进行操控,最终使得class文件与Java源文件有所不同
进入代码环节。
比如我们有一个需求,统计某个模块的耗时时间,一般情况下,我们都会这么写
package com.example.aopjiagoushejitest;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.os.SystemClock;
import android.util.Log;
import android.view.View;
import java.util.Random;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void mShake(View view) {
long begin = System.currentTimeMillis();
//随机休眠两毫秒以内
SystemClock.sleep(new Random().nextInt(2000));
long duringTime = System.currentTimeMillis() - begin;
Log.i("zhang_xin",duringTime+"");
}
public void mVideo(View view) {
long begin = System.currentTimeMillis();
//随机休眠两毫秒以内
SystemClock.sleep(new Random().nextInt(2000));
long duringTime = System.currentTimeMillis() - begin;
Log.i("zhang_xin",duringTime+"");
}
public void mAudio(View view) {
long begin = System.currentTimeMillis();
//随机休眠两毫秒以内
SystemClock.sleep(new Random().nextInt(2000));
long duringTime = System.currentTimeMillis() - begin;
Log.i("zhang_xin",duringTime+"");
}
public void saySomething(View view) {
long begin = System.currentTimeMillis();
//随机休眠两毫秒以内
SystemClock.sleep(new Random().nextInt(2000));
long duringTime = System.currentTimeMillis() - begin;
Log.i("zhang_xin",duringTime+"");
}
}
布局就是四个按钮,这样写是不是太麻烦了,我们现在换成我们的面向切面编程。
首先配置我们的环境
apply plugin: 'com.android.application'
android {
compileSdkVersion 29
buildToolsVersion "29.0.2"
defaultConfig {
applicationId "com.example.aopjiagoushejitest"
//版本至少为24
minSdkVersion 24
targetSdkVersion 29
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.1.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.1.1'
//添加依赖
implementation 'org.aspectj:aspectjrt:1.9.4'
}
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main
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.9",
"-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;
}
}
}
}
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.3'
//配置自己的构建环境
classpath 'org.aspectj:aspectjtools:1.9.4'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
定义的注解
package com.example.aopjiagoushejitest;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)//作用域
@Retention(RetentionPolicy.RUNTIME)//运行时
public @interface BehaviorTrace {
String value();
}
MainActivity
package com.example.aopjiagoushejitest;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.os.SystemClock;
import android.util.Log;
import android.view.View;
import java.lang.annotation.Retention;
import java.util.Random;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@BehaviorTrace("shake")//切入点
public void mShake(View view) {
}
@BehaviorTrace("video")//切入点
public void mVideo(View view) {
}
@BehaviorTrace("audio")//切入点
public void mAudio(View view) {
}
@BehaviorTrace("something")//切入点
public void saySomething(View view) {
}
}
关键类
package com.example.aopjiagoushejitest;
import android.os.SystemClock;
import android.util.Log;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import java.util.Random;
//Aspect切入面:是切入点和通知的结合,加了这个标记,这个类就是一个切面了
@Aspect
public class BehaviorTraceAspect {
//在切面中定义些规则
//1,知道哪些切入点需要进行性能统计,即哪些方法添加了@BehaviorTrace注解
//execution(注解名 注解用的地方)第一个*表示所有的类,第二个*表示所有的方法 ..表示无限参数
@Pointcut("execution(@com.example.aopjiagoushejitest.BehaviorTrace * *(..))")
public void methodAnnottatedWithBehaviorTrace() {
//添加该注解BehaviorTrace的地方都会执行这个方法
}
//2,对进入切面的注解(内容)如何处理
//@Before 在切入点之前运行
// @After 在切入点之后运行
//@Around 在切入点前后都运行
@Around("methodAnnottatedWithBehaviorTrace()")
public Object weaveJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {
//通过链接点拿到方法的签名
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
//拿到类名
String className = methodSignature.getDeclaringType().getSimpleName();
//拿到方法名
String methodName = methodSignature.getName();
//拿到注解对应的值
String annoationName = methodSignature.getMethod().getAnnotation(BehaviorTrace.class).value();
long begin = System.currentTimeMillis();
Object result = joinPoint.proceed();//表示当前方法(有注释的方法)的执行
//随机休眠两毫秒以内
SystemClock.sleep(new Random().nextInt(2000));
long duringTime = System.currentTimeMillis() - begin;
Log.d("zhang_xin", String.format("%s功能:%s类的%s方法执行了,用时%d ms",
annoationName, className, methodName, duringTime));
return result;
}
}
大家回忆下我们使用的淘宝APP,当我们点击某些按钮的时候,他会提示你还未登录,你需要登录。我们常用的做法是你点击按钮之前判断用户是否登录,如果没有登录就跳转到登录界面。这样就会造成很多地方都会写这个判断逻辑。我们现在通过我们的AOP编程来实现这个功能,我们在需要登录的功能作为切入点,把这些切入点放入一个切面内统一管理。
我们现在先用动态代理的方式来做登录
package com.example.logintest;
public interface ILogin {
void toLogin();
}
package com.example.logintest;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;
import java.lang.reflect.Proxy;
public class MainActivity extends AppCompatActivity implements ILogin{
private ILogin proxyLogin;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
/**
* 第一个参数:类加载器
* 第二个参数:代理对象的目标类
* 第三个参数:回调处理类
*/
proxyLogin = (ILogin) Proxy.newProxyInstance(this.getClassLoader(),new Class[]{ILogin.class},new MyHandler(this,this));
}
public void me(View view) {
proxyLogin.toLogin();
}
public void play(View view) {
proxyLogin.toLogin();
}
@Override
public void toLogin() {
Toast.makeText(this,"已登录,跳转到个人中心界面",Toast.LENGTH_LONG).show();
}
}
package com.example.logintest;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
public class LoginActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
}
public void loginEvent(View view) {
SharePreferenceUtil.setBooleanSp(SharePreferenceUtil.IS_LOGIN, true, this);
Toast.makeText(this, "登陆成功", Toast.LENGTH_SHORT).show();
finish();
}
}
package com.example.logintest;
import android.content.Context;
import android.content.Intent;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class MyHandler implements InvocationHandler {
private Object target;
private Context context;
public MyHandler(Object target, Context context) {
this.target = target;
this.context = context;
}
/**
* 拦截Object对象的所有方法
* @param proxy
* @param method
* @param args
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object ob = null;
//如果已经登陆了
if(SharePreferenceUtil.getBooleanSp(SharePreferenceUtil.IS_LOGIN,context)){
//调用拦截的方法
ob = method.invoke(target,args);
}else{
Intent intent = new Intent(context,LoginActivity.class);
context.startActivity(intent);
}
return ob;
}
}
package com.example.logintest;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
public class SharePreferenceUtil {
public static final String IS_LOGIN = "isLogin";
public static boolean getBooleanSp(String key, Context context) {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
return preferences.getBoolean(key, false);
}
public static void clearSharePref(String key, Context context) {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = preferences.edit();
editor.remove(key);
editor.apply();
}
/* Boolean */
public static void setBooleanSp(String key, Boolean value, Context context) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = prefs.edit();
editor.putBoolean(key, value);
editor.apply();
}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".LoginActivity">
<Button
android:text="登录"
android:onClick="loginEvent"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="me"
android:text="我的淘宝"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="play"
android:text="购买"/>
</LinearLayout>
我们看下AOP架构如何实现登录
package com.dn_alan.myapplication.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginFilter {
int userDefine() default 0;
}
package com.example.aopjiagoushejitest;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
//这里我就直接定义一个变量控制登录与否,就不写SharedPreference了
public static boolean isLogin ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void quit(View view) {
}
//如果已经登录,跳转到我的购物车界面,这里我就直接弹出一个吐丝
//如果没有登录,跳转到登录界面
@LoginFilter
public void myShoppingCar(View view) {
Toast.makeText(this,"这是我的购物车界面",Toast.LENGTH_LONG).show();
isLogin = false;
}
}
package com.example.aopjiagoushejitest;
import android.content.Context;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
/**
* 登录切面
*/
@Aspect
public class LoginFilterAspect {
@Pointcut("execution(@com.example.aopjiagoushejitest.LoginFilter * *(..))")
public void loginFilter() {
}
@Around("loginFilter()")
public void arroundLoginFilter(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
ILogin iLogin = LoginAssistant.getInstance().getiLogin();
if (iLogin == null) {
return;
}
Signature signature = proceedingJoinPoint.getSignature();
if (signature instanceof MethodSignature) {
MethodSignature methodSignature = (MethodSignature) signature;
LoginFilter loginFilter = methodSignature.getMethod().getAnnotation(LoginFilter.class);
if(loginFilter!=null){
Context param = LoginAssistant.getInstance().getApplicationContext();
if (iLogin.isLogin(param)) {
proceedingJoinPoint.proceed();
} else {
iLogin.login(param, loginFilter.userDefine());
}
}else{
//抛异常
}
}else{
//抛异常,这里省去
}
}
}
上面三各类是主要的类,看下其它的类在
package com.example.aopjiagoushejitest;
import android.app.Application;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
LoginSDK.getInstance().init(this, iLogin);
}
ILogin iLogin = new ILogin() {
@Override
public void login(Context contextApplication, int define) {
switch (define) {
case 0:
Intent intent = new Intent(contextApplication,LoginActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
break;
case 1:
Toast.makeText(contextApplication, "您还没有登录,请登陆后执行", Toast.LENGTH_SHORT).show();
break;
default:
Toast.makeText(contextApplication, "您还没有登录,请登陆后执行", Toast.LENGTH_SHORT).show();
break;
}
}
@Override
public boolean isLogin(Context contextApplication) {
return MainActivity.isLogin;
}
@Override
public void clearLoginStatue(Context contextApplication) {
MainActivity.isLogin = false;
}
};
}
package com.example.aopjiagoushejitest;
import android.content.Context;
public class LoginSDK {
private Context contextApplication;
private LoginSDK() {
}
private static class Helper {
private static LoginSDK loginSDK = new LoginSDK();
}
public static LoginSDK getInstance() {
return Helper.loginSDK;
}
public void init(Context context,ILogin iLogin){
contextApplication = context.getApplicationContext();
LoginAssistant.getInstance().setApplicationContext(context);
LoginAssistant.getInstance().setiLogin(iLogin);
}
/**
* 单点登录,验证token失效的统一接入入口
*/
public void serverTokenInvalidation(int userDefine) {
LoginAssistant.getInstance().serverTokenInvalidation(userDefine);
}
}
package com.example.aopjiagoushejitest;
import android.content.Context;
public class LoginAssistant {
private LoginAssistant() {
}
private static class Helper {
private static LoginAssistant loginAssistant = new LoginAssistant();
}
public static LoginAssistant getInstance() {
return Helper.loginAssistant;
}
private ILogin iLogin;
private Context applicationContext;
public ILogin getiLogin() {
return iLogin;
}
public void setiLogin(ILogin iLogin) {
this.iLogin = iLogin;
}
public Context getApplicationContext() {
return applicationContext;
}
public void setApplicationContext(Context applicationContext) {
this.applicationContext = applicationContext;
}
public void serverTokenInvalidation(int userDefine){
if(iLogin == null){
return;
}
iLogin.clearLoginStatue(applicationContext);
iLogin.login(applicationContext,userDefine);
}
}
package com.example.aopjiagoushejitest;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
public class LoginActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
}
public void login(View view) {
MainActivity.isLogin = true;
this.finish();
}
}
package com.example.aopjiagoushejitest;
import android.content.Context;
public interface ILogin {
void login(Context contextApplication,int define);
boolean isLogin(Context contextApplication);
void clearLoginStatue(Context contextApplication);
}
AspectJ 切面注解中五种通知注解:@Before、@After、@AfterRunning、@AfterThrowing、@Around