假如现在有一个需求,在对数据库进行增删改查的时候,假如执行每个操作之前都要求把数据备份一下。这个时候怎么做比较好呢,难道要在每个方法之前都写一个save()方法吗,如果用到增删改查的地方非常多,这时候就非常麻烦了。
通过java中的动态代理就可以很方便的实现。比如
首先有个操作数据库的类
public interface DBOperation {
int save();
int delete();
int insert();
Object get();
}
定义一个activity,实现数据库操作接口,通过Proxy.newProxyInstance方法创建出DBOperation的代理实现类,这个方法需要一个InvocationHandler参数,
自定义一个InvocationHandler,在其invoke方法中我们就可以在执行每个方法之前和之后做一些自己的操作了。
public class ProxyActivity extends AppCompatActivity implements DBOperation{
private final static String TAG = "myTag >>> ";
DBOperation db;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_proxy);
db = (DBOperation) Proxy.newProxyInstance(DBOperation.class.getClassLoader()
,new Class[]{DBOperation.class},new DBHandler(this));
}
public void action(View view) {
db.delete();
}
class DBHandler implements InvocationHandler{
DBOperation db;
public DBHandler(DBOperation db) {
this.db = db;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (db != null) {
Log.e("TAG","before");
save();
Log.e("TAG","after");
return method.invoke(db,args);
}
return null;
}
}
@Override
public int save() {
Log.e(TAG, "保存数据");
return 0;
}
@Override
public int delete() {
Log.e(TAG, "删除数据");
return 0;
}
@Override
public int insert() {
return 0;
}
@Override
public Object get() {
return null;
}
}
上面的代码点击执行action方法,执行结果如果下
2019-07-02 22:49:55.296 7516-7516/com.chs.architecturetest E/TAG: before 2019-07-02 22:49:55.296 7516-7516/com.chs.architecturetest E/myTag >>>: 保存数据 2019-07-02 22:49:55.296 7516-7516/com.chs.architecturetest E/TAG: after 2019-07-02 22:49:55.297 7516-7516/com.chs.architecturetest E/myTag >>>: 删除数据
在项目开发中,我们经常会遇到这样的需求
我们不可能去每个方法中都写相关的统计代码,如果类很多的情况下会麻烦死还容易出错,如果使用动态代理也是比较麻烦的,这时候我们可以使用AspectJ。
AspectJ是一个面向切面的框架,它扩展了Java语言。AspectJ定义了AOP语法,它有一个专门的编译器用来生成遵守Java字节编码规范的Class文件。
下面使用它来解决前面的两个问题
首先配置AspectJ
app下的build.gralde中添加依赖
implementation 'org.aspectj:aspectjrt:1.8.13'
工程的build.gralde中添加classpath AspectJ还需要添加maven的依赖
dependencies {
classpath 'com.android.tools.build:gradle:3.4.1'
classpath 'org.aspectj:aspectjtools:1.8.10'
classpath 'org.aspectj:aspectjweaver:1.8.10'
}
buildscript {
repositories {
mavenCentral()
}
最后app下的build.gralde中添加AspectJ的编译代码,在dependencies同级添加。
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.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;
}
}
}
}
OK配置完毕下面开始解决第一个行为统计的问题
首先定义一个注解ClickBehavior,运行时注解,作用在方法上,并且有一个参数代表需要统计的行为的名称
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ClickBehavior {
String value();
}
然后定义一个切面类 ClickBehaviorAspectJ
@Aspect//定义切面类
public class ClickBehaviorAspectJ {
private final static String TAG = "myTag >>> ";
//execution 定义切入点
//* *(..)) 通配符 可以处理所有ClickBehavior注解的方法
@Pointcut("execution(@com.chs.architecturetest.annotation.ClickBehaviorAspectJ * *(..))")
public void methodPointCut() {}
//对切入点方法应该如何处理 环绕通知 切入点之前和之后需要做的的事情
@Around("methodPointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
//获取签名方法
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
//获取方法名
String methodName = signature.getName();
//获取class名
String className = signature.getDeclaringType().getSimpleName();
//获取需要统计的value值
String funName = signature.getMethod().getAnnotation(com.chs.architecturetest.annotation.ClickBehavior.class).value();
//当前时间
long begin = System.currentTimeMillis();
Log.e(TAG,"ClickBehaviorAspectJ Method Before");
Object proceed = joinPoint.proceed();
Log.e(TAG,"ClickBehaviorAspectJ Method End");
//执行时间
long duration = System.currentTimeMillis() - begin;
Log.e(TAG, String.format("统计了:%s功能,在%s类的%s方法,用时%d ms",
funName, className, methodName, duration));
return proceed;
}
}
这里面有几个注解,一般用前三个就能完成
("execution(com.chs.architecturetest.MainActivity *(..))")
,或者整个工程中的所有方法 ("execution(* *(..))")
//execution(<修饰符模式>? <返回类型模式> <方法名模式>(<参数模式>) <异常模式>?) 在Activity中整3个按钮分别为登录,VIP,账户,并设置点击方法。给这几个点击方法设置行为点击注解
@ClickBehavior("VIP页面")
public void goToVip(View view) {
Log.e(TAG,"去VIP页面");
startActivity(new Intent(this,OtherActivity.class));
}
@ClickBehavior("账户页面")
public void goToZh(View view) {
Log.e(TAG,"去账户页面");
startActivity(new Intent(this,OtherActivity.class));
}
@ClickBehavior("登录页面")
public void goToLogin(View view) {
Log.e(TAG,"去登录页面");
}
OK完成到这里行为统计就完成了,执行带@ClickBehavior注解的方法都会执行统计的代码, 比如点击登录按钮打印日志
2019-07-02 23:23:48.639 8640-8640/com.chs.architecturetest E/myTag >>>: ClickBehaviorAspectJ Method Before 2019-07-02 23:23:48.639 8640-8640/com.chs.architecturetest E/myTag >>>: 去登录页面 2019-07-02 23:23:48.639 8640-8640/com.chs.architecturetest E/myTag >>>: ClickBehaviorAspectJ Method End 2019-07-02 23:23:48.640 8640-8640/com.chs.architecturetest E/myTag >>>: 统计了:登录页面功能,在ProxyActivity类的goToLogin方法,用时0 ms
检查登录的功能
首先写一个注解ClickBehavior。它不需要有值
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LoginBehavior {
}
定义登录的AspectJ类
@Aspect//定义切面类
public class LoginAspectJ {
private final static String TAG = "myTag >>> ";
//execution 定义切入点
//* *(..)) 通配符 可以处理所有ClickBehavior注解的方法
@Pointcut("execution(@com.chs.architecturetest.annotation.LoginBehavior * *(..))")
public void methodPointCut() {}
//对切入点方法应该如何处理 环绕通知 切入点之前和之后需要做的的事情
@Around("methodPointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
//是否登录真实项目中去sharedprefrence中去那
Context context = (Context) joinPoint.getThis();
if(false){
Log.e(TAG, "检测到已登录!");
return joinPoint.proceed();
}else {
Log.e(TAG, "检测到没有登录!");
context.startActivity(new Intent(context,LoginActivity.class));
return null;
}
}
}
在around方法中就可以执行判断是否登录的逻辑了,真实项目中一般都是从SharedPreferences中拿到数据判断是否登录。
最后给需要判断登录状态的地方添加@LoginBehavior注解
@LoginBehavior
@ClickBehavior("VIP页面")
public void goToVip(View view) {
Log.e(TAG,"去VIP页面");
startActivity(new Intent(this,OtherActivity.class));
}
@LoginBehavior
@ClickBehavior("账户页面")
public void goToZh(View view) {
Log.e(TAG,"去账户页面");
startActivity(new Intent(this,OtherActivity.class));
}
@ClickBehavior("登录页面")
public void goToLogin(View view) {
Log.e(TAG,"去登录页面");
}
比如这里将前面代码if判断中直接改为false,点击去VIP页面的按钮测试结果如下,会跳转到到登录页面
2019-07-02 23:26:00.057 8640-8640/com.chs.architecturetest E/myTag >>>: ClickBehaviorAspectJ Method Before 2019-07-02 23:26:00.058 8640-8640/com.chs.architecturetest E/myTag >>>: 检测到没有登录! 2019-07-02 23:26:00.067 8640-8640/com.chs.architecturetest E/myTag >>>: ClickBehaviorAspectJ Method End 2019-07-02 23:26:00.068 8640-8640/com.chs.architecturetest E/myTag >>>: 统计了:VIP页面功能,在ProxyActivity类的goToVip方法,用时10 ms
OK完成啦