`
明日天涯
  • 浏览: 35657 次
  • 性别: Icon_minigender_1
  • 来自: 福州
文章分类
社区版块
存档分类
最新评论

Spring之AOP思想的理解和简单实现

阅读更多
Spring之Aop的简单实现

所谓Aop,即Aspect Oriented Programming,面向方面编程。这个概念听起来可能有点抽象,所以在这里我们先引入Aop中的一些术语并结合它们来对Aop思想做一个整体的解释:

1.Aspect(切面):横切性关注点的抽象即为切面。记得有这么个俗语,意思就是一根筷子容易折断,而一捆筷子就不容易折断了,说的是团结的力量。那么,现在,大家想一下,如果我们手里拿着一把刀,要斩断一捆筷子(由十根筷子组成),我们要怎么办呢?答案是明显的,就是横着砍下去!我想应该没有人会选择竖着砍下去的,呵呵。那么,在砍的那个过程中,我们要关注的地方有哪些呢?或者说我们要砍断的筷子有哪几根呢?答案还是那么明显,当然是要把十根筷子都砍断咯。那么,对于这捆筷子中的每一根都可以理解为是一个横切性关注点。由于个人的想象力有限,所以举的例子不免有些牵强。好了,我们继续来解释切面的概念,那么在编程中,横切性关注点是什么呢?实际上,简单的来说,就是程序运行时我们要对哪些方法进行拦截,拦截后要做些什么事(例如可以对部分函数的调用进行日志记录),这些都算是横切性关注点。上面说了切面是横切性关注点的抽象,这里,我们可以结合面向对象的概念来理解。大家都知道的是,类是物体特征的抽象,所以结合这个来理解切面的意思应该会容易一点。
2.Joinpoint(连接点):顾名思义,连接点的作用就是可以在上面接一点东西。实际上,Aop中的连接点的意思也差不多,就是那些被我们拦截到的点(Spring中,这些点指的就是方法,因为Spring只支持方法类型的拦截点),那么我们拦截一个方法的目的是什么呢?当然是为了附带做一些事(即执行一些代码)啦,或者说是接入一些执行代码,所以被拦截的方法我们可以称之为接入点。
3.Pointcut(切点):切点用于指定或定义希望在程序流中拦截的Joinpoint。切点还包含一个通知(所谓通知,即拦截到Joinpoint后我们所要附带做的事,如方法的调用等等),该通知在连接点被拦截时发生,因此如果在一个调用的特定方法上定义一个切点,那么在调用该方法时,Aop框架将截获该切点,并执行切点所关联的通知。
4.Advice(通知):所谓通知,就是拦截到Joinpoint后我们所要做的事情(通常就是执行一些代码)。通知可分为前置通知、后置通知、异常通知、最终通知、环绕通知(对于各种通知的解释,这里不再做详细介绍了)。
5.Target Object(目标对象):包含连接点的对象,也称之为被通知或被代理对象。因为Aop是需要通过代理机制来实现的。对于目标对象的方法调用,实际上是通过它的代理对象来进行的,因此可以在代理对象中对调用操作进行拦截,然后执行切点的相关通知。
6.Aop Proxy(Aop代理):Aop框架为目标对象创建的代理对象,包含了通知。在Spring中,AOP代理可以是JDK动态代理或CGLIB代理。
7.Weave(织入):将Aspect运用到Target对象并导致proxy对象创建的这个过程。
8.Introduction(引入):添加方法或属性到被通知的类。在不修改任何源代码的情况下,Introduction可以在程序运行期间动态地为类添加一些方法和属性。

接着,我们结合上面的提到的Aop的这些术语来对Aop做一番解释。我们不妨引入一个需求(或者可称之为Aop思想的动机,即为什么要提出Aop思想):监控部分重要函数的执行时间,并以短信的方式通知相关人员。那么这个需求该怎么实现呢?或许会有朋友说,这个太简单了,不就是直接在方法体内添加发送短信的代码嘛。但是,如果要监控的函数很多呢?逐个地方手动地进行添加?如果需求突然变更了,要求将发短信改为发邮件,那该怎么办呢?如果要监控的目标函数也要发生改变那又该怎么办呢?所以,将代码写死绝对不会是一个程序设计的好思想,也不会是一个合格的编程人员应该做的。那么,Aop思想将可以用来很好地解决这个问题,它对于该需求的实现过程是这样的:
(1)将要监控的函数定义为切点,也就是说把要监控的函数当作接入点,并且定义到配置文件中去,这样就我们可以动态地修改切点了。
(2)为包含接入点的目标对象定义Aop代理(实际上可以只定义一个Aop代理来作为多个目标对象的代理)
(3)将发送短信的代码封装为一个类的方法(我们称之为通知方法),并且抽取该类的接口,然后我们在实际编码中使用的是接口,并把具体的通知类定义到配置文件中去,这样有便于我们以后做扩展。
(4)实现Aop代理的invoke方法,并在invoke中调用接入点方法,且在调用之后接着调用通知的方法(也就是发短信或者发邮件的方法)。
好了,对于刚才我们引入的需求,运用Aop思想,其大致的实现就是上面几步。那么,当需求变更为“发送邮件”时,我们只需要改变一下配置文件中的通知类就可以了;当要监控的函数需要改变时,我们也只需要改一下配置文件中对于切点的配置就可以了。
下面,我们来看一下Aop思想的一个简单实现的例子(该例子利用Aop思想完成一件事就是当我们调用UserDao对象的saveUser方法时,系统会进行日志记录):
1. 编写IOC容器要用到的配置文件(用来配置bean对象和切点)——beans.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans">
<bean id="UserDao" class="AopTest.UserDao"></bean>
<bean id="LogTool" class="AopTest.LogTool"></bean>
<aop id="logging" ref="LogTool" pointCut="UserDao.*" method="before"></aop>
</beans>

2.编写bean节点配置类,用来封装配置文件中所配置的bean对象的信息——BeanNode.java
package AopTest;

/**
* xml配置文件中配置的bean节点的映射对象
* @author Administrator
*
*/
public class BeanNode
{
private String id;
private String className;

public BeanNode()
{
super();
}

public BeanNode(String id, String className)
{
this.id=id;
this.className=className;
}

public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}


}

3.编写aop节点配置类——AopNode.java
package AopTest;

/**
* xml配置文件中配置的aop节点的映射对象
* @author Administrator
*
*/
public class AopNode
{
private String id;
private String ref;
private String pointCut;
private String method;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getRef() {
return ref;
}
public void setRef(String ref) {
this.ref = ref;
}
public String getPointCut() {
return pointCut;
}
public void setPointCut(String pointCut) {
this.pointCut = pointCut;
}
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}


}

4.编写自定义IOC容器,用来管理配置文件中配置的bean对象——IocContainer.java
package AopTest;

import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.XPath;
import org.dom4j.io.SAXReader;

/**
* 自定义Ioc容器
* @author Administrator
*
*/
public class IocContainer
{
private static java.util.Map<String,BeanNode> beanMap=new java.util.HashMap<String, BeanNode>();
private static java.util.List<AopNode> aopNodes=new java.util.ArrayList<AopNode>();


/**
* 解析配置好的xml文件,读取bean节点对象以及aop节点对象,并添加到队列中
* @param fileName
*/
private void readXML(String fileName)
{
//创建一个文件xml文件读取器
SAXReader saxReader=new SAXReader();
Document document=null;

try
{
//取得类的类装载器,通过类装载器,取得类路径下的文件
java.net.URL xmlPath=this.getClass().getClassLoader().getResource(fileName);
//读取文件内容
System.out.println("xmlPath="+xmlPath);
document=saxReader.read(xmlPath);
//创建一个map对象
java.util.Map<String, String> nameSpaceMap=new java.util.HashMap<String,String>();
//加入命名空间
nameSpaceMap.put("nameSpace", "http://www.springframework.org/schema/beans");

//创建beans/bean查询路径
XPath xpath1=document.createXPath("nameSpace:beans/nameSpace:bean");
//设置命名空间
xpath1.setNamespaceURIs(nameSpaceMap);
//获取文档下所有bean节点
java.util.List<Element> beans=xpath1.selectNodes(document);
System.out.println("bean对象的个数为:"+beans.size());

for(int i=0; i<beans.size(); i++)
{
Element element=beans.get(i);
//获取bean节点对象的id属性
String id=element.attributeValue("id");
System.out.println("bean对象的id为:"+id);
//获取bean节点对象的className属性
String className=element.attributeValue("class");
//创建bean节点对象
BeanNode beanNode=new BeanNode(id, className);
beanMap.put(beanNode.getId(), beanNode);
}

//创建beans/aop查询路径
XPath xpath2=document.createXPath("nameSpace:beans/nameSpace:aop");
//设置命名空间
xpath2.setNamespaceURIs(nameSpaceMap);
//获取文档下所有aop节点
java.util.List<Element> aops=xpath2.selectNodes(document);

for(int i=0; i<aops.size(); i++)
{
Element element=aops.get(i);
//获取aop节点对象的id属性
String id=element.attributeValue("id");
//获取aop节点对象的ref属性
String ref=element.attributeValue("ref");
//获取aop节点对象的pointCut属性
String pointCut=element.attributeValue("pointCut");
//获取aop节点对象的method属性
String method=element.attributeValue("method");

//创建aop节点对象
AopNode aopNode=new AopNode();
aopNode.setId(id);
aopNode.setRef(ref);
aopNode.setPointCut(pointCut);
aopNode.setMethod(method);
aopNodes.add(aopNode);
}

}


catch(Exception e)
{
e.printStackTrace();
}
}

/**
* 获取指定对象的代理对象,可以通过该代理对象执行指定对象的所有方法
* @param id
* @return
* @throws Exception
*/
public  Object getProxyOfBean(String id) throws Exception
{
//解析beans.xml文件
readXML("AopTest/beans.xml");
BeanNode beanNode=beanMap.get(id);
if(null==beanNode)
{
throw new Exception("没有这个东西!id="+id);
}

//得到配置的bean对象,注意,此处调用默认无参构造器
//通过反射机制实例化指定的bean对象
Class c=Class.forName(beanNode.getClassName());
Object  bean=c.newInstance();
//向代理对象传入代理配置参数(即aop节点中配置的参数)
UserDaoProxy.aopNodes=aopNodes;
//获取指定bean对象的代理对象
Object proxy=UserDaoProxy.getProxy(bean);
return proxy;

}

public static BeanNode getBeanNodeById(String id)
{
return beanMap.get(id);
}

//获得指定id的bean对象
public static Object getBean(String id) throws Exception
{
BeanNode beanMapping=beanMap.get(id);
if(null==beanMapping)
{
throw new Exception("没有这个东西!id="+id);
}
//得到配置的bean对象,注意,此处调用默认无参构造器
Class c=Class.forName(beanMapping.getClassName());
Object  bean=c.newInstance();
return bean;
}
}
5.编写目标对象接口——IUserDao.java
package AopTest;

/**
* userDao接口
* @author Administrator
*
*/
public interface IUserDao
{
/**
* 根据用户名和密码,保存用户对象
* @param userName
* @param userPwd
*/
void saveUser(String userName, String userPwd);

/**
* 根据用户id删除用户对象
* @param id
*/
void deleteUser(int id);
}

6.编写目标对象类(实现目标对象接口类)——UserDao.java
package AopTest;

public class UserDao implements IUserDao
{
/**
* 根据用户名和密码,保存用户对象
* @param userName
* @param userPwd
*/
public void saveUser(String userName, String userPwd)
{
System.out.println("保存用户对象成功,用户名:"+userName+" 密码:"+userPwd);
}

/**
* 根据用户id删除用户对象
* @param id
*/
public void deleteUser(int id)
{
System.out.println("删除用户对象成功,用户编号:"+id);
}
}

7.编写日志工具类接口,用来记录某方法调用的信息——ILogTool.java
package AopTest;

/**
* 日志记录工具接口
* @author Administrator
*
*/
public interface ILogTool
{
/**
* 在方法执行前记录日志
* @param m: 正在执行的方法
* @param args: 方法中的参数
*/
void before(java.lang.reflect.Method m, Object[] args);

/**
* 在方法执行之后记录日志
* @param m: 正在执行的方法
* @param args:方法中的参数
*/
void after(java.lang.reflect.Method m, Object[] args);
}

8.编写日志工具实现类——LogTool.java
package AopTest;

/**
* 日志工具实现类
* @author Administrator
*
*/
public class LogTool implements ILogTool
{
/**
* 在方法执行前记录日志
* @param m: 正在执行的方法
* @param args: 方法中的参数
*/
public void before(java.lang.reflect.Method m, Object[] args)
{
//获取方法名字
String methodName=m.getName();
//获取传入方法中的参数值
String paramValue="";
for(int i=0; i<args.length; i++)
{
Object param=args[i];
paramValue=paramValue+param.toString()+",";
}
System.out.println("before execute "+methodName+" method, params:"+paramValue);
}

/**
* 在方法执行之后记录日志
* @param m: 正在执行的方法
* @param args:方法中的参数
*/
public void after(java.lang.reflect.Method m, Object[] args)
{
//获取方法名
String methodName=m.getName();
//获取传入方法中的参数值
String paramValue=null;
for(int i=0; i<args.length; i++)
{
Object param=args[i];
paramValue=" "+param.toString();
}
System.out.println("after execute "+methodName+" params:"+paramValue);
}
}

9.编写目标对象的代理类——UserDaoProxy.java
package AopTest;

import java.lang.reflect.Method;

/**
* 通用代理类的实现
* Aop要通过jdk中的“代理”机制来实现
* 实际上Aop思想的实现就是将包含接入点的目标对象交给其代理对象来管理,客户端通过目标对象的代理对象来调用目标对象提供的接入点方法,
* 这样我们可以在代理对象中调用接入点方法时做其他一些处理,这里我们称之为通知
* @author Administrator
*
*/
public class UserDaoProxy implements java.lang.reflect.InvocationHandler
{
//被切点对象的配置列表,里面是xml中读取的AopXmlMapping对象
static java.util.List<AopNode> aopNodes;
//被代理的对象(即目标对象)
static Object proxiedObj;

/**
* 获取指定对象的代理对象
* @param obj
* @return
*/
public static Object getProxy(Object obj)
{
return java.lang.reflect.Proxy.newProxyInstance(obj.getClass().getClassLoader(),
obj.getClass().getInterfaces(),
new UserDaoProxy(obj));
}

private UserDaoProxy(Object obj)
{
this.proxiedObj=obj;
}

/**
* 实现InvocationHandler接口的invoke方法
* 代理对象被调用时,实际上是执行了该方法
* 因此那些被代理执行的方法应该写在该方法体内
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable
{
Object result=null;
try
{
ILogTool logTool=null;
boolean needAop=false;
for(int i=0; i<aopNodes.size(); i++)
{
//获取aop节点的配置信息
AopNode aopNode=aopNodes.get(i);
//获取切入点或称之为连接点(也就是要切入到哪个对象的哪个方法上去)
String pointCut=aopNode.getPointCut();
//获取包含通知方法的对象(我们在aop节点中配置的是LogTool对象)
String logToolName=aopNode.getRef();
//找到日志工具对象——包含通知方法的对象
logTool=(LogTool)IocContainer.getBean(logToolName);
int limit=pointCut.indexOf(".");
//获取目标对象的beanId
String destPointBeanId=pointCut.substring(0,limit);//匹配beanid部分

//1.获取连接点(Spring中只支持方法类型的连接点)
String methodRane=pointCut.substring(limit+1,pointCut.length());//匹配方法部分
System.out.println("destPointBeanId:  "+destPointBeanId);
//获取目标对象的节点配置信息
BeanNode beanNode=IocContainer.getBeanNodeById(destPointBeanId);
System.out.println("目标对象的类名为: "+beanNode.getClassName());
System.out.println("被代理执行的对象的类名为: "+this.proxiedObj.getClass().getName());
//找到了要切入的目标对象!
if(null!=beanNode&&beanNode.getClassName().equals(this.proxiedObj.getClass().getName())){
if(methodRane.equals("*"))//被代理的对象的所有方法都要接受切入!
{
//2.获取连接点方法的名字,然后执行该方法
if(aopNode.getMethod().equals("before"))
{
//方法前切入(即前置通知)
//这里的method实际上就是客户端调用的被代理对象的方法
logTool.before(method, args);//3.执行要切入的方法(即通知)
//调用切入的目标对象(也就是被代理的对象)的方法(即连接点方法)
//invoke方法的第一个参数为method方法的拥有者,第二个参数为传入method方法的参数对象
result = method.invoke(this.proxiedObj, args);//4.通过代理对象执行连接点方法
needAop=true;
break;
}
else
{
//方法后切入(即后置通知)
//调用切入的目标对象(也就是被代理的对象)的方法(即连接点方法)
result = method.invoke(proxy, args);
logTool.after(method, args);//调用后切入!
needAop=true;
break;
}

}
}
}
if(!needAop)
{
result = method.invoke(proxy, args);//调用目标对象(也就是被代理的对象)的方法
}

catch (Exception e)
{
e.printStackTrace();
throw new RuntimeException("invocation : " + e.getMessage());

return result;
}
}

10.编写测试类——Tester.java
package AopTest;

public class Tester
{
public static void main(String[] args)
{
try
{
IocContainer ic=new IocContainer();
//获取UserDao对象的代理对象
IUserDao userDao=(IUserDao)ic.getProxyOfBean("UserDao");
userDao.saveUser("zzq", "123456");
}
catch(Exception e)
{
e.printStackTrace();
}

}
}

11.测试结果如下:
xmlPath=file:/F:/myeclipse6_workspace/netjava_web_project/WebRoot/WEB-INF/classes/AopTest/beans.xml
bean对象的个数为:2
bean对象的id为:UserDao
bean对象的id为:LogTool
destPointBeanId:  UserDao
目标对象的类名为: AopTest.UserDao
被代理执行的对象的类名为: AopTest.UserDao
before execute saveUser method, params:zzq,123456,
保存用户对象成功,用户名:zzq 密码:123456


最后,想稍微解释一下以上的实现代码,实际上Aop思想的实现主要是依赖于IOC思想以及java中的代理机制。
分享到:
评论
1 楼 javafound 2010-07-21  

相关推荐

    适用spring 实现AOP思想

    在本文中,我们将深入探讨Spring AOP的核心概念、工作原理以及如何在实际项目中实现AOP思想。 1. **AOP概述** 面向切面编程(Aspect-Oriented Programming,AOP)是一种编程范式,旨在减少代码的重复性,提高模块...

    小马哥讲 Spring AOP 编程思想 - API 线索图.pdf

    在讨论Spring AOP(面向切面编程)时,首先需要理解几个核心概念。Spring AOP 是Spring框架提供的一个功能模块,它允许开发者将横切关注点(cross-cutting concerns)从业务逻辑中解耦出来,通过在方法调用前后进行...

    理解Spring AOP实现与思想 案例代码

    Spring AOP(面向切面编程)是...通过深入理解和实践Spring AOP,开发者可以更高效地组织代码,提高代码的可读性和可维护性,同时减少重复的工作。在实际开发中,合理利用AOP可以有效提升软件系统的灵活性和扩展性。

    初探spring aop内部实现 java

    本篇文章将深入探讨Spring AOP的内部实现,以及如何通过源代码理解其DataSource实现和FactoryBean模式。 首先,让我们了解AOP的基本概念。AOP的核心思想是“切面”,它封装了特定的关注点,如日志记录、事务管理、...

    使用Spring配置文件实现AOP

    在Spring中,AOP的实现依赖于两个主要组件:通知(Advice)和切点(Pointcut)。通知是实际执行的增强代码,如方法调用前的记录日志;切点是通知应该应用到的方法或类。Spring支持五种类型的的通知:前置通知...

    spring2.5AOPdemo详细资料

    Spring AOP(面向切面编程)是Spring框架的重要组成部分,它提供了一种在不修改源代码的情况下对程序进行...这个Demo提供了实践和学习AOP的绝佳机会,对于提升你的Spring技能和理解面向切面编程的思想具有重要意义。

    Spring AOP的底层实现技术

    总结来说,Spring AOP通过代理模式和切面编程思想,实现了代码的解耦和模块化,提高了软件的可维护性和可扩展性。了解并熟练掌握Spring AOP的底层实现技术,对于提升开发效率和编写高质量的Java应用程序具有重要意义...

    spring-aop-4.2.6.RELEASE.zip

    在IT行业中,Spring框架无疑是Java开发领域的中流砥柱,而Spring AOP则是其核心组件之一。本资料包"spring-aop-4.2.6.RELEASE.zip"正是针对Spring AOP的一个重要版本,它与"spring-framework.zip"一同构成了强大的...

    aop示例spring 的aop思想解决项目中多次出现的同一个问题

    总的来说,Spring的AOP思想极大地提升了代码的整洁性和可维护性,使得开发者能够更加专注于业务逻辑,而不是重复的公共服务。在实际项目中,合理利用AOP可以显著提高开发效率,并降低系统复杂度。通过深入理解和熟练...

    Spring IOC AOP MVC 简单例子

    在`SpringAOP`目录中,可能包含了定义切面、通知(advice)、切入点(pointcut)等内容。AOP的实现通常通过定义切面类,其中包含通知方法,并通过切入点表达式确定这些通知在何时何地执行。这使得代码更加模块化,...

    spring编程AOP开发包

    6. **代理(Proxy)**:Spring AOP通过动态代理实现对目标对象的拦截,有JDK动态代理和CGLIB代理两种方式。JDK代理适用于目标对象实现接口的情况,而CGLIB代理则可以处理没有接口的对象。 7. **织入(Weaving)**:...

    以注解方式模拟Spring IoC AOP

    Spring提供了两种主要的AOP实现方式:基于代理(Proxy-based)和基于注解(Annotation-based)。 - **基于代理的AOP**:Spring使用JDK动态代理或CGLIB动态代理创建目标对象的代理,代理对象在调用目标方法前后执行...

    spring aop的demo

    在`springAop1`这个压缩包中,可能包含了一个简单的应用示例,展示了如何定义一个切面类,以及如何在该类中定义通知方法。例如,我们可能会看到一个名为`LoggingAspect`的类,其中包含了`@Before`注解的方法,用于在...

    Spring AOP IOC源码笔记.pdf

    Spring框架是Java开发中不可或缺的一部分,它主要由两个核心组件构成:IoC(Inversion of Control,控制反转)和AOP(Aspect Oriented Programming,面向切面编程)。本笔记将深入探讨这两个概念以及它们在Spring中...

    反射实现 AOP 动态代理模式(Spring AOP 的实现 原理) - Java 例子 -

    在Java编程中,AOP(面向切面编程)是一种强大的设计模式,它允许开发者将关注点分离,将横切关注点(如日志、事务管理)与核心业务逻辑解耦...理解Spring AOP的工作原理和反射机制对于开发高质量的Java应用至关重要。

    使用动态代理演示Spring的AOP编程原理

    目的:每次讲解Spring的AOP知识点时,学生都是觉得非常郁闷,因为非常不理理解。其实,Spring没有什么东西,不就是反射技术加设计模式的编程嘛。为了说明Spring的AOP原理,本人使用代理模式中的动态代理完成演示AOP...

    myeclipse spring IOC和AOP 例子

    下面将详细阐述Spring的IOC和AOP,以及如何在实际项目中实现和配置。 ### Spring IOC(Inversion of Control,控制反转) 控制反转是一种设计模式,它的核心思想是将对象的创建和管理交由容器负责,而不是由对象...

    Spring中IOC/AOP的说明和例子

    而在IOC模式下,开发者只需定义对象的接口和实现,Spring容器负责实例化、装配这些对象以及管理它们的生命周期。这样,对象之间的耦合度降低,代码更加模块化,便于测试和维护。 实现IOC的方式主要有两种:依赖注入...

    第四章:Spring AOP API 设计模式1

    在本章中,我们将探讨17种设计模式在Spring AOP中的应用和实现,以及它们如何帮助提升软件的可维护性和扩展性。 1. **抽象工厂模式(Abstract Factory)**:Spring AOP框架中的`AopProxyFactory`接口代表抽象工厂,...

    深入解析Spring AOP源码:从原理到实现,全方位掌握Java AOP编程精髓

    通过学习和理解Spring AOP的源码,开发者可以更好地运用这一技术,定制化地实现自己的切面逻辑,同时优化性能,提高代码质量。对于那些需要更高级功能或对性能有更高要求的项目,可以考虑使用AspectJ进行更深入的AOP...

Global site tag (gtag.js) - Google Analytics