What is the AOP

In the software industry, AOP for the abbreviation of Aspect Oriented Programming, meaning: section-oriented Programming, through pre-compilation and runtime dynamic proxy to achieve unified maintenance of program functions of a technology. AOP is a continuation of OOP, a hot topic in software development, and a derived paradigm of functional programming. Using AOP, each part of the business logic can be isolated, thus reducing the degree of coupling between each part of the business logic, improving the reusability of the program, and improving the efficiency of development.

It’s a separation of concerns technique. We use the term “business logic” or “business function” a lot in software development, and our code is basically implementing a particular kind of business logic. But we often can’t focus on business logic. For example, we write business logic code at the same time as we write transaction management, caching, logging, and so on, and every business function has to be mixed up with these business functions, which is very, very painful. In order to separate the concerns of business function from those of generalization function, AOP technology emerged.

AOP and OOP

Object orientation is characterized by inheritance, polymorphism and encapsulation. In keeping with the principle of single responsibility, OOP spreads functionality across different objects. Let different classes design different methods, so that the code is scattered among each class. You can reduce code complexity and increase class reuse.

But while dispersing the code, it also increases the repetition of the code. For example, we might need to log in each method in both classes. With OOP design, we have to add logging content to the methods of both classes. Maybe they are identical, but because OOP is designed to make it impossible to relate classes to each other, it is impossible to unify these repetitive pieces of code. AOP, however, was created to solve this kind of problem. It is the programming idea of dynamically cutting code into a specified method, a specified location, of a class at runtime.

If procedural programming is one-dimensional, object-oriented programming is two-dimensional. OOP divides classes horizontally, adding a dimension to procedural. While aspect combined with object-oriented programming is three-dimensional, compared with object-oriented programming alone, it adds an “aspect” dimension. Technically, AOP is basically implemented through a proxy mechanism.





AOPConcept.JPG

Common use of AOP in Android development

The library I wrapped already Outlines common Android AOP uses

Github address: github.com/fengzhizi71…

0. Download and install

Add it in build.gradle in the root directory

buildscript {
     repositories {
         jcenter()
     }
     dependencies {
         classpath 'com. Hujiang. Aspectjx: gradle - android plugin - aspectjx: 1.0.8'}}Copy the code

Add it to build.gradle in the app module directory

apply plugin: 'com.hujiang.android-aspectjx'. dependencies { compile'com. Safframework: saf - aop: 1.0.0'. }Copy the code

1. Asynchronously execute the methods in app

Move away from Thread, Handler, BroadCoast, etc., and make it easier to execute asynchronous methods. Simply annotate @async on the target method

import android.app.Activity;
import android.os.Bundle;
import android.os.Looper;
import android.widget.Toast;

import com.safframework.app.annotation.Async;
import com.safframework.log.L;

/** * Created by Tony Shen on 2017/2/7. */

public class DemoForAsyncActivity extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        initData();
    }

    @Async
    private void initData(a) {

        StringBuilder sb = new StringBuilder();
        sb.append("current thread=").append(Thread.currentThread().getId())
                .append("\r\n")
                .append("ui thread=")
                .append(Looper.getMainLooper().getThread().getId());


        Toast.makeText(DemoForAsyncActivity.this, sb.toString(), Toast.LENGTH_SHORT).show(); L.i(sb.toString()); }}Copy the code

You can clearly see that the current thread is different from the UI thread.





@async Execution result.png

The principle of @async is as follows, using Rxjava to implement asynchronous methods.

import android.os.Looper;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

import rx.Observable;
import rx.Subscriber;
import rx.android.schedulers.AndroidSchedulers;
import rx.schedulers.Schedulers;

/** * Created by Tony Shen on 16/3/23. */
@Aspect
public class AsyncAspect {

    @Around("execution(! synthetic * *(..) ) && onAsyncMethod()")
    public void doAsyncMethod(final ProceedingJoinPoint joinPoint) throws Throwable {
        asyncMethod(joinPoint);
    }

