Spring是一个十分火热开源框架,而AOP(面向切面编程)则是Spring最重要的概念之一,为了更好的理解和学习AOP的思想,使用核心库来实现一次不失为一个好方法。

  首先介绍一下AOP的概念,AOP(Aspect Oriented Programming),即面向切面编程,所谓的面向切面编程,就是从一个横切面的角度去设计代码的思想,传统的OOP思想是用封装继承和多态构造一种纵向的层次关系,但不适合定义横向的关系,而AOP思想则对此进行了很好的补充。

  例如日志管理代码往往横向的散布在很多对象层次中,但跟它对应的对象的核心功能可以说是毫无关系,还有很多类似的代码,如权限验证,调试输出,事务处理等,也都是如此,这样的话就不利于代码的复用和管理了。

  这时候AOP技术就应运而生了,它利用“横切”技术,深入封装对象的内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于后续的可操作性和可维护性。

  那么AOP又是如何实现的呢?

  答案是动态代理(关于代理会有另外篇章做详细介绍,这里就不赘述了)。实现动态代理有两种方式,一种是JDK动态代理,一种是CGLib动态代理。

  那么分别使用两种方式来做一个简单的栗子。

  先设计一个场景,假设我们有一个计算接口ICalculator和实现了该接口的计算器类CalculatorImpl。

public interface ICalculator {
//加法运算
int add(int a,int b);
//减法
int subtract(int a,int b);
//乘法
int multiply(int a,int b);
//除法
int devide(int a,int b);
}
public class CalculatorImpl implements ICalculator{
@Override
public int add(int a, int b) {
return a + b;
} @Override
public int subtract(int a, int b) {
return a - b;
} @Override
public int multiply(int a, int b) {
return a * b;
} @Override
public int devide(int a, int b) {
return a / b;
}
}

  如何在不改动原来计算器类内部代码的情况下记录计算器各个方法使用的总次数呢?

  有了动态代理后,其实就很简单了,先创建一个类并实现InvocationHandler接口,覆盖invoke方法,

public class TestHandler implements InvocationHandler {

    private Object targetObject;
private int useTimes; //绑定委托对象,并返回代理类
public Object bind(Object targetObject){
this.targetObject = targetObject;
return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),targetObject.getClass().getInterfaces(),this);
} @Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//do something
before();
Object result = method.invoke(targetObject,args);
after();
return result;
}
private void before(){
System.out.println("we can do something before calculate.");
} private void after(){
useTimes++;
System.out.println("已使用:"+useTimes+"次");
}
}

  别看代码好像有点多,其实主要的方法就是invoke方法,里面的Object result = method.invoke(targetObject,args);相当于继续用原来的参数执行原来方法。这里的before和after为自定义的函数,可以在目标代码执行前后做一些我们想要做的事情,比如这里的使用次数统计。

  在bind方法里,传入目标代理对象,并返回一个代理类实例。接下来我们看看如何使用:

public class TestProxy {
public static void main(String[] args) {
TestHandler proxy = new TestHandler();
ICalculator calculator = (ICalculator)proxy.bind(new CalculatorImpl());
int result = calculator.add(1,2);
System.out.println("result is:"+result);
result = calculator.subtract(3,2);
System.out.println("result is:"+result);
result = calculator.multiply(4,6);
System.out.println("result is:"+result);
result = calculator.devide(6,2);
System.out.println("result is:"+result);
}
}

  我们先定义一个TestHandler,然后通过bind方法来获得一个代理实例,之后我们就可以直接使用这个实例了。运行结果如下:

we can do something before calculate.
已使用:1次
result is:3
we can do something before calculate.
已使用:2次
result is:1
we can do something before calculate.
已使用:3次
result is:24
we can do something before calculate.
已使用:4次
result is:3

  这样我们就实现了不修改CalculatorImpl内部代码的情况下对代码进行扩展。

  接下来用CGLib的方式来实现一次。

  先创建一个类来实现MethodInterceptor接口,并覆盖intercept方法。其他代码跟使用JDK代理大同小异,仅仅是获取代理对象的过程有所差异。

public class CGLibProxy implements MethodInterceptor {
private int useTimes;
private Object target; public Object getInstance(Object target){
this.target=target;
Enhancer enhancer =new Enhancer();
enhancer.setSuperclass(this.target.getClass());
enhancer.setCallback(this);
return enhancer.create();
} @Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
before();
Object result = methodProxy.invokeSuper(o,objects);
after();
return result;
} private void before(){
System.out.println("we can do something before calculate.");
} private void after(){
useTimes++;
System.out.println("已使用:"+useTimes+"次");
}
}

  测试一下:

public class TestCGLibProxy {
public static void main(String[] args) {
CGLibProxy cgLibProxy = new CGLibProxy();
ICalculator calculator = (ICalculator) cgLibProxy.getInstance(new CalculatorImpl());
int result = calculator.add(1,2);
System.out.println("result is:"+result);
result = calculator.subtract(3,2);
System.out.println("result is:"+result);
result = calculator.multiply(4,6);
System.out.println("result is:"+result);
result = calculator.devide(6,2);
System.out.println("result is:"+result);
}
}

  运行结果如下:

we can do something before calculate.
已使用:1次
result is:3
we can do something before calculate.
已使用:2次
result is:1
we can do something before calculate.
已使用:3次
result is:24
we can do something before calculate.
已使用:4次
result is:3

  现在我们得到了同样的结果。(需要导入两个包,cglib-2.2.2.jar asm-3.3.jar)

  两种方法各有所长,JDK代理需要先设置一个接口,然后才能实现代理,这是它的缺点,也是它的优点,缺点是这样会麻烦一点,而且无法对那些已经封装好的,没有实现接口的类进行代理,而CGLib代理的方式不需要使用接口。但也正是因为如此,JDK代理的方式仅仅拦截类中覆盖接口的方法,而CGLib则会拦截类的所有方法调用。两者各有利弊,所以需要具体情况具体分析。在Spring中也是混杂使用了两种代理模式。

【Java疑难杂症】利用Java核心库实现简单的AOP的更多相关文章

  1. Java:利用java Timer类实现定时执行任务的功能

    一.概述 在java中实现定时执行任务的功能,主要用到两个类,Timer和TimerTask类.其中Timer是用来在一个后台线程按指定的计划来执行指定的任务.TimerTask一个抽象类,它的子类代 ...

  2. 【Java】利用java.io.PrintWriter写出文本文件

    代码: package com.hy.expired; import java.io.FileNotFoundException; import java.io.PrintWriter; public ...

  3. Java读取利用java.util类Properties读取resource下的properties属性文件

    说明:upload.properties属性文件在resources下 import java.io.IOException;import java.io.InputStream;import jav ...

  4. JAVA WEB快速入门之通过一个简单的Spring项目了解Spring的核心(AOP、IOC)

    接上篇<JAVA WEB快速入门之从编写一个JSP WEB网站了解JSP WEB网站的基本结构.调试.部署>,通过一个简单的JSP WEB网站了解了JAVA WEB相关的知识,比如:Ser ...

  5. 基于《仙剑奇侠传柔情版》利用Java的简单实现(一)

    基于<仙剑奇侠传柔情版>利用Java的简单实现(一) 2018-12-01 23:55:36   by Louis  一,新建一个类GameFrame.class,具体代码如下: pack ...

  6. JAVA 文件编译执行与虚拟机(JVM)简单介绍

    详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcytpo3 java程序的内存分配 JAVA 文件编译执行与虚拟机(JVM)介绍 ...

  7. Java面试 32个核心必考点完全解析

    目录 课程预习 1.1 课程内容分为三个模块 1.2 换工作面临问题 1.3 课程特色 课时1:技术人职业发展路径 1.1 工程师发展路径 1.2 常见技术岗位划分 1.3 面试岗位选择 1.4 常见 ...

  8. java web学习总结(二十二) -------------------简单模拟SpringMVC

    在Spring MVC中,将一个普通的java类标注上Controller注解之后,再将类中的方法使用RequestMapping注解标注,那么这个普通的java类就够处理Web请求,示例代码如下: ...

  9. 利用Java动态生成 PDF 文档

    利用Java动态生成 PDF 文档,则需要开源的API.首先我们先想象需求,在企业应用中,客户会提出一些复杂的需求,比如会针对具体的业务,构建比较典型的具备文档性质的内容,一般会导出PDF进行存档.那 ...

随机推荐

  1. (扩展根目录容量方法汇总)把Linux系统迁移到另一个分区或者硬盘

    Linux系统扩容方法汇总 相信很多朋友都有过这样的经历,本想装个Ubantu玩玩,没想到玩久了反而不习惯Windows了,然而开始装系统的时候只分配了非常小的空间,那应该怎样扩展我们的ubantu呢 ...

  2. 【转】认识物理I/O构件- 主机I/O总线

    在数据离开系统内存总线后,它通常传输到另一条总线,即主机I/O总线.在今天的产品中,最常见的主机I/O总线是PCI总线,但也存在着几种其他的总线,如S -总线,EIS A总线及VME总线.主机I/O总 ...

  3. 使用Hibernate Tools从数据库逆向生成Hibernate实体类

    自动生成model.java.*.hbm.xml 甚至是dao.java.*.ddl.*.html等等.一般也就如下三种方式1. MyEclipse 自带插件2. jboss的 hibernate-t ...

  4. 【SQL注入】mysql中information_schema详解

    在MySQL中,把 information_schema 看作是一个数据库,确切说是信息数据库.其中保存着关于MySQL服务器所维护的所有其他数据库的信息.如数据库名,数据库的表,表栏的数据类型与访问 ...

  5. aapt不是内部命令

    解决方法:在E:\sdk\build-tools\目录下的任意文件夹下查找aapt,复制到E:\sdk\platform-tools,具体盘符是情况而定,如果还不行,尝试配置环境变量!

  6. IIS下自定义错误页面配置的两种方式(亲测可行)--IIS服务器

    网站自定义错误页面的设置,大家应该都知道它的重要性……不多说,下面带大家一步步在IIS下设置网站自定义错误页面…… 1.首先进入你的网站主页,找到[错误页](注意是IIS下的错误页不是.NET错误页) ...

  7. 深度学习系列 Part (1)

    传统机器学习的回顾 近年来,深度学习的概念十分火热,人工智能也由于这一技术的兴起,在近几年吸引了越来越多的关注.我们这里,将结合一些基本的用例,简要的介绍一下这一新的技术. 我们首先需要明确人工智能. ...

  8. poj 2345 Central heating

    Central heating Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 678   Accepted: 310 Des ...

  9. Windows下caffe的配置和调用caffe库(一)

    一.Windows下caffe的配置: 1. 下载caffe官网提供的开发包,https://github.com/microsoft/caffe 2. 将caffe-master目录下的Window ...

  10. (11.06)Java小知识

    最近由于课程变化,学习计划也跟着改动,留给我写博客的时间也越来越少.今天晚上没有课,抽空过来图书馆写一写,许久不写感觉都有点陌生了! 今天要和大季家分享的衔接了上一篇博客,是关于方法的嵌套调用与递归调 ...