参考AOP入门(一)中的例子,外面来实现用Spring AOP加入各种统计的东东。
注:
这篇文章中,我用的是完全传统的Spring AOP,不带有任何AspectJ的东西。
- 在前面AOP实现一文中提到,Spring AOP要求被代理类必须由Spring容器来管理,即是一个SpringBean。所以,我们要做的第一步,就是配置引入Spring容器管理。
- 在src/main/resources/下添加applicationContext.xml (名字可任意)如下
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
<bean id="zoo" class="com.edi.poc.Zoo"/>
<bean id="dinoHall" class="com.edi.poc.DinoHall"/>
<bean id="jack" class="com.edi.poc.Tourer"/>
</beans>
- 修改main代码如下
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
Zoo zoo = (Zoo) ctx.getBean("zoo");
Hall dinoHall = (Hall)ctx.getBean("dinoHall");
Tourer jack = (Tourer)ctx.getBean("jack");
zoo.open();
jack.visit(zoo, HALL_NAME.DINOSAUR);
zoo.close();
}
这里传入的applicationContext.xml就是Spring配置文件的名字。定的是什么,这里就传什么。所以说,配置名任意。
- 运行下试试
十月 15, 2013 11:07:56 上午 org.springframework.context.support.AbstractApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@1320a41: startup date [Tue Oct 15 11:07:56 CST 2013]; root of context hierarchy
十月 15, 2013 11:07:57 上午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from class path resource [applicationContext.xml]
十月 15, 2013 11:07:57 上午 org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
信息: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@12ebf9a: defining beans [zoo,dinoHall,jack]; root of factory hierarchy
十月 15, 2013 11:07:57 上午 org.springframework.beans.factory.support.DefaultSingletonBeanRegistry destroySingletons
信息: Destroying singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@12ebf9a: defining beans [zoo,dinoHall,jack]; root of factory hierarchy
Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'zoo' defined in class path resource [applicationContext.xml]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Could not instantiate bean class [com.edi.poc.Zoo]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.edi.poc.Zoo.<init>()
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1007)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:953)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:487)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:458)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:295)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:223)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:292)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:628)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:932)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:479)
at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:139)
at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:83)
at com.edi.poc.Main.main(Main.java:12)
Caused by: org.springframework.beans.BeanInstantiationException: Could not instantiate bean class [com.edi.poc.Zoo]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.edi.poc.Zoo.<init>()
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:83)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1000)
... 13 more
Caused by: java.lang.NoSuchMethodException: com.edi.poc.Zoo.<init>()
at java.lang.Class.getConstructor0(Unknown Source)
at java.lang.Class.getDeclaredConstructor(Unknown Source)
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:78)
... 14 more
报错了,为何? [com.edi.poc.Zoo]: No default constructor found; nested exception is java.lang.NoSuchMethodException:
因为我们的zoo没有无参构造函数,Spring的BeanFactory默认会去调无参构造函数。修改如下:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
<bean id="zoo" class="com.edi.poc.Zoo">
<constructor-arg value="People"/>
</bean>
<bean id="dinoHall" class="com.edi.poc.DinoHall"/>
<bean id="jack" class="com.edi.poc.Tourer">
<constructor-arg value="Jack"/>
</bean>
</beans>
得到结果:
Dinosaur hall is opened.
The People Zoo is opened.
Charge Jack $1.00 for ticket.
Jack needs to be charged first.
Dianosaur hall charges Jack $2.00
Jack visited diano hall.
Dinosaur hall is closed.
The People Zoo is closed.
- 好,现在加入整个公园的客流量,显然是在Zoo的enter方法出+1,即before enter +1
- 创建统计类
package com.edi.poc.statistic;
public class Statistic {
private static int totalTraffic = 0;
public static void increaseTotalTraffic()
{
totalTraffic ++;
}
public static int getTotalTraffic()
{
return totalTraffic;
}
}
- 创建一个简单的beforeadvice
package com.edi.poc.aop;
import java.lang.reflect.Method;
import org.springframework.aop.MethodBeforeAdvice;
public class MyBeforeMethod implements MethodBeforeAdvice {
public void before(Method arg0, Object[] arg1, Object arg2)
throws Throwable {
System.out.println("Before method...");
}
}
- 修改配置文件
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
<bean id="zoo" class="com.edi.poc.Zoo">
<constructor-arg value="People"/>
</bean>
<bean id="dinoHall" class="com.edi.poc.DinoHall"/>
<bean id="jack" class="com.edi.poc.Tourer">
<constructor-arg value="Jack"/>
</bean>
<bean id="myBeforeAdvice" class="com.edi.poc.aop.MyBeforeMethod"/>
<bean id="customServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="zoo"/>
<property name="interceptorNames">
<list>
<value>myBeforeAdvice</value>
</list>
</property>
<!--
<property name="proxyTargetClass">
<value>true</value>
</property>
-->
</bean>
</beans>
注:
Spring的ProxyFactory在创建Bean的时候,动态的去选择JDK或CGLIB来做反射生成目标类。可以通过上面蓝色部分强制声明为CGLIB。
- 运行下试试
十月 15, 2013 11:45:36 上午 org.springframework.context.support.AbstractApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@e14d81: startup date [Tue Oct 15 11:45:36 CST 2013]; root of context hierarchy
十月 15, 2013 11:45:36 上午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from class path resource [applicationContext.xml]
十月 15, 2013 11:45:36 上午 org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
信息: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@14c428f: defining beans [zoo,dinoHall,jack,myBeforeAdvice,customServiceProxy]; root of factory hierarchy
Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'customServiceProxy': FactoryBean threw exception on object creation; nested exception is org.springframework.aop.framework.AopConfigException: Could not generate CGLIB subclass of class [class com.edi.poc.Zoo]: Common causes of this problem include using a final class or a non-visible class; nested exception is java.lang.IllegalArgumentException: Superclass has no null constructors but no arguments were given
at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.doGetObjectFromFactoryBean(FactoryBeanRegistrySupport.java:149)
at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.getObjectFromFactoryBean(FactoryBeanRegistrySupport.java:102)
at org.springframework.beans.factory.support.AbstractBeanFactory.getObjectForBeanInstance(AbstractBeanFactory.java:1454)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:249)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194)
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1117)
at com.edi.poc.Main.main(Main.java:13)
Caused by: org.springframework.aop.framework.AopConfigException: Could not generate CGLIB subclass of class [class com.edi.poc.Zoo]: Common causes of this problem include using a final class or a non-visible class; nested exception is java.lang.IllegalArgumentException: Superclass has no null constructors but no arguments were given
at org.springframework.aop.framework.CglibAopProxy.getProxy(CglibAopProxy.java:217)
at org.springframework.aop.framework.ProxyFactoryBean.getProxy(ProxyFactoryBean.java:363)
at org.springframework.aop.framework.ProxyFactoryBean.getSingletonInstance(ProxyFactoryBean.java:317)
at org.springframework.aop.framework.ProxyFactoryBean.getObject(ProxyFactoryBean.java:243)
at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.doGetObjectFromFactoryBean(FactoryBeanRegistrySupport.java:142)
... 6 more
Caused by: java.lang.IllegalArgumentException: Superclass has no null constructors but no arguments were given
at org.springframework.cglib.proxy.Enhancer.emitConstructors(Enhancer.java:721)
at org.springframework.cglib.proxy.Enhancer.generateClass(Enhancer.java:499)
at org.springframework.cglib.transform.TransformingClassGenerator.generateClass(TransformingClassGenerator.java:33)
at org.springframework.cglib.core.DefaultGeneratorStrategy.generate(DefaultGeneratorStrategy.java:25)
at org.springframework.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:216)
at org.springframework.cglib.proxy.Enhancer.createHelper(Enhancer.java:377)
at org.springframework.cglib.proxy.Enhancer.create(Enhancer.java:285)
at org.springframework.aop.framework.CglibAopProxy.getProxy(CglibAopProxy.java:205)
... 10 more
这里原因就是我实现的Zoo类有一个非空参的构造函数,那么空参构造函数就必须自己声明,CGLIB要求必须提供一个空参构造器。
加上空参构造器后再执行,打印如下:
Before method...
Before method...
Dinosaur hall is opened.
The People Zoo is opened.
Before method...
Charge Jack $1.00 for ticket.
Jack needs to be charged first.
Dianosaur hall charges Jack $2.00
Jack visited diano hall.
Before method...
Dinosaur hall is closed.
The People Zoo is closed.
这里看到 "Before method" 打印了四次,因为zoo总共被调用了四次
Before .open()
Before .addHall()
Before.enter()
Before .close()
- 其实我们只要before.enter(),所以要指定改BeforeAdvice到enter()这个joinpoint,这个绑定就需要用到advisor了。advisor是Spring自己创造的一个专门做这种绑定的东东。修改配置如下:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
<!-- Configure Target Objects -->
<bean id="zoo" class="com.edi.poc.Zoo">
<constructor-arg value="People"/>
</bean>
<bean id="dinoHall" class="com.edi.poc.DinoHall"/>
<bean id="jack" class="com.edi.poc.Tourer">
<constructor-arg value="Jack"/>
</bean>
<!-- Configure pointcut -->
<bean id="customPointcut" class="org.springframework.aop.support.NameMatchMethodPointcut">
<property name="mappedName" value="enter"/>
</bean>
<!-- Configure Advice -->
<bean id="myBeforeAdvice" class="com.edi.poc.aop.MyBeforeMethod"/>
<!-- Configure Advisor -->
<bean id="customAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="pointcut" ref="customPointcut"/>
<property name="advice" ref="myBeforeAdvice"/>
</bean>
<bean id="customServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="zoo"/>
<property name="interceptorNames">
<list>
<value>customAdvisor</value>
</list>
</property>
<!--
<property name="proxyTargetClass">
<value>true</value>
</property>
-->
</bean>
</beans>
这里定义了一个pointcut选取名字为“enter”的方法(joinpoint),定义了一个customadvisor把这个pointcut和advice关联起来。
执行结果:
Dinosaur hall is opened.
The People Zoo is opened.
Before method...
Charge Jack $1.00 for ticket.
Jack needs to be charged first.
Dianosaur hall charges Jack $2.00
Jack visited diano hall.
Dinosaur hall is closed.
The People Zoo is closed.
- 修改advice为我们真正的义务逻辑
public void before(Method arg0, Object[] arg1, Object arg2)
throws Throwable {
System.out.println("Before method...");
Statistic.increaseTotalTraffic();
}
再修改下main,把traffic打出来
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
Zoo zoo = (Zoo) ctx.getBean("customServiceProxy");
Hall dinoHall = (Hall)ctx.getBean("dinoHall");
zoo.addHall(HALL_NAME.DINOSAUR, dinoHall);
Tourer jack = (Tourer)ctx.getBean("jack");
zoo.open();
jack.visit(zoo, HALL_NAME.DINOSAUR);
System.out.println("Current traffic: " + Statistic.getTotalTraffic());
zoo.close();
}
- 运行,结果如下:
Dinosaur hall is opened.
The People Zoo is opened.
Before method...
Charge Jack $1.00 for ticket.
Jack needs to be charged first.
Dianosaur hall charges Jack $2.00
Jack visited diano hall.
Current traffic: 1
Dinosaur hall is closed.
The People Zoo is closed.
- 我们再来实现统计场馆收入的记录,即after visit
- 修改统计类,加入income记录如下:
public class Statistic {
private static int totalTraffic = 0;
private static Map<HALL_NAME, BigDecimal> incomeOfHalls = new HashMap<HALL_NAME, BigDecimal>();
public static void increaseTotalTraffic()
{
totalTraffic ++;
}
public static int getTotalTraffic()
{
return totalTraffic;
}
public static BigDecimal getIncome(HALL_NAME hallName)
{
BigDecimal currentAmt = incomeOfHalls.get(hallName);
if(currentAmt!=null)
return currentAmt;
return BigDecimal.valueOf(0);
}
public static void increaseIncome(HALL_NAME hallName, BigDecimal amount)
{
BigDecimal currentAmount = incomeOfHalls.get(hallName);
if(currentAmount==null)
currentAmount = amount;
else
currentAmount = currentAmount.add(amount);
incomeOfHalls.put(hallName, currentAmount);
}
}
好吧,饶过我吧……我偷懒了……这只是个例子,请无视多线程问题和其他问题……只要能跑就够了。
- 创建after advice,并修改配置文件
public class MyAfterMethod implements AfterReturningAdvice{
public void afterReturning(Object returnValue, Method method,
Object[] args, Object target) throws Throwable {
System.out.println("After return...");
Statistic.increaseIncome(HALL_NAME.DINOSAUR, BigDecimal.valueOf(2l));
}
}
配置:
<bean id="customPointcut2" class="org.springframework.aop.support.NameMatchMethodPointcut">
<property name="mappedName" value="visit"/>
</bean>
<bean id="myAfterAdvice" class="com.edi.poc.aop.MyAfterMethod"/>
<bean id="customAdvisor2" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="pointcut" ref="customPointcut2"/>
<property name="advice" ref="myAfterAdvice"/>
</bean>
<bean id="customServiceProxy2" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="dinoHall"/>
<property name="interceptorNames">
<list>
<value>customAdvisor2</value>
</list>
</property>
</bean>
- 修改main,并运行
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
Zoo zoo = (Zoo) ctx.getBean("customServiceProxy");
Hall dinoHall = (Hall)ctx.getBean("customServiceProxy2");
zoo.addHall(HALL_NAME.DINOSAUR, dinoHall);
Tourer jack = (Tourer)ctx.getBean("jack");
zoo.open();
jack.visit(zoo, HALL_NAME.DINOSAUR);
System.out.println("Current traffic: " + Statistic.getTotalTraffic());
NumberFormat currency = NumberFormat.getCurrencyInstance();
System.out.println("Income of " + HALL_NAME.DINOSAUR + ": "+ currency.format(Statistic.getIncome(HALL_NAME.DINOSAUR)));
zoo.close();
}
输出:
Dinosaur hall is opened.
The People Zoo is opened.
Before method...
Charge Jack $1.00 for ticket.
Jack needs to be charged first.
Dianosaur hall charges Jack $2.00
Jack visited diano hall.
After return...
Current traffic: 1
Income of DINOSAUR: $2.00
Dinosaur hall is closed.
The People Zoo is closed.
- 这里看到有个问题,每个Target都得声明一个ProxyFactoryBean,这样非常麻烦,后期开发维护量巨大。为了相当程度的减轻这个问题,Spring提供了自动代理。
自动代理提供了AbstractAdivosrAutoProxyCreator,可以方便继承,原生提供了两个Creator:
BeanNameAutoProxyCreator 根据Bean名称创建代理(针对Bean所有方法)
DefaultAdvisorAutoProxyCreator 根据advisor本身包含信息创建代理(针对特定方法)
- 修改使用BeanNamesAutoProxyCreator
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<!-- Configure the name filter -->
<property name="beanNames" value="*zoo"></property>
<property name="interceptorNames" value="customAdvisor"></property>
</bean>
- 修改使用DefaultAdvisorAutoProxyCreator
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>
这种方式感觉有点过于强大了,以至于很容易出错。本例用这个Creator就报错了。但是考虑到现在这种技术很少用了,就没有深究。这里只是介绍下有这种方式存在。如果以后遇到类似用法,再补坑。
- 总结
总的来说,传统的Spring AOP实现方式就是动态代理,但是有各种各样的限制和麻烦:
- 被代理类以及其父类(如果有)必须有无参构造函数;(有点像早期的序列化要求)
- pointcut, advice,advisor都是成套出现,为实现代理一个方法,可能就得实现这三个的一套,显得重复而臃肿;
- 如果不使用自动代理,每个Target都有一个ProxyFactoryBean,及其臃肿;
- 如果使用动态代理,advice,advisor定义不是非常强大,aspect实现绑定比较麻烦。
本文源码:
https://github.com/EdisonXu/POC/tree/master/intro-aop
Spring 传统AOP实例
最新推荐文章于 2022-05-21 13:03:37 发布