    @Pointcut("@within(com.safframework.app.annotation.Async)||@annotation(com.safframework.app.annotation.Async)")
    public void onAsyncMethod(a) {}private void asyncMethod(final ProceedingJoinPoint joinPoint) throws Throwable {

        Observable.create(new Observable.OnSubscribe<Object>() {

            @Override
            public void call(Subscriber<? super Object> subscriber) {
                Looper.prepare();
                try {
                    joinPoint.proceed();
                } catch(Throwable throwable) { throwable.printStackTrace(); } Looper.loop(); } }).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(); }}Copy the code

2. Place the result returned by the method in the cache

I started by writing a CouchBase annotation for my company’s back-end project. The annotation is a custom annotation that combines Spring Cache and CouchBase to put the results returned by a method directly into CouchBase, simplifying The operation of CouchBase. Let developers focus more on the business code.

Inspired by this, I wrote an Android version of the annotations to see how they are used.

import android.app.Activity;
import android.os.Bundle;
import android.widget.Toast;

import com.safframework.app.annotation.Cacheable;
import com.safframework.app.domain.Address;
import com.safframework.cache.Cache;
import com.safframework.injectview.Injector;
import com.safframework.injectview.annotations.OnClick;
import com.safframework.log.L;
import com.safframwork.tony.common.utils.StringUtils;

/** * Created by Tony Shen on 2017/2/7. */

public class DemoForCacheableActivity extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_demo_for_cacheable);
        Injector.injectInto(this);

        initData();
    }

    @Cacheable(key = "address")
    private Address initData(a) {

        Address address = new Address();
        address.country = "China";
        address.province = "Jiangsu";
        address.city = "Suzhou";
        address.street = "Ren min Road";

        return address;
    }

    @OnClick(id={R.id.text})
    void clickText(a) {

        Cache cache = Cache.get(this);
        Address address = (Address) cache.getObject("address");
        Toast.makeText(this, StringUtils.printObject(address),Toast.LENGTH_SHORT).show(); L.json(address); }}Copy the code

Annotate initData() with the @cacheable annotation and the cached key, and click the text button to print out the same cached data as initData().





@cacheable Execution result. PNG

Currently, the @cacheable annotation only works with Android 4.0 and beyond.

3. Place the result returned by the method in SharedPreferences

The @prefs annotation is used similarly to @cacheable above, except that the result is placed in SharedPreferences.

Also, the @prefs annotation only works with Android 4.0 and above

4. Print out the input and output parameters of the method during App debugging

When debugging, if you can’t see what’s wrong at a glance, you’ll print out some key information. You can do that by labeling any method in your App with @LogMethod.

public class DemoForLogMethodActivity extends Activity{

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        initData1();

        initData2("test");

        User u = new User();
        u.name = "tony";
        u.password = "123456";
        initData3(u);
    }

    @LogMethod
    private void initData1(a) {}@LogMethod
    private String initData2(String s) {

        return s;
    }

    @LogMethod
    private User initData3(User u) {

        u.password = "abcdefg";

        returnu; }}Copy the code




@logMethod Result. PNG

Currently, method input and output parameters support only primitive types and strings, but in the future I will add support for printing arbitrary objects and gracefully rendering them.

5. Hook before and after a method is called

Usually, some key click events, buttons and pages will be buried in the process of App development, which is convenient for data analysts and product managers to view and analyze in the background.

In the past, before the release of the App in big e-commerce companies, we would go through the process with data analysts to see where we need to bury the points. With the release just around the corner, adding code will be a rush, and you need to arrange people to test it. And buried code is very common, so the @hook annotation. It can hook before and after a method is called. Can be used alone or in conjunction with any custom annotations.

    @HookMethod(beforeMethod = "method1",afterMethod = "method2")
    private void initData(a) {

        L.i("initData()");
    }

    private void method1(a) {
        L.i("method1() is called before initData()");
    }

    private void method2(a) {
        L.i("method2() is called after initData()");
    }Copy the code

Method1 () is called before initData(), initData(), and finally method2() is called after initData().





@hook Result. PNG

The principle of @hook is as follows. BeforeMethod and afterMethod do not affect the use of the original method even if they are not found or defined.

