AOP是一种思想,一种编程方式。编写一段代码在合适的时机找到切入点然后执行。不直接修改原来的代码,而是在原代码执行的前后执行一段额外的代码。
这么做的好处有:
1.解耦合,系统应追求高内聚低耦合,增强的逻辑独立存在,即插即用,不需要移除掉切点即可,对原有业务无影响(或影响极小)。
2.符合开闭原则,对扩展开放,对修改关闭,不修改原有代码。改代码的代价有些时候比较大。
3.代码复用,可以在不侵入当前代码的情况下复用代码或引入第三方功能从而扩展系统功能。
AOP是通过代理的方式实现的,由代理对象持有原对象,在执行原对象目标方法的前后可以执行额外的增强代码。
代理对象需要是原对象接口的实现或原对象的子类,这样就可以在对象引用处直接替换原对象。
代理方式分静态代理和动态代理,区别在于代理对象生成方式不同
静态代理:编译期增强,生成可见的代理class,使用代理类替换原有类进行调用。静态代理是手动步枪,打一枪就需要手动换单装填上膛。
动态代理:运行期增强,内存中动态生成代理类,使用反射动态调用原对象方法。动态代理是自动步枪,一次装弹上膛,哒哒哒就完事儿了。
AOP的实现有:AspectJ、JDK动态代理、CGLIB动态代理、JBossAOP、JMangler(后面两种仅限于传说,没接触过)
AspectJ属于静态代理,使用特定编译器在编译期生成代理对象并与源码融在了一起,即修改了class文件。
JDK动态代理必须基于接口,即生成的代理对象是对原对象接口的实现,相当于替换了实现类,面向对象中接口可以替换实现类。
CGLIB动态代理基于继承,即生成的代理对象是原对象的子类,面向对象中子类可以替换父类。
不论是静态代理还是动态代理
AOP均有三个关注点:切面增强内容、何地切入、何时切入
切面增强内容:切面增强逻辑的代码段
何地切入:需要给哪个类哪个方法增强
何时切入:切入发生在何时,方法执行前、执行后、还是执行前后
下面举一个例子,以不使用代理,使用静态代理,使用动态代理看看如何实现
Word类是一个单词本,可以分析出一个字符串包含了单词本中的几个关键词
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
/** * <p>单词本</p> * * @author nec * @date 2021/5/11 10:33 */ public interface Word { /** * <p>添加新词</p> * * @since 2021/5/13 18:11 * @param word * @return */ void addWord(String word); /** * <p>返回单词本包含的词</p> * * @since 2021/5/13 18:11 * @param content * @return */ List<String> findKeyWord(String content); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
public class WordImpl implements Word { private static List<String> keyWordList = new ArrayList<>(); @Override public void addWord(String word) { if (keyWordList.contains(word)) { return; } keyWordList.add(word); } @Override public List<String> findKeyWord(String content) { List<String> resultList = new ArrayList<>(); if (StringUtils.isEmpty(content)) { return resultList; } for (String word : keyWordList) { if (content.indexOf(word) > -1) { resultList.add(word); } } return resultList; } } |
上线一段后,需要对于返回值打印便于我们通过日志排查问题,同时统计热点词汇方便优化需求。
最简单的实现为,使用一个日志工具类,代码中调用Word.findKeyWord时,对返回值做处理,或者直接修改findKeyWord方法,return前处理。
对返回值做处理
1 2 3 4 |
Word word = createWord(); List<String> keyWordList = word.findKeyWord(content); MyLogUtil.printResult(content, keyWordList); MyLogUtil.countResult(content, keyWordList); |
直接修改findKeyWord方法,return前处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
@Override public List<String> findKeyWord(String content) { List<String> resultList = new ArrayList<>(); if (StringUtils.isEmpty(content)) { return resultList; } for (String word : keyWordList) { if (content.indexOf(word) > -1) { resultList.add(word); } } MyLogUtil.printResult(content, resultList); MyLogUtil.countResult(resultList); return resultList; } |
上面两种方式其实都不好!打印日志和统计都不是业务逻辑,findKeyWord的核心逻辑没有任何变化,只能算是一种辅助扩展功能,甚至可有可无
如果此类处理大量存在,则会导致:
1.影响代码执行:这种“辅助功能”与业务逻辑执行毫无关系,可一旦发生异常则影响执行结果,还得多做些“保证不产生负面影响”的操作,如捕获异常
2.增加代码复杂度:业务逻辑中侵入了与业务无关的代码,影响阅读,提升业务逻辑复杂度
3.废弃时清理难度大:有天不需要这种“辅助功能”了,还得一一删除排查,确保清理干净,确保没有清理出bug
定义代理类,替换原对象进行调用执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
public class WordStaticProxy implements Word { private Word word; public void setWord(Word word) { this.word = word; } @Override public void addWord(String word) { this.word.addWord(word); } @Override public List<String> findKeyWord(String content) { System.out.println("静态代理哦!"); before(); List<String> resultList = this.word.findKeyWord(content); after(content, resultList); return resultList; } private void before() { // TODO } private void after(String content, List<String> resultList) { try { MyLogUtil.printResult(content, resultList); MyLogUtil.countResult(resultList); } catch (Exception e) { e.printStackTrace(); } } } |
执行静态代理
1 2 3 4 |
Word word = createWord(); WordStaticProxy proxy = new WordStaticProxy(); proxy.setWord(word); List<String> keyWordList = proxy.findKeyWord(content); |
定义拦截器并生成代理类,替换原对象进行调用执行,这里使用JDK动态代理。
拦截器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
public class WordProxyHandler implements InvocationHandler { /** * 原对象,被代理对象 */ private Object word; public WordProxyHandler(Object word) { this.word = word; } /** * <p>代理方法</p> * * @param proxy 生成的代理对象 * @param method 被代理对象的方法,反射执行invoke需要传入原对象 * @param args 被代理对象的方法的入参 * @return */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("动态代理哦!"); before(); Object result = method.invoke(word, args); after(args, result); return result; } private void before() { // TODO } private void after(Object[] args, Object result) { try { String content = args[0].toString(); List<String> resultList = (List<String>) result; MyLogUtil.printResult(content, resultList); } catch (Exception e) { e.printStackTrace(); } } } |
生成动态代理对象工具类
1 2 3 4 5 6 7 |
public class DynamicProxyUtil { public static <T> T getProxy(Class<T> clazz, T obj) { InvocationHandler wordProxyHandler = new WordProxyHandler(obj); return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class<?>[]{clazz}, wordProxyHandler); } } |
执行动态代理
1 2 3 |
Word original = createWord(); Word proxy = DynamicProxyUtil.getProxy(Word.class, original); List<String> keyWordList = proxy.findKeyWord(content); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
public class MainTest { private static Word word; public static void main(String[] args) { String content = "京东的刘强东在肯德基吃啃得起"; testNormal(content); System.out.println("----------------"); testStaticProxy(content); System.out.println("----------------"); testDynamicProxy(content); } private static void testNormal(String content) { System.out.println("正常执行测试start..."); init(ProxyType.NORMAL); List<String> keyWordList = word.findKeyWord(content); try { MyLogUtil.printResult(content, keyWordList); } catch (Exception e) { e.printStackTrace(); } System.out.println("正常执行测试end..."); } private static void testStaticProxy(String content) { System.out.println("静态代理执行测试start..."); init(ProxyType.STATIC_PROXY); List<String> keyWordList = word.findKeyWord(content); System.out.println("静态代理执行测试end..."); } private static void testDynamicProxy(String content) { System.out.println("动态代理执行测试start..."); init(ProxyType.DYNAMIC_PROXY); List<String> keyWordList = word.findKeyWord(content); System.out.println("动态代理执行测试end..."); } private static void init(ProxyType proxyType) { switch (proxyType) { case NORMAL : word = createWord(); break; case STATIC_PROXY: word = createStaticProxy(); break; case DYNAMIC_PROXY: word = createDynamicProxy(); break; default: word = createWord(); } } private static Word createWord() { Word wordTmp = new WordImpl(); wordTmp.addWord("啃得起"); wordTmp.addWord("京东"); wordTmp.addWord("拼多多"); wordTmp.addWord("马云"); return wordTmp; } private static Word createStaticProxy() { Word word = createWord(); WordStaticProxy proxy = new WordStaticProxy(); proxy.setWord(word); return proxy; } private static Word createDynamicProxy() { Word word = createWord(); return DynamicProxyUtil.getProxy(Word.class, word); } public enum ProxyType{ NORMAL, STATIC_PROXY, DYNAMIC_PROXY } } |
测试结果
正常执行测试start…
京东的刘强东在肯德基吃啃得起:出现 2 个关键词
包含的关键词有:啃得起 京东
正常执行测试end…
静态代理执行测试start…
静态代理哦!
京东的刘强东在肯德基吃啃得起:出现 2 个关键词
包含的关键词有:啃得起 京东
静态代理执行测试end…
动态代理执行测试start…
动态代理哦!
京东的刘强东在肯德基吃啃得起:出现 2 个关键词
包含的关键词有:啃得起 京东
动态代理执行测试end…
1.aop是一种优秀的编程思想,可以解耦合和提高代码复用作用,编程优雅
2.动态代理使得aop更加简单易用,应用范围广,是目前aop主流用法
3.aop不可滥用,会破坏的程序连贯性。可以用aop默默做一些事,如事务支持、日志打印,如果核心业务逻辑使用aop拆分隔离,则程序不直观不连贯,滥用时维护不见得方便。
from:https://blog.csdn.net/u012098021/article/details/116802005