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

Spring AOP中的前置通知和后置通知详解

2017-05-19 18:20 549 查看
不同版本的spring对AOP的支持有所不同,spring2.0之前,它主要针对不同类型的拦截器使用XML配置文件通过代理来实现。而spring2.0之后,它可以使用JDK5的注解来完成AOP的实现,只是几个简单标签就可以完成,使得开发更加简单,便捷。所以推荐使用后一种方法。但是很多旧的项目中使用了前一种实现方法,所以我们也应该对第一种方法有所掌握。

首先通过代码介绍spring2.0之前如何实现前后置通知

定义一个User接口:

package org.spring.advice;

publicinterface User {
publicvoid normalUser();
publicvoid vip();
publicvoid vvip();

}

写一个类去实现这个接口:

package org.spring.advice;

publicclass UserImpl implements User{

publicvoid normalUser() {
System.out.println("普通用户访问资源");
}

publicvoid vip() {
System.out.println("VIP用户访问资源");
}

publicvoid vvip() {
System.out.println("超级VIP用户访问资源");
}

}

这里模拟网站的三种资源针对不同的用户,不同角色用户在访问之前就要进行验证,这个验证用前置通知模拟,用户访问后反馈给后台提示信息,这个访问后提示用后置通知模拟

下面实现前置通知类,执行业务方法之前所要进行的操作都可以放在前置方法中进行处理

package org.spring.advice;

import java.lang.reflect.Method;
import org.springframework.aop.MethodBeforeAdvice;
publicclass AdviceBeforeOfUser implements MethodBeforeAdvice{

publicvoid before(Method method, Object[] obj1, Object obj2)throws Throwable {
System.out.println("-----------------------");
System.out.println("验证用户是否有访问此资源的权限...");
}
}

前置通知类实现MethodBeforeAdvice接口,覆盖其before()方法。

接着实现后置通知类,业务方法执行完之后想要返回什么信息,如日志等,可以放在后置方法中

package org.spring.advice;

import java.lang.reflect.Method;

import org.springframework.aop.AfterReturningAdvice;
publicclass AdviceAfterOfUser implements AfterReturningAdvice{

publicvoid afterReturning(Object arg0, Method arg1, Object[] arg2,
Object arg3) throws Throwable {
System.out.println("通知信息:该用户成功访问");
}
}


接下来,最重要的就是配置文件,所有的连接操作都通过配置文件来完成。

<?xmlversion="1.0"encoding="UTF-8"?>
<beans
xmlns="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">
<!-- 注册前置通知类 -->
<beanid="beforeAdvice"class="org.spring.advice.AdviceBeforeOfUser"/>
<!-- 注册后置通知类 -->
<beanid="afterAdvice"class="org.spring.advice.AdviceAfterOfUser"/>
<beanid="proxy"class="org.springframework.aop.framework.ProxyFactoryBean">
<propertyname="proxyInterfaces"value="org.spring.advice.User"/>
<propertyname="target"ref="userImpl"></property>
<propertyname="interceptorNames">
<list>
<value>beforeAdvice</value>
<value>afterAdvice</value>
</list>
</property>
</bean>
<beanid="userImpl"class="org.spring.advice.UserImpl"></bean>
</beans>


解释一下下面这段配置文件中的信息:
<bean id="proxy"class="org.springframework.aop.framework.ProxyFactoryBean">

      <propertyname="proxyInterfaces"value="org.spring.advice.User"/>
      <propertyname="target"ref="userImpl"></property>
      <propertyname="interceptorNames">
         <list>
           
<value>beforeAdvice</value>
           
<value>afterAdvice</value>
         </list>
      </property>
   </bean>
这是一个代理Bean,这个代理Bean指定了应用的接口(<propertyname="proxyInterfaces" value="org.spring.advice.User" />)

指明了操作的目标对象(<propertyname="target" ref="userImpl"></property>)

声明了拦截器名称(<propertyname="interceptorNames">)

interceptorNames这个拦截器实现了对这个方法的拦截拦截后就可以实现在这个方法前后加自定义的切面模块功能。拦截器在AOP框架中起着核心功能

最后编写测试类:

package org.spring.advice;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

publicclass Test {
publicstatic void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = (User)ac.getBean("proxy");
user.normalUser();
user.vip();
user.vvip();
}
}


运行结果:



我们可以看到,已经成功实现了,使用spring的AOP代理,我们不需要为业务类每个都去写验证方法,而是统一实现,如果不想要后置通知或不想要前置通知,直接在配置文件中去掉value值即可,不需要更改代码,非常方便。

而spring2.0之后,提供Annotation设置AOP的通知,更加简化了AOP的实现,还是用之前的例子,来对比看看spring2.0之后AOP的通知有多简便。

 

接口和实现类不变,前置通知类的实现:

package org.spring.advice2;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
publicclass AdviceBeforeOfUser{
@Before("execution(* org.spring.advice2.UserImpl.*(..))")
publicvoid before(){
System.out.println("-----------------------");
System.out.println("验证用户是否有访问此资源的权限...");
}
}

后置通知类的实现: 

package org.spring.advice2;

import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;

@Aspect
publicclass AdviceAfterOfUser{
@AfterReturning("execution(* org.spring.advice2.UserImpl.*(..))")
publicvoid afterReturning(){
System.out.println("通知信息:该用户成功访问");
}
}

@Aspect标签声明该类是一个Aspect(切面)类

@Before,@AfterReturning分别声明该方法是一个前置通知和后置通知

"execution(* org.spring.advice2.UserImpl.*(..))" 这是一个正则表达式,这里表明对UserImpl类中的所有方法进行前置后置通知,也可以具体到某个方法进行通知,修改该表达式即可。

这里需要注意:在匹配正则表达式时,“*”和后面的接口之间要有一个空格,否则会报错:Pointcut is notwell-formed

配置文件:

<?xmlversion="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"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

<aop:aspectj-autoproxy/>
<!-- 注册前置通知类 -->
<beanid="beforeAdvice"class="org.spring.advice2.AdviceBeforeOfUser"/>
<!-- 注册后置通知类 -->
<beanid="afterAdvice"class="org.spring.advice2.AdviceAfterOfUser"/>

<beanid="userImpl"class="org.spring.advice2.UserImpl"></bean>
</beans>
我们可以看到,配置文件大大简化了,仅仅加入一句<aop:aspectj-autoproxy/>   表示自动进行代理,spring替我们管理一切事务。 

测试方法基本不变,只是没人写代理类,直接获取接口实现类的id即可,

User user = (User)ac.getBean("userImpl");

运行结果不变,很快捷的就实现了前置和后置通知。

注意:这里配置文件头和前面有所区别,要引进

xmlns:aop=http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop     http://www.springframework.org/schema/aop/spring-aop.xsd"
 

前置通知,后置通知的实现是spring封装了Java的代理类,那么他的原理是什么呢?我们可以抛开spring,用Java来模拟他的实现流程。

首先,接口和实现类依然不变,然后自己定义一个代理类

package org.spring.proxyaop;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

publicclass UserProxy {
//指定要被代理的类
private Usertarget;

public UserProxy(User ui) {
this.target = ui;
}
//返回UserImpl值类型的方法
public User getUserProxy(){
User proxy = null;
//指定负责加载的类加载器
ClassLoader loader = target.getClass().getClassLoader();

//加载要代理接口中的所有方法
Class[] interfaces = new Class[]{User.class};

InvocationHandler ih = new InvocationHandler() {

/*
proxy---代表了正在返回的代理对象
method----代表正在执行的方法
obj-----代表方法执行时传入的参数
*/
public Object invoke(Object proxy, Method method, Object[] obj)
throws Throwable {

System.out.println("-----------------------");
System.out.println("验证用户是否有访问此资源的权限...");
Object result = method.invoke(target, null);
System.out.println("通知信息:该用户成功访问");
/*
* method.invoke(obj,args)
*obj代表了被代理的类
*args代表了为代理类中的方法传入参数,我们这里的方法没有用到参数,所以传入null
* */
returnresult;
}
};

//Proxy 这是Java提供的代理类
proxy = (User) Proxy.newProxyInstance(loader, interfaces, ih);
return proxy;
}
}
这里有详细注解,不进行解释。最基本的就是重写了invoke()方法。

最后写测试方法

package org.spring.proxyaop;

publicclass Test {
publicstatic void main(String[] args) {
User user = new UserImpl();
User proxy = new UserProxy(user).getUserProxy();
proxy.normalUser();
proxy.vip();
proxy.vvip();
}
}

运行结果依然不变,spring无论对其怎么简化,核心的东西是不会变的。
 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息