SpringAOP____配置之向代理类注入Advice

来源:互联网 发布:大数据 零售业思路 编辑:程序博客网 时间:2024/06/14 04:27

何为AOP:

AOP是Aspect-Oriented Programming(面向切面编程)的简写。AOP是对OOP模式的一种补充和扩展,SpringAOP是AOP模式的一个框架。通过织入的方法将逻辑织入方法内,又不破坏代码的封装性。降低了业务逻辑之间的耦合度。


OOP术语:

方面(Aspect):相当于OOP中的类,就是封装用于横插入系统的功能。例如日志、事务、安全验证等。

通知(Advice):相当于OOP中的方法,是编写实际功能代码的地方。

连接点(Joinpoint):程序执行过程中插入方面的地方。Spring AOP只支持在方法调用和异常抛出中插入方面代码。

切入点(Pointcut):定义通知应该织人到哪些连接点上。通常切入点指的是类或方法名。例如某个通知要应用所有以abc开头的方法中,那么所有满足这个规则的方法都是切入点。

目标(Target):目标类或者目标接口。

代理(Proxy):AOP工作时是通过代理对象来访问目标对象。其实AOP的实现是通过动态代理,离不开代理模式,所以必须要有一个代理对象。

织入(Weaving):在目标对象中插入方面代码的过程就叫做织入。


xml配置AOP:


aop实现的原理其实就是利用了jdk的动态反射机制对实现了接口的类或者接口(类必须实现接口),产生一个代理类,代理类中实现了接口中的所有方法,并可以在代理类中实现自己的逻辑。

通过一个例子看看如何生成AOP。

业务类:

首先要有个接口,还要有个类实现这个接口:

接口:

package com.xy.business.logic.logicInterface;public interface TestLogicInterface {public void add(String name);public void delete(String name);public boolean exist(String name);}

接口的实现类:

package com.xy.business.logic.logicImpl;import com.xy.business.logic.logicInterface.TestLogicInterface;public class TestLogicImpl implements TestLogicInterface {@Overridepublic void add(String name) {System.out.println("用户"+name+"添加成功");}@Overridepublic void delete(String name) {System.out.println("用户"+name+"删除成功");}@Overridepublic boolean exist(String name) {if("许阳".equals(name)){return true;}throw new RuntimeException("不是许阳不给你运行!");}}

接着,我们需要写几个类用来描述我们的joinPoint


Advice类:


前置通知(BeforeMethod):

package com.xy.aop;import java.lang.reflect.Method;import org.springframework.aop.MethodBeforeAdvice;/** * @author 前置通知 * @description 方法执行之间执行 * @Todo 必须实现MethodBeforeAdvice接口 */public class BeforeMethod implements MethodBeforeAdvice {        /**     *@param method 要执行切点的方法     *@param arg  这些方法的参数值     *@param target 切点所在类的实例对象     */    @Override    public void before(Method method, Object[] arg1, Object target)            throws Throwable {        String className = target.getClass().getName();        String methodName = method.getName();        System.out.println("我是执行在"+className+"的"+methodName+"方法(---->前<----)的逻辑!");        }}
注意方法必须实现MethodBeforeAdvice接口,重写before方法。配置到xml中,Spring根据类实现的接口才能知道方法中的逻辑该在何时运行(joinPoint)。


后置通知(AfterMethod):

package com.xy.aop;import java.lang.reflect.Method;import org.springframework.aop.AfterReturningAdvice;/** * @author 后置通知 * @description 执行方法后执行 * @Todo 必须实现AfterReturningAdvice接口 */public class AfterMethod implements AfterReturningAdvice {/** * @param returnValue 被拦截方法的返回值 */@Overridepublic void afterReturning(Object returnValue, Method method,Object[] args, Object target) throws Throwable {String className = target.getClass().getName();String methodName = method.getName();System.out.println("我是执行在"+className+"的"+methodName+"方法(---->后<----)的逻辑");}}


环绕通知(AroundMethod):

package com.xy.aop;import org.aopalliance.intercept.MethodInterceptor;import org.aopalliance.intercept.MethodInvocation;/** * @author 环绕通知 * @description 环绕通知能力最强,可以在方法调用前执行通知代码,可以决定是否还调用目标方法。也就是说它可以控制被拦截的方法的执行,还可以控制被拦截方法的返回值。 * @Todo 必须实现MthodInterceptor接口 */public class AroundMethod implements MethodInterceptor {@Override/** * @param invocation 代理类 */public Object invoke(MethodInvocation invocation) throws Throwable {String metodName = invocation.getMethod().getName();System.out.println("环绕通知准备记录时间");long startTime = System.currentTimeMillis();invocation.proceed();//调用被拦截的方法long endTime = System.currentTimeMillis();System.out.println("环绕通知: 执行"+metodName+"方法花费时间"+(endTime-startTime));//将拦截方法的返回值设置为null//return invocation.proceed();return null;}}


异常通知(throwMethod):

package com.xy.aop;import java.lang.reflect.Method;import org.springframework.aop.ThrowsAdvice;/** *  * @author 方法报错通知 * @description 在方法报错之后切入的方法 * @Todo 必须实现ThrowsAdvice接口 */public class ThrowMthod implements ThrowsAdvice{/* * e表示方法抛出的异常类型,只根据异常类型切入方法public void afterThrowing(Throwable e){} */ //如果跟上面的方法同时存在,异常都能匹配上时,优先执行上面的逻辑。 public void afterThrowing(Method method, Object args, Object target,              Throwable subclass) {       String className = target.getClass().getName();     String methodName = method.getName();          System.out.println("异常通知:"+className+"的方法"+methodName+"出现异常,异常名称:"+subclass.getClass().getSimpleName());    }  }

配置文件applicationContext.xml:

最后我们需要修改配置文件,将我们的业务层生成一个代理对象,然后将Advice注入到代理对象里面。

<?xml version="1.0" encoding="UTF-8"?><beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:p="http://www.springframework.org/schema/p"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"><bean id="testLogic" class="com.xy.business.logic.logicImpl.TestLogicImpl"/>   <!-- 定义前置通知,此处id的值可以更改,只要和注入的值对应上就行-->           <bean id="beforeLogAdvice" class="com.xy.aop.BeforeMethod"></bean>           <!-- 定义后置通知 -->           <bean id="afterLogAdvice" class="com.xy.aop.AfterMethod"></bean>           <!-- 定义异常通知 -->           <bean id="throwsLogAdvice" class="com.xy.aop.ThrowMthod"></bean>           <!-- 定义环绕通知 -->           <bean id="logAroundAdvice" class="com.xy.aop.AroundMethod"></bean>             <!-- 定义代理类,让代理类代理testLogic,我们访问代理类来执行业务中的方法 -->          <bean id="proxyTest" class="org.springframework.aop.framework.ProxyFactoryBean">     <!-- 代理的接口(jdk动态代理需要代理接口,cglib不用接口) -->      <property name="proxyInterfaces">     <value>com.xy.busniess.logic.logicInterface.TestLogicInterface</value>      </property>     <property name="interceptorNames">    <list>    <value>beforeLogAdvice</value>    <value>afterLogAdvice</value>    <value>throwsLogAdvice</value>    <value>logAroundAdvice</value>    </list>     </property>     <property name="target" ref="testLogic"></property>    </bean> </beans>


此处可以看到,当生成一个代理对象的时候需要三个参数,一个是proxyInterfaces,值是我们需要代理的业务接口。第二个参数是interceptorNames,值是我们实现了不同Advice接口的Advice类。第三个参数target就是我们的业务的具体实现了。


测试类:

package com.xy.web;import org.springframework.context.support.ClassPathXmlApplicationContext;import com.xy.business.logic.logicInterface.TestLogicInterface;public class mainTest {public static void main(String[] args) {//从applicationContext中拿到代理类TestLogicInterface testLogicInterface = (TestLogicInterface)new ClassPathXmlApplicationContext("applicationContext.xml").getBean("proxyTest");//代理类中实现了被代理类的所有方法,同被代理类一样被使用testLogicInterface.add("许阳");System.out.println("---------------------华丽的分割线---------------------");//执行exist方法,故意让方法报错testLogicInterface.exist("我不是许阳");}}

打印结果:

我是执行在com.xy.business.logic.logicImpl.TestLogicImpl的add方法(---->前<----)的逻辑!
环绕通知准备记录时间
用户许阳添加成功
环绕通知: 执行add方法花费时间0
我是执行在com.xy.business.logic.logicImpl.TestLogicImpl的add方法(---->后<----)的逻辑
---------------------华丽的分割线---------------------
我是执行在com.xy.business.logic.logicImpl.TestLogicImpl的exist方法(---->前<----)的逻辑!
环绕通知准备记录时间
Exception in thread "main" 异常通知:com.xy.business.logic.logicImpl.TestLogicImpl的方法exist出现异常,异常名称:RuntimeException
java.lang.RuntimeException: 不是许阳不给你运行!
    at com.xy.business.logic.logicImpl.TestLogicImpl.exist(TestLogicImpl.java:22)


通过打印结果可以知道,如果在一个代理类中同时加入四种Advice,最先执行的是beforeLogAdvice,接着执行logAroundAdvice,然后执行afterLogAdvice,当出现异常后,后续所有的方法以及Advice都将被强制中断。




0 0