import com.safframework.app.annotation.HookMethod;
import com.safframework.log.L;
import com.safframwork.tony.common.reflect.Reflect;
import com.safframwork.tony.common.reflect.ReflectException;
import com.safframwork.tony.common.utils.Preconditions;

import org.aspectj.lang.ProceedingJoinPoint;
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.lang.reflect.Method;


/** * Created by Tony Shen on 2016/12/7. */
@Aspect
public class HookMethodAspect {

    @Around("execution(! synthetic * *(..) ) && onHookMethod()")
    public void doHookMethodd(final ProceedingJoinPoint joinPoint) throws Throwable {
        hookMethod(joinPoint);
    }

    @Pointcut("@within(com.safframework.app.annotation.HookMethod)||@annotation(com.safframework.app.annotation.HookMethod)")
    public void onHookMethod(a) {}private void hookMethod(final ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();

        HookMethod hookMethod = method.getAnnotation(HookMethod.class);

        if (hookMethod==null) return;

        String beforeMethod = hookMethod.beforeMethod();
        String afterMethod = hookMethod.afterMethod();

        if (Preconditions.isNotBlank(beforeMethod)) {
            try {
                Reflect.on(joinPoint.getTarget()).call(beforeMethod);
            } catch (ReflectException e) {
                e.printStackTrace();
                L.e("no method "+beforeMethod);
            }
        }

        joinPoint.proceed();

        if (Preconditions.isNotBlank(afterMethod)) {
            try {
                Reflect.on(joinPoint.getTarget()).call(afterMethod);
            } catch (ReflectException e) {
                e.printStackTrace();
                L.e("no method "+afterMethod); }}}}Copy the code

6. Execute methods safely, regardless of exceptions

Normally, writing code like this would throw a null-pointer exception, causing the App to Crash.

    private void initData(a) {

        String s = null;
        int length = s.length();
    }Copy the code

However, using @safe ensures that an exception will not Crash the App, making it a better user experience.

    @Safe
    private void initData(a) {

        String s = null;
        int length = s.length();
    }Copy the code

Look at the logcat log again. The App did not Crash but printed out the error log information.





Logcat log. PNG

Let’s take a look at @safe and catch the Throwable when something goes wrong.

import com.safframework.log.L;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

import java.io.PrintWriter;
import java.io.StringWriter;

/** * Created by Tony Shen on 16/3/23. */
@Aspect
public class SafeAspect {

    @Around("execution(! synthetic * *(..) ) && onSafe()")
    public Object doSafeMethod(final ProceedingJoinPoint joinPoint) throws Throwable {
        return safeMethod(joinPoint);
    }

    @Pointcut("@within(com.safframework.app.annotation.Safe)||@annotation(com.safframework.app.annotation.Safe)")
    public void onSafe(a) {}private Object safeMethod(final ProceedingJoinPoint joinPoint) throws Throwable {

        Object result = null;
        try {
            result = joinPoint.proceed(joinPoint.getArgs());
        } catch (Throwable e) {
            L.w(getStringFromException(e));
        }
        return result;
    }

    private static String getStringFromException(Throwable ex) {
        StringWriter errors = new StringWriter();
        ex.printStackTrace(new PrintWriter(errors));
        returnerrors.toString(); }}Copy the code

7. Track the time spent on a method for performance tuning

Whether developing an App or a Service, we often do some performance testing, such as looking at how long certain methods take. So that developers can do some optimization work. @Trace was created for this purpose.

    @Trace
    private void initData(a) {

        for (int i=0; i<10000; i++) { Map map =new HashMap();
            map.put("name"."tony");
            map.put("age"."18");
            map.put("gender"."male"); }}Copy the code

To see the result of this code execution, logging took 3ms.





@trace Execution result.png

The time it takes to Trace a method can be achieved with a single @Trace annotation. If it takes too long then you need to optimize the code and test it. Of course, such annotations are not recommended in a production environment.

conclusion

AOP is a powerful complement to OOP. Playing with AOP is very helpful for developing apps, of course you can also use my library directly :), and I will constantly update the new usage methods. Because the level is limited, if any place is not correct, welcome to point out, I can timely modify 🙂