您的位置:首页 > 编程语言 > Java开发

Spring 传统AOP实例

2013-10-30 09:07 357 查看
参考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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: