Spring中AOP和动态代理的简单实例演示
AOP (Aspect-Oriented Programming) 面向切面编程
OOP (Object-Oriented Programming) 面向对象编程
想要了解AOP
和动态代理,我们先看下面这个例子
假设我们现在有一个TestDao
类中,有增删改查四种操作
@Repository
public class TestDao implements Dao{
public void create(String table,String args){}
public void update(String table,String args){}
public void retrieve(String table,String args){}
public void delete(String table,String args){}
}
某一天老板让你为数据库的增删改查操作添加日志,如果从面向对象的角度来考虑,我们需要创建一个Logger类,在数据库进行增删改查的方法内调用Logger类的方法,如下图所示:
@Component
public class Logger {
public void log(Class c,String table,String args,String method) {
System.out.println("Dao层中的"+c+"对表"+table+"和参数"+args+"进行了"+method+"操作");
}
}
@Repository
public class TestDao implements Dao{
@Autowired
private Logger logger;
public void create(String table,String args){
String methodName = Thread.currentThread().getStackTrace()[1].getMethodName();
logger.log(this.getClass(),table,args,methodName);
}
public void update(String table,String args){
String methodName = Thread.currentThread().getStackTrace()[1].getMethodName();
logger.log(this.getClass(),table,args,methodName);
}
public void retrieve(String table,String args){
String methodName = Thread.currentThread().getStackTrace()[1].getMethodName();
logger.log(this.getClass(),table,args,methodName);
}
public void delete(String table,String args){
String methodName = Thread.currentThread().getStackTrace()[1].getMethodName();
logger.log(this.getClass(),table,args,methodName);
}
}
下面对以上述方法完成的日志记录功能进行测试
@Test
public void test() {
ApplicationContext ac = new ClassPathXmlApplicationContext("aop.xml");
TestDao bean = ac.getBean(TestDao.class);
bean.create("student","张三");
bean.update("teacher","李四");
bean.retrieve("student","王五");
bean.delete("teacher","李四");
}
得到以下结果
尽管在调用log方法的时候使用了反射,栈等技巧避免了手动输入关键信息,但是仍然需要在每个方法中调用一次log方法。一旦方法过多,手动在方法中添加日志记录功能的方式就过于繁琐,还会造成耦合。
AOP思想
想要避免上述问题,就需要AOP的思想。AOP(Aspect-Oriented Programming)面向切面编程是把业务流程中通用的步骤抽取成为一个切面,然后根据业务流程将切面切入到指定的位置中。
什么是动态代理?
在理解动态代理之前,我们需要先了解代理模式,继续以增删改查为例
当TestDao
中的方法越来越多的时候,每个方法都去手动日志记录太麻烦了,占用了你大量的时间,这时你把日志记录的任务交给新来的实习生,让实习生负责TestDao
中所有的日志记录。
当你需要日志信息的时候,直接找实习生就可以了,不需要再去访问TestDao
,此时实习生就是TestDao
的代理人,TestDao
被实习生代理了。
在java中,我们需要创建代理类来代替实习生的作用,我们有需求直接去寻找代理类,而不需要访问原来的类。Spring为业务类创建一个代理类,我们把业务流程中通用的步骤抽取成切面放在代理类中,这就是Spring中AOP的实现原理。
代理模式又分为静态代理和动态代理。静态代理不在本篇博客的讨论范围之中,Spring使用了动态代理,而动态代理又分为Jdk动态代理和Cglib动态代理,我们将分开进行演示讲解,相信会对你理解AOP和动态代理有所帮助。
JDK动态代理
JDK1.8中创建代理类时使用的是java.lang.reflect.Proxy
,JDK中还有一个java.net.Proxy
,注意不要搞混。
在JDK官方文档中,我们找到了Proxy代理类的创建方法,找到方法newProxyInstance
的详细信息。
Proxy中的newProxyInstance方法有三个参数:ClassLoader
,interfaces
,InvocationHandler
。
ClassLoader
为被代理对象的类加载器。
interfaces
为被代理对象实现的接口。
InvocationHandler
为方法调用器,帮被代理对象执行目标方法。
使用JDK动态代理必须有一个前提,就是被代理类必须实现了任意接口,下面我们来编写代理类。
public class DaoProxy{
/**
* 创建一个静态方法为Dao类型的被代理对象创建代理对象
* @param dao 被代理对象
* @return
*/
public static Dao getDaoProxy(final TestDao dao) {
//通过反射获取被代理对象的类加载器
ClassLoader classLoader = dao.getClass().getClassLoader();
//通过反射获取被代理对象实现的接口
Class<?>[] interfaces = dao.getClass().getInterfaces();
//创建匿名内部类的方式调用处理器,帮被代理对象执行目标方法
InvocationHandler handler = new InvocationHandler() {
/**
* 对invoke方法进行重写
* @param proxy 代理类对象
* @param method 方法
* @param args 方法参数
* @return 方法调用后返回的结果
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//在方法调用后面可以添加业务代码
String c = dao.getClass().getSimpleName();
String table = (String) args[0];
String args1 = (String) args[1];
String method_name = method.getName();
System.out.println("Dao层中的"+c+"对表"+table+"和参数"+args1+"进行了"+method_name+"操作");
//使用反射执行目标方法
Object result = method.invoke(dao, args);
//在方法执行后面可以添加业务代码
System.out.println("方法执行完毕");
return result;
}
};
//proxy为创建的代理对象
Object proxy = Proxy.newProxyInstance(classLoader, interfaces, handler);
return (Dao) proxy;
}
}
代理类创建完成之后,进行功能测试
@Test
public void test() {
//AopTest bean1 = ac.getBean(AopTest.class);
//System.out.println(logger);
//初始化SpringIOC容器
ApplicationContext ac = new ClassPathXmlApplicationContext("aop.xml");
//获取TestDao的Bean对象
TestDao dao = ac.getBean(TestDao.class);
//调用DaoProxy的getDaoProxy方法为dao创建代理对象
Dao proxy = DaoProxy.getDaoProxy(dao);
//使用代理对象执行目标方法
proxy.create("student","张三");
proxy.update("teacher","李四");
proxy.retrieve("student","王五");
proxy.delete("teacher","李四");
}
得到测试结果,代理类中的业务代码被正确执行
Dao层中的TestDao对表student和参数张三进行了create操作
方法执行完毕
Dao层中的TestDao对表teacher和参数李四进行了update操作
方法执行完毕
Dao层中的TestDao对表student和参数王五进行了retrieve操作
方法执行完毕
Dao层中的TestDao对表teacher和参数李四进行了delete操作
方法执行完毕
Cglib动态代理
JDK动态代理有一个缺点,那就是被代理类必须实现接口。为了解决这个问题,开发出了Cglib动态代理,笔者推荐大家看参考资料的中的博客,有详细的源码解析。
参考资料
t和参数王五进行了retrieve操作
方法执行完毕
Dao层中的TestDao对表teacher和参数李四进行了delete操作
方法执行完毕
#### Cglib动态代理
JDK动态代理有一个缺点,那就是被代理类必须实现接口。为了解决这个问题,开发出了Cglib动态代理,笔者推荐大家看参考资料的中的博客,有详细的源码解析。
#### 参考资料
[CGLIB动态代理实现原理](https://blog.csdn.net/yhl_jxy/article/details/80633194)