您所在的位置:主页 > JAVA技术 >

Spring-aop学习笔记与总结

时间:2018-03-19 16:28来源:未知 作者:os 点击:

 
 
如果抛弃了spring那该如何实现AOP?就是说只用code Java 实现一个AOP。
 
小胖子于是狠下心好好学下这AOP到底是怎么实现的,通过了不懈努力终于得到了一些进展,终于也大概知道这AOP是怎么回事。原来spring是借助jdk proxy和CGlib两种技术实现的。对于jdk proxy 实现但得有个限制条件:必须要提供个接口和这个接口的实现类。对于CGlib这个就简单多了,只要实现methodinterceptor接口就可。那到底是如何实现的?小胖子回到家后二话不说,打开电脑先撸了一遍代码:
 
复制代码
public interface TestDao {
 
public void add();
 
}
 
public class TestDaoImpl implements TestDao {
 
@Override
 
public void add() {
 
System.out.println("TestDaoImpl开始执行...");
 
}
 
}
 
public class TestService {
 
public void test() {
 
TestDao dao = new TestDaoImpl();
 
System.out.println("调用testDao.add()方法前,执行些校验操作...");
 
dao.add();
 
System.out.println("调用testDao.add()方法后,执行些commit or rollback 操作...");
 
}
 
}
复制代码
 
 
很明显现在要做的就是在TestService里面dao.add()方法在调用前,先执行一些打印操作,和执行后也执行一些打印操作。小胖子二话不说又查起了资料,终于找到了一种设计模式叫:装饰器模式。装饰器模式主要就是实现接口并持有该接口的引用。于是小胖子把程序改进为:
 
复制代码
public class LogDao implements TestDao {
 
private TestDao dao ;
 
public LogDao(TestDao testDao) {
 
this.dao=testDao;
 
}
 
@Override
 
public void add() {
 
System.out.println("调用testDao.add()方法前,执行些校验操作...");
 
dao.add();
 
System.out.println("调用testDao.add()方法后,执行些commit or rollback 操作...");
 
}
 
public static void main(String[] args) {
 
LogDao dao = new LogDao(new TestDaoImpl());
 
dao.add();
 
}
 
}
复制代码
 
 
运行结果很明显:
 
调用testDao.add()方法前,执行些校验操作...
 
TestDaoImpl开始执行...
 
调用testDao.add()方法后,执行些commit or rollback 操作...
 
结果虽然是有点像aop了但还是不尽人意:很明显这打印的日志却不能重复利用。于是小胖子很快又找到了jdk 底层那个高大上的代理模式,主要是要实现一个叫InvocationHander的接口;于是把代码再改改:
 
复制代码
public class TestInvocationHandler implements InvocationHandler {
 
private Object object;
 
public TestInvocationHandler(Object obj ) {
 
this.object = obj;
 
}
 
@Override
 
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 
if("add".equals(method.getName())) {
 
System.out.println("调用testDao.add()方法前,执行些校验操作2...");
 
Object result = method.invoke(object, args);
 
System.out.println("调用testDao.add()方法后,执行些commit or rollback 操作...");
 
return result;
 
}
 
return method.invoke(proxy, args);
 
}
 
}
复制代码
 
 
这样打印的日志就可以被复用起来了,到现在aop就大概实现了,调用方法小胖子简单的写了个main:
 
复制代码
public static void main(String[] args) {
 
TestDao dao = new TestDaoImpl();
 
TestDao proxyDao = (TestDao) Proxy.newProxyInstance(TestInvocationHandler.class.getClassLoader(), new Class<?>[] {TestDao.class},
 
new TestInvocationHandler(dao));
 
proxyDao.add();
 
}
复制代码
 
 
结果:
 
调用testDao.add()方法前,执行些校验操作2...
 
TestDaoImpl开始执行...
 
调用testDao.add()方法后,执行些commit or rollback 操作...
 
但很快小胖子又发现了一个问题,在TestInvocationHandler 里面添加了if("add".equals(method.getName())) 的判断,如果没加这个方法判断程序会一直死循环报错,所以如果要对其他方法调用前后也打印日志,也得在这里面添加判断。
 
还有一个问题就是jdk proxy动态代理只能针对接口来做代理,不能对类做代理,所以回到上文提到:对于jdk proxy 实现但得有个限制条件:必须要提供个接口和这个接口的实现类。
 
那对于CGlib那又是如何实现的呢?原来这个是针对类来实现的,只要实现了methodinterceptor接口就可以了。小胖子迫不及待上了maven库把CGlib包下了下来,导进项目再撸一把代码:
 
复制代码
public class TestDaoProxy implements MethodInterceptor {
 
@Override
 
public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable {
 
if("add".equals(arg1.getName())) {
 
System.out.println("调用testDao.add()方法前,执行些校验操作...");
 
arg3.invokeSuper(arg0, arg2);
 
System.out.println("调用testDao.add()方法后,执行些commit or rollback 操作...");
 
return arg0 ;
 
}
 
arg3.invokeSuper(arg0, arg2);
 
return arg0;
 
}
 
}
 
public static void main(String[] args) {
 
TestDaoProxy daoProxy = new TestDaoProxy();
 
Enhancer enhancer = new Enhancer();
 
enhancer.setSuperclass(TestDaoImpl.class);
 
enhancer.setCallback(daoProxy);
 
TestDao dao = (TestDaoImpl) enhancer.create();
 
dao.add();
 
}
复制代码
 
 
发现报错了...
 
复制代码
Caused by: java.lang.ClassNotFoundException: org.objectweb.asm.Type
 
at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
 
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
 
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
 
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
 
... 4 more
复制代码
 
 
很明显这是缺少了相关jar包,原来是缺少了asm.jar,只要再把asm.jar引进来,结果就出来了。
 
但最终还是存在一个问题,无论是使用proxy还是CGlib都存在一个缺陷,代码之间的耦合还是没有解决;上面这个if("add".equals(method.getName())) 的判断还是存在,如果有新的方法也要相应添加新的判断...