Spring 传统AOP实例

9 篇文章 0 订阅
  1. 参考AOP入门()中的例子,外面来实现用Spring AOP加入各种统计的东东。

     

    注:

    这篇文章中,我用的是完全传统的Spring AOP,不带有任何AspectJ的东西。

     

  2. 在前面AOP实现一文中提到,Spring AOP要求被代理类必须由Spring容器来管理,即是一个SpringBean。所以,我们要做的第一步,就是配置引入Spring容器管理。
    1. 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>

    1. 修改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配置文件的名字。定的是什么,这里就传什么。所以说,配置名任意。

    1. 运行下试试

    十月 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没有无参构造函数,SpringBeanFactory默认会去调无参构造函数。修改如下:

    <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.

  3. 好,现在加入整个公园的客流量,显然是在Zooenter方法出+1,即before enter +1
    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;

    }

    }

    1. 创建一个简单的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...");

    }

     

    }

    1. 修改配置文件

    <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>

     

    注:

    SpringProxyFactory在创建Bean的时候,动态的去选择JDKCGLIB来做反射生成目标类。可以通过上面蓝色部分强制声明为CGLIB

     

    1. 运行下试试

    十月 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()

     

    1. 其实我们只要before.enter(),所以要指定改BeforeAdviceenter()这个joinpoint,这个绑定就需要用到advisor了。advisorSpring自己创造的一个专门做这种绑定的东东。修改配置如下:

    <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把这个pointcutadvice关联起来。

    执行结果:

    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.

     

    1. 修改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();

    }

    1. 运行,结果如下:

    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.

     

  4. 我们再来实现统计场馆收入的记录,即after visit
    1. 修改统计类,加入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);

    }

    }

    好吧,饶过我吧……我偷懒了……这只是个例子,请无视多线程问题和其他问题……只要能跑就够了。

     

    1. 创建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>

     

    1. 修改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.

     

  5. 这里看到有个问题,每个Target都得声明一个ProxyFactoryBean,这样非常麻烦,后期开发维护量巨大。为了相当程度的减轻这个问题,Spring提供了自动代理。

    自动代理提供了AbstractAdivosrAutoProxyCreator,可以方便继承,原生提供了两个Creator

    BeanNameAutoProxyCreator 根据Bean名称创建代理(针对Bean所有方法)

    DefaultAdvisorAutoProxyCreator 根据advisor本身包含信息创建代理(针对特定方法)

    1. 修改使用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>

    1. 修改使用DefaultAdvisorAutoProxyCreator

    <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>

    这种方式感觉有点过于强大了,以至于很容易出错。本例用这个Creator就报错了。但是考虑到现在这种技术很少用了,就没有深究。这里只是介绍下有这种方式存在。如果以后遇到类似用法,再补坑。

     

  6. 总结

    总的来说,传统的Spring AOP实现方式就是动态代理,但是有各种各样的限制和麻烦:

    1. 被代理类以及其父类(如果有)必须有无参构造函数;(有点像早期的序列化要求)
    2. pointcut, advice,advisor都是成套出现,为实现代理一个方法,可能就得实现这三个的一套,显得重复而臃肿;
    3. 如果不使用自动代理,每个Target都有一个ProxyFactoryBean,及其臃肿;
    4. 如果使用动态代理,adviceadvisor定义不是非常强大,aspect实现绑定比较麻烦。

     

    本文源码:

    https://github.com/EdisonXu/POC/tree/master/intro-aop

     

     

     

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值