beizili

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

一、什么是AOP?

同上篇IOC编程一样,AOP也是一种编程思想,而不是一门技术。AOP的全称叫Aspect-Oriented Programming,也叫面向切面编程。在理解什么事AOP之前,我们需先知道什么是分散关注。

分散关注
  即将通用需求功能从不相关类之中分离出来;同时,能够使得很多类共享一个行为,一旦行为发生变化,不必修改很多类,只要修改这个行为就可以。AOP就是这种实现分散关注的编程方法,它将“关注”封装在“方面”中。

       AOP也可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善。OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力。也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。对于其他类型的代码,如安全性、异常处理和透明的持续性也是如此。这种散布在各处的无关的代码被称为横切(cross-cutting)代码,在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。

二、为什么要用AOP?

现在举一个实际的例子来说明AOP到底为我们解决了什么问题

例:

当前有个Dao接口,其有insert、delete、update三个方法,还有个实现类DaoImpl:

public interface Dao {

    public void insert();

    public void delete();

    public void update();

}

 

public class DaoImpl implements Dao {

    @Override
    public void insert() {
        System.out.println("DaoImpl.insert()");
    }

    @Override
    public void delete() {
        System.out.println("DaoImpl.delete()");
    }

    @Override
    public void update() {
        System.out.println("DaoImpl.update()");
    }

}

现需在insert与update被调用的前后,打印调用前的毫秒数与调用后的毫秒数
 

1.最原始的写法:

public class ServiceImpl {

    private Dao dao = new DaoImpl();

    public void insert() {
        System.out.println("insert()方法开始时间:" + System.currentTimeMillis());
        dao.insert();
        System.out.println("insert()方法结束时间:" + System.currentTimeMillis());
    }

    public void delete() {
        dao.delete();
    }

    public void update() {
        System.out.println("update()方法开始时间:" + System.currentTimeMillis());
        dao.update();
        System.out.println("update()方法结束时间:" + System.currentTimeMillis());
    }

}

这是最原始的写法,这种写法的缺点也是一目了然:

方法调用前后输出时间的逻辑无法复用,如果有别的地方要增加这段逻辑就得再写一遍,如果Dao有其它实现类,那么必须新增一个类去包装该实现类,这将导致类数量不断膨胀

2.装饰器模式:

public class LogDao implements Dao {

    private Dao dao;

    public LogDao(Dao dao) {
        this.dao = dao;
    }

    @Override
    public void insert() {
        System.out.println("insert()方法开始时间:" + System.currentTimeMillis());
        dao.insert();
        System.out.println("insert()方法结束时间:" + System.currentTimeMillis());
    }

    @Override
    public void delete() {
        dao.delete();
    }

    @Override
    public void update() {
        System.out.println("update()方法开始时间:" + System.currentTimeMillis());
        dao.update();
        System.out.println("update()方法结束时间:" + System.currentTimeMillis());
    }

}

装饰器的优点为:

透明,对调用方来说,它只知道Dao,而不知道加上了日志功能
类不会无限膨胀,如果Dao的其它实现类需要输出日志,只需要向LogDao的构造函数中传入不同的Dao实现类即可

不过这种方式同样有明显的缺点,缺点为:

输出日志的逻辑还是无法复用
输出日志的逻辑与代码有耦合,如果我要对delete()方法前后同样输出时间,需要修改LogDao

但是,这种做法相比最原始的代码写法,已经有了很大的改进。

3.代理模式:

public class LogInvocationHandler implements InvocationHandler {

    private Object obj;

    public LogInvocationHandler(Object obj) {
        this.obj = obj;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String methodName = method.getName();
        if ("insert".equals(methodName) || "update".equals(methodName)) {
            System.out.println(methodName + "()方法开始时间:" + System.currentTimeMillis());
            Object result = method.invoke(obj, args);
            System.out.println(methodName + "()方法结束时间:" + System.currentTimeMillis());

            return result;
        }

        return method.invoke(obj, args);
    }

}

其调用方式很简单:

public static void main(String[] args) {
    Dao dao = new DaoImpl();

    Dao proxyDao = (Dao)Proxy.newProxyInstance(LogInvocationHandler.class.getClassLoader(), new Class<?>[]{Dao.class}, new LogInvocationHandler(dao));

    proxyDao.insert();
    System.out.println("----------分割线----------");
    proxyDao.delete();
    System.out.println("----------分割线----------");
    proxyDao.update();
}

这种方式的优点为:

输出日志的逻辑被复用起来

但还是有缺点:

代码之间的耦合还是没有解决,像要针对delete()方法加上这部分逻辑就必须修改代码

 

4.AOP

首先定义一个时间处理类,我将它命名为TimeHandler:

public class TimeHandler {

    public void printTime(ProceedingJoinPoint pjp) {
        Signature signature = pjp.getSignature();
        if (signature instanceof MethodSignature) {
            MethodSignature methodSignature = (MethodSignature)signature;
            Method method = methodSignature.getMethod();
            System.out.println(method.getName() + "()方法开始时间:" + System.currentTimeMillis());

            try {
                pjp.proceed();
                System.out.println(method.getName() + "()方法结束时间:" + System.currentTimeMillis());
            } catch (Throwable e) {

            }
        }
    }

}

切面的内容可以复用,比如TimeHandler的printTime方法,任何地方需要打印方法执行前的时间与方法执行后的时间,都可以使用TimeHandler的printTime方法
避免使用Proxy、CGLIB生成代理,这方面的工作全部框架去实现,开发者可以专注于切面内容本身
代码与代码之间没有耦合,如果拦截的方法有变化修改配置文件即可

 

我们传统的编程方式是垂直化的编程,即A–>B–>C–>D这么下去,一个逻辑完毕之后执行另外一段逻辑。但是AOP提供了另外一种思路,它的作用是在业务逻辑不知情(即业务逻辑不需要做任何的改动)的情况下对业务代码的功能进行增强,这种编程思想的使用场景有很多,例如事务提交、方法执行之前的权限检测、日志打印、方法调用事件等等。
 

参考博文:

https://www.cnblogs.com/songanwei/p/9417343.html

https://blog.csdn.net/weixin_37672169/article/details/78248832?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task

 

更多内容请关注微信公众号“外里科技

 

posted on 2020-03-29 23:14  被子里  阅读(9)  评论(0)    收藏  举报  来源