zoukankan      html  css  js  c++  java
  • 深入理解spring中的AOP原理 —— 实现MethodInterceptor接口,自已动手写一个AOP

     

    1.前言

           AOP是面向切面编程,即“Aspect Oriented Programming”的缩写。面对切面,就是面向我们的关注面,不能让非关注面影响到我们的关注面。而现实中非关切面又必不可少,例如获取资源、释放资源、处理异常、记录日志等,太多的非关切面会让关切面的代码变得杂糅,难以维护。此时面向切面编程便是解决此问题的方案,减少非关切面的东西,让我们只专注于核心业务代码。而要理解AOP,就必须要懂得代理模式是啥,能干啥,特别要清楚Cglib动态代理是咋回事(不懂的可以看这篇文章)。

    2.实现过程

        为了直观看到各个类,我将示例所需的类作为静态内部类放在外层类中。

    package demo;
     
    import java.util.Arrays;
     
    import org.aopalliance.intercept.MethodInterceptor;
    import org.aopalliance.intercept.MethodInvocation;
    import org.springframework.aop.framework.ProxyFactory;
     
    public class SpringAop {
     
        public static class UserDaoImpl {
            public int addUser(String user) {
                System.out.println("保存了一个用户 " + user);
                return 1;
     
            }
     
            public int deleteUser(int id) {
     
                if (id <= 0 || id > 99999999) {
                    throw new IllegalArgumentException("参数id不能大于99999999或小于0");
                }
     
                System.out.println("删除了id为" + id + "用户");
                return 1;
            }
        }
     
        /**
         * 自定义的方法拦截器
         *
         */
        public static class UserMethodInterceptor implements MethodInterceptor {
     
            @Override
            public Object invoke(MethodInvocation mi) throws Throwable {
                String methodName = mi.getMethod().getName();// 方法名
                Object returnVal = null;
     
                /*
                 * 根据方法名的不同设置不同的拦截策类 似于配置文件中的<aop:pointcut
                 * expression="execution( xxxmethodName)"/>,以此定义切入点。
                 *
                 * spring框架中可以根据方法对应的包、对应的参数、返回值、方法名等多个条件,并结合正则表达式来定义切入点
                 * 为了简单化,此处我只用方法名的前缀来定义切入点。
                 *
                 */
     
                if (methodName.startsWith("add")) {
     
                    returnVal = beforeEnhance(mi);
     
                } else if (methodName.startsWith("delete")) {
                    returnVal = afterThrowingEnhance(mi);
                } else {
                    returnVal = mi.proceed();
                }
     
                return returnVal;
            }
     
            /*
             * 前置增强策略
             */
            private Object beforeEnhance(MethodInvocation mi) throws Throwable {
                /*
                 * spring框架通过配置文件或注解来获得用户自定义的增强方法名及对其JavaBean的类名, 然后通过反射机制动态地调用用户的增强方法。
                 * 为了简单化的测试,我这里将增强方法及对应的类给定死了、不能改动, 增强JavaBean就是ConsoloEnhancer,
                 * 增强方法就是"public void before(MethodInvocation mi)"
                 *
                 */
                new ConsoloEnhancer().before(mi); // 调用用户定义的前置处理方式
                Object returnVal = mi.proceed(); // 执行目标方法
                return returnVal;
            }
     
            /*
             * 异常处理策略
             */
            private Object afterThrowingEnhance(MethodInvocation mi) throws Throwable {
                try {
                    return mi.proceed();// 执行目标方法
                } catch (Throwable e) {
                    new ConsoloEnhancer().afterThrowing(mi, e); // 调用用户定义的异常处理方式
                    throw e;
     
                }
     
            }
        }
     
        /**
         * 定义包含增强方法的JavaBean
         *
         */
        public static class ConsoloEnhancer {
            /**
             * 前置增强方法
             *
             * @param mi
             */
            public void before(MethodInvocation mi) {
                /*
                 * 这里的MethodInvocation类型参数mi和开发中常用的切点工具类JoinPoint的作用类似,它可以获取
                 * 目标对象("Object getThis()"方法)、目标方法("Method getMethod()"方法)、
                 * 方法参数("Object[] getArguments()"方法)等信息
                 */
                System.out.print("调用了" + mi.getThis() + "对象的" + mi.getMethod().getName() + "方法。方法入参原始值是"
                        + Arrays.toString(mi.getArguments()) );
                if (mi.getThis().getClass() == UserDaoImpl.class) {         //对方法入参进行修改
                    Object[] args = mi.getArguments();
                    String enhancedAgr ="user-" +(String) args[0]  ;
                    args[0] = enhancedAgr;
                }
                System.out.println(".而增强后的方法入参是"+Arrays.toString(mi.getArguments()) );
                System.out.println("*********************前置增强加星号 *********************");
            }
     
            /**
             * 处理异常的增强方法
             *
             * @param mi
             * @param e
             */
            public void afterThrowing(MethodInvocation mi, Throwable e) {
                System.out.println("调用了" + mi.getThis() + "对象的" + mi.getMethod().getName() + "方法,当方法入参是"
                        + Arrays.toString(mi.getArguments()) + "时,发生了异常。");
                System.out.println("异常的堆栈信息是" + Arrays.toString(e.getStackTrace()));
                System.out.println("!!!!!!!!!!!!!!!!!!!!!异常加感叹号!!!!!!!!!!!!!!!!!!!!!!");
            }
     
        }
     
        public static void main(String[] args) {
            UserDaoImpl userDaoImpl = new UserDaoImpl();
            ProxyFactory proxyFactory = new ProxyFactory(); // 初始化一个代理工厂
            // 设置目标类,以便于Cglib工具包动态生成目标类的子类,即我们所需的代理类
            proxyFactory.setTarget(userDaoImpl);
            /*
             * 设置拦截器,而拦截器的"public Object invoke(MethodInvocation mi)"定义了
             * 代理类(实际是UserDaoImpl的子类)的方法生成策略。
             *
             */
            proxyFactory.addAdvice(new UserMethodInterceptor());
     
            // 向上转型,转型为父类类型
            UserDaoImpl proxyUserDaoImpl = (UserDaoImpl) proxyFactory.getProxy();
     
            /**
             * 调用代理方法
             */
            proxyUserDaoImpl.deleteUser(2);  //应该正常执行,没有什何增强效果
            System.out.println("");// 换行
            proxyUserDaoImpl.addUser("李华");  //入参值会被修改
            System.out.println("");// 换行
            proxyUserDaoImpl.deleteUser(-1);  // 将异常处理效果
     
        }
    }
    各个内部类

     实现细节

    1)先定义一个业务类。

    这个简单的业务类有保存用户、删除用户的功能。

    	public static class UserDaoImpl {
    		public int addUser(String user) {
    			System.out.println("保存了一个用户 " + user);
    			return 1;
    
    		}
    
    		public int deleteUser(int id) {
    
    			if (id <= 0 || id > 99999999) {
    				throw new IllegalArgumentException("参数id不能大于99999999或小于0");
    			}
    
    			System.out.println("删除了id为" + id + "用户");
    			return 1;
    		}
    	}
    

     

    2)自定义一个通知(实际上是一个拦截器)

             这个Advice通知实现了 MethodInterceptor接口,而MethodInterceptor接口继承了Interceptor接口,Intercepto接口又继承了Advice接口,因此我个将这拦截器称为一个通知。

    为了偷懒,此处的Advice通知一次性实现了两个功能,前置增强和后异常处理增强,明显不满足“单一职责”的原则,此处同时要处理两种增强。而spring框架中,当读到配置文件的"<aop:before>"标签,就安排一个AspectJMethodBeforeAdvice类(继承于AbstractAspectAdive抽象类,此抽象类实现了Advice接口)去处理前置增强;当读到配置文件的"<aop:after-throwing>"标签,则会安排一AspectJAfterThrowingAdvice类(实现了MethodInterceptor接口)去做异常增强处理。spring框架切实做到了遵循”单一职责“原则,专门组织一个类去处理一种类型(前置、后置或最终等)的增强。

             另外需要注意的是:这里的MethodInterceptor接口是位于"org.aopalliance.intercept"包,除此之外Cglib工具包中的"org.springframework.cglib.proxy"包也有MethodInterceptor接口,但这个接口不是我们所需要的。

     Advice接口结构体系图(配图引用自"spring适配器模式-aop中的MethodInterceptor拦截器")

    	public static class UserMethodInterceptor implements MethodInterceptor {
    
    		@Override
    		public Object invoke(MethodInvocation mi) throws Throwable {
    			String methodName = mi.getMethod().getName();// 方法名
    			Object returnVal = null;
    
    			/*
    			 * 根据方法名的不同设置不同的拦截策类 似于配置文件中的<aop:pointcut
    			 * expression="execution( xxxmethodName)"/>,以此定义切入点。
    			 *
    			 * spring框架中可以根据方法对应的包、对应的参数、返回值、方法名等多个条件,并结合正则表达式来定义切入点
    			 * 为了简单化,此处我只用方法名的前缀来定义切入点。
    			 *
    			 */
    			if (methodName.startsWith("add")) {
    
    				returnVal = beforeEnhance(mi);
    
    			} else if (methodName.startsWith("delete")) {
    				returnVal = afterThrowingEnhance(mi);
    			} else {
    				returnVal = mi.proceed();
    			}
    
    			return returnVal;
    		}
    
    		/*
    		 * 前置增强策略
    		 */
    		private Object beforeEnhance(MethodInvocation mi) throws Throwable {
    			/*
    			 * spring框架通过配置文件或注解来获得用户自定义的增强方法名及对其JavaBean的类名, 然后通过反射机制动态地调用用户的增强方法。
    			 * 为了简单化的测试,我这里将增强方法及对应的类给定死了、不能改动, 增强JavaBean就是ConsoloEnhancer,
    			 * 增强方法就是"public void before(MethodInvocation mi)"
    			 * 
    			 */
    			new ConsoloEnhancer().before(mi); // 调用用户定义的前置处理方式
    			Object returnVal = mi.proceed(); // 执行目标方法
    			return returnVal;
    		}
    
    		/*
    		 * 异常处理策略
    		 */
    		private Object afterThrowingEnhance(MethodInvocation mi) throws Throwable {
    			try {
    				return mi.proceed();// 执行目标方法
    			} catch (Throwable e) {
    				new ConsoloEnhancer().afterThrowing(mi, e); // 调用用户定义的异常处理方式
    				throw e;
    
    			}
    
    		}
    	}
    

     

    3) 用户定义包含增强方法的JavaBean

    MthodInvocation接口比较有意思,它的祖先级接口是Joinpoint ,注意不是开发中的JoinPoint,但MthodInvocation封装的信息和JoinPoint差不多,都包含目标对象、方法的参数、目标方法等。

    	public static class ConsoloEnhancer {
    		/**
    		 * 前置增强方法
    		 * 
    		 * @param mi
    		 */
    		public void before(MethodInvocation mi) {
    			/*
    			 * 这里的MethodInvocation类型参数mi和开发中常用的切点工具类JoinPoint的作用类似,它可以获取
    			 * 目标对象("Object getThis()"方法)、目标方法("Method getMethod()"方法)、
    			 * 方法参数("Object[] getArguments()"方法)等信息
    			 */
    			System.out.print("调用了" + mi.getThis() + "对象的" + mi.getMethod().getName() + "方法。方法入参原始值是"
    					+ Arrays.toString(mi.getArguments()) );
    			if (mi.getThis().getClass() == UserDaoImpl.class) {			//对方法入参进行修改
    				Object[] args = mi.getArguments();
    				String enhancedAgr ="user-" +(String) args[0]  ;
    				args[0] = enhancedAgr;
    			}
    			System.out.println(".而增强后的方法入参是"+Arrays.toString(mi.getArguments()) );
    			System.out.println("*********************前置增强加星号 *********************");
    		}
    
    		/**
    		 * 处理异常的增强方法
    		 * 
    		 * @param mi
    		 * @param e
    		 */
    		public void afterThrowing(MethodInvocation mi, Throwable e) {
    			System.out.println("调用了" + mi.getThis() + "对象的" + mi.getMethod().getName() + "方法,当方法入参是"
    					+ Arrays.toString(mi.getArguments()) + "时,发生了异常。");
    			System.out.println("异常的堆栈信息是" + Arrays.toString(e.getStackTrace()));
    			System.out.println("!!!!!!!!!!!!!!!!!!!!!异常加感叹号!!!!!!!!!!!!!!!!!!!!!!");
    		}
    
    	}
    

      

     

    4)方法测试

    	public static void main(String[] args) {
    		UserDaoImpl userDaoImpl = new UserDaoImpl();
    		ProxyFactory proxyFactory = new ProxyFactory(); // 初始化一个代理工厂
    		// 设置目标类,以便于Cglib工具包动态生成目标类的子类,即我们所需的代理类
    		proxyFactory.setTarget(userDaoImpl);
    		/*
    		 * 设置拦截器,而拦截器的"public Object invoke(MethodInvocation mi)"定义了
    		 * 代理类(实际是UserDaoImpl的子类)的方法生成策略。
    		 * 
    		 */
    		proxyFactory.addAdvice(new UserMethodInterceptor());
    
    		// 向上转型,转型为父类类型
    		UserDaoImpl proxyUserDaoImpl = (UserDaoImpl) proxyFactory.getProxy();
    
    		/**
    		 * 调用代理方法
    		 */
    		proxyUserDaoImpl.deleteUser(2);  //应该正常执行,没有什何增强效果
    		System.out.println("");// 换行
    		proxyUserDaoImpl.addUser("李华");  //入参值会被修改
    		System.out.println("");// 换行
    		proxyUserDaoImpl.deleteUser(-1);  // 将异常处理效果
    
    	}
    

      控制台输出

    3.总结

        spring框架的AOP非常强大,可以实现前置增强、后置增强、最终增强、环绕增强等处理,另外还可以对方法入参进行控制过滤,而影响目标方法的执行中的状态。AOP都是基于(Cglib)代理模式实现的,其中的关键点在于实现MethodInterceptor接口,在其“public Object invoke(MethodInvocation mi)”方法中制定代理方法的生成策略,而从此方法的MethodInvocation类型参数mi中可以获得目标对象、方法的参数、目标方法等信息,根据这些信息可以精确地控制增强效果。

  • 相关阅读:
    如何编写CMakeLists.txt
    C++11 condition_variable
    TCP/IP 收发数据
    CLion 远程开发和调试C/C++代码
    Python unittest mock 在哪里打patch
    MVCC版本链
    数据无法修改?解密MVCC原理
    MVCC ReadView介绍
    正则表达式备忘(基于JavaScript)
    《C+编程规范 101条规则、准则与最佳实践》笔记
  • 原文地址:https://www.cnblogs.com/gocode/p/aop-in-spring.html
Copyright © 2011-2022 走看看