aop:Aspect Oriented Programming,aspect是切面的意思,连起来的意思很明显,就是面向切面编程的意思,大家可能对里面的概念不是太清楚,我大致画了个图,希望能帮各位理清一下这里面的概念。
核心的概念就是上面这几个了。其中通知有五种,除了上面列出来的前置通知、后置通知、异常通知、最终通知之外,另外还有一个环绕通知,环绕通知的功能是最强大的,继承了上边四种通知的功能。这些概念各自有自己的英文名称,为了之后的教程方便,也为了大家不迷糊,我们说清楚它的表示。
连接点:joinpoint
切入点:pointcut
切面:aspect
通知:advice
前置通知:before
后置通知:after
异常通知:afterThrowing
最终通知:afterReturning
织入:weave
aop实现的原理就是动态生成代理类,通过代理类改变了方法的内部结构,在方法调用前加入通知。spring里面有三种方式来实现aop切面编程。
1.spring实现aop之经典方式,通过MethodBeforeAdvice,AfterReturningAdvice,ThrowsAdvice,MethodInterceptor等接口来实现aop编程。
2.spring+aspectj+xml
3.spring+aspectj+注解
------------------------------------------------------------spring实现aop之经典方式------------------------------------------------------------
通过实现MethodBeforeAdvice,AfterReturningAdvice,ThrowsAdvice,MethodInterceptor等接口来实现aop编程,
我们一般称之为"spring实现aop之经典方式"。我主要拿MethodBeforeAdvice来举例子,其余的都是一样的方式。
一、定义一个类BeforeMethod实现实现MethodBeforeAdvice这个接口,这个类用来作为前置通知,将会在目标方法执行之前执行
package cn.zhao.test.aop;
import java.lang.reflect.Method;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.stereotype.Component;
@Component
public class BeforeMethod implements MethodBeforeAdvice{
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("执行前置通知");
}
}
需要注意的是这个类需要被spring管理。
二、定义好了前置通知类之后,就可以配置它要拦截的方法了,在spring的配置文件中添加如下配置:
<bean id="proxyFactoryBean" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="interceptorNames">
<list>
<!-- VALUE不会监测有效性,建议使用idref -->
<idref bean="beforeMethod"/>
<!-- <value>beforeMethod</value> -->
</list>
</property>
<property name="target" ref="aopServiceImpl"/>
</bean>
ProxyFactoryBean用来产生target属性指向的bean的代理类,产生代理类的时候会使用interceptorNames指定的bean来加强你的目标类,从而构成代理类。我们来看下目标类以及其接口
三、目标类及目标接口
package cn.zhao.test.service;
public interface AopService {
void f1();
String f2(String string);
}
实现了AopService接口的类:
package cn.zhao.test.service.impl;
import org.springframework.stereotype.Component;
import cn.zhao.test.service.AopService;
@Component
public class AopServiceImpl implements AopService {
@Override
public void f1() {
System.out.println("AopServiceImpl实现的f1()方法");
}
@Override
public String f2(String string) {
return "AopServiceImpl实现的f2()方法,参数:"+string;
}
}
通过上面这三步就可以了,我们来测试一下,单元测试方法如下:
public class AopTest {
ApplicationContext ac=null;
@Before
public void setUp(){
ac=new ClassPathXmlApplicationContext("spring-mvc-jpa.xml");
}
@Test
public void testAop(){
//AopService bean = ac.getBean("aopServiceImpl",AopService.class);
//要使用代理类 proxyFactoryBean
AopService bean = ac.getBean("proxyFactoryBean",AopService.class);
bean.f1();
String f2 = bean.f2("zgao");
System.out.println(f2);
}
}
注意,这里要获取代理类proxyFactoryBean,如果直接获取未被代理过的aopServiceImpl对象,那么还是达不到预期的效果啊
执行结果如下所示:
好了,大功告成了。
总结:这种实现aop的方式不需要指定前置通知是哪个方法,因为你实现了MethodBeforeAdvice的同时,前置通知对应的方法就已经确定了,即void before(Method method, Object[] args, Object target)。
上面第二步的xml配置稍微看起来有点繁杂,其实还有相比之下更为简单的配置,我把第二步中的xml配置修改为如下内容:
<aop:config>
<aop:advisor advice-ref="beforeMethod" pointcut="execution(* cn.zhao.test.service..*Service.*(..))" />
</aop:config>
需要注意的是:aop:advisor:用于配置只有一个通知(就是java里面的方法)和切入点的切面,和aop:aspect是同一个级别的,只不过aop:aspect用于配置的切面,可以包含多个通知(即方法)与切入点,我后边的文章会说到aop:aspect的使用。
测试方法也要改一改,因为没有proxyFactoryBean这个bean了,我们直接获取原生bean就行:
@Test
public void testAop(){
AopService bean = ac.getBean("aopServiceImpl",AopService.class);
//要使用代理类
// AopService bean = ac.getBean("proxyFactoryBean",AopService.class);
bean.f1();
String f2 = bean.f2("zgao");
System.out.println(f2);
}
此时,再执行结果是一样的:
其他几个接口配置都是类似的,我就不多说了,这里只列出实现接口的部分示例代码:
class AspectDuiYingSpring2 implements AfterReturningAdvice{
@Override
public void afterReturning(Object returnValue, Method method,
Object[] args, Object target) throws Throwable {
// TODO Auto-generated method stub
}
}
class AspectDuiYingSpring3 implements ThrowsAdvice{
}
class AspectDuiYingSpring5 implements AfterAdvice{
}
class AspectDuiYingSpring4 implements org.aopalliance.intercept.MethodInterceptor{
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
String info = methodInvocation.getMethod().getDeclaringClass()+ "." +
methodInvocation.getMethod().getName() + "()";
System.out.println(info);
try{
Object result = methodInvocation.proceed();
return result;
}
finally{
System.out.println(info);
}
}
}
------------------------------------------------------------------spring+aspectj+xml方式实现aop--------------------------------------------------
前边我们说过了spring通过实现接口的方式来实现aop的效果,这篇教程呢,来看看spring+aspectj+xml这种方式如何实现aop编程。
一、定义一个类,不需要实现任何的接口,也不需要继承任何一个类
package cn.zhao.test.aop;
import org.springframework.stereotype.Component;
@Component
public class BeforeMethod2 {
public void beforeProcess() throws Throwable {
System.out.println("beforeProcess");
}
}
定义好这个类之后,我们来看看怎么一步步把它变成前置通知,也就是说怎么让它在目标方法执行之前执行
二、同上一篇文章介绍的配置方式类似,第二步开始配置xml文件:
<aop:config>
<aop:aspect ref="beforeMethod2" >
<aop:before method="beforeProcess" pointcut="execution(* cn.zhao.test.service.AopService.*(..))"/>
</aop:aspect>
</aop:config>
还记得上次我说过的"aop:advisor:用于配置只有一个通知(就是java里面的方法)和切入点的切面,和aop:aspect是同一个级别的,只不过aop:aspect用于配置的切面,可以包含多个通知(即方法)与切入点,我后边的文章会说到aop:aspect的使用。"吗?这里就用到的是aop:aspect。
三、目标类及目标接口
package cn.zhao.test.service;
public interface AopService {
void f1();
String f2(String string);
}
实现了AopService接口的类:
package cn.zhao.test.service.impl;
import org.springframework.stereotype.Component;
import cn.zhao.test.service.AopService;
@Component
public class AopServiceImpl implements AopService {
@Override
public void f1() {
System.out.println("AopServiceImpl实现的f1()方法");
}
@Override
public String f2(String string) {
return "AopServiceImpl实现的f2()方法,参数:"+string;
}
}
四、编写测试类,直接得到aopServiceImpl的bean对象调用就行了,这里也不存在什么获取代理对象:
@Test
public void testAop2(){
AopService bean = ac.getBean("aopServiceImpl",AopService.class);
bean.f1();
String f2 = bean.f2("zgao");
System.out.println(f2);
}
结果:
那你说了,在第二步我可以使用aop:advisor吗,我们说了,aop:advisor只使用与只有一个方法且实现了org.aopalliance.aop.Advice接口的类,你想啊,这次我们定义的是一个普通的类BeforeMethod2,它里面可能有不止一个方法,如果你使用aop:advisor,没有属性让你指定方法,spring怎么知道要使用哪个方法作为前置通知又或者是其他通知呢?因此在这里我们要使用aop:aspect才可以,否则将会报出如下错误:
Caused by: org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'beforeMethod2' must be of type [org.aopalliance.aop.Advice], but was actually of type [cn.zhao.test.aop.BeforeMethod2]
------------------------------------------------------spring+aspectj+xml方式实现aop详细举例----------------------------------------------------
下面我举一个详细的例子,看看每一种通知使用spring+aspect+xml这种方式该如何配置才能实现aop。
一、定义一个普通的类,里面的方法等会将会通过配置使之成为通知:
package cn.zhao.test.spring.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
public class HelloAspect {
public void beforeHello(){
System.out.println("beforeHello");
}
/**
* @param name:截获匹配方法的参数,属于自动获取
*/
public void beforeHello2(String name){
System.out.println("beforeHello:"+name);
}
/**
* 手动获取被通知方法(目标方法)的信息
* @param name
*/
public void beforeHello3(JoinPoint jp){
System.out.println("连接点所在位置信息:"+jp.toString());
System.out.println("连接点所在位置详细信息:"+jp.toLongString());
System.out.println("连接点所在位置简短信息:"+jp.toShortString());
System.out.println("目标对象代理:"+jp.getThis());
System.out.println("目标对象:"+jp.getTarget());
System.out.println("目标方法参数:"+jp.getArgs());
System.out.println("目标方法:"+jp.getSignature());
System.out.println("连接点类型:"+jp.getKind());//method-execution目前spring也只支持这种
}
/**
* @param retVal这里设置为object因为当这里设置的类型和目标方法的返回值类型不一样时就不执行这个通知了,这里设置
* 为objct就一定执行
*/
// public void afterReturningAdvice(String retVal){
public void afterReturningAdvice(Object retVal){
System.out.println("afterReturningAdvice:"+retVal);
}
public void afterThrowingAdvice(Exception e1){
System.out.println("异常信息:"+e1.getMessage());
}
public void afterHello(){
System.out.println("afterHello");
}
public Object aroundAdvice(ProceedingJoinPoint pjp){
System.out.println("aroundAdvice开始拦截住了方法");
if (pjp.getArgs().length==1) {//只替换sayHello(String name)
try {//穿的参数可以覆盖原来的参数
Object ret = pjp.proceed(new Object[]{"haha"});
System.out.println("aroundAdvice拦截住了方法---结束");
return ret;
} catch (Throwable e) {
e.printStackTrace();
}
}else {
try {
pjp.proceed();
} catch (Throwable e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return null;
}
}
二、详细的配置方式:
<bean id="helloAspect" class="cn.zhao.test.spring.aop.HelloAspect"></bean>
<!-- 所有切面配置必须在aop:config中。切面配置:切面是通知和切入点(连接点(即方法)的群集)的组合。
注意aop:config是有序的 -->
<aop:config ><!-- proxy-target-class="true"指定使用cglib代理xml风格 -->
<aop:pointcut expression="execution(* cn.zhao.test..*Service.sayHello(..))" id="hellopc"/>
<!-- aop:aspect用于配置切面,可包含多个通知(即方法)与切入点。
aop:advisor:用于配置只有一个通知和切入点的切面。两个标签同级-->
<aop:aspect ref="helloAspect" order="1">
<aop:before method="beforeHello" pointcut-ref="hellopc" />
<!-- method="beforeHello2(java.lang.String)"也可写成method="beforeHello2"之所以加参数,可区别重载
arg-names:指定通知的参数名. args(name)匹配目标方法只有一个参数且其类型为通知实现方法中同名的参数的类型 -->
<aop:before method="beforeHello2(java.lang.String)"
pointcut="execution(* cn.zhao.test..*Service.sayHello(..)) and args(name)" arg-names="name" />
<aop:before method="beforeHello3" pointcut-ref="hellopc" />
<!-- returning="retVal"指定目标方法的返回值给通知的哪个参数 -->
<aop:after-returning pointcut-ref="hellopc" method="afterReturningAdvice" arg-names="retVal"
returning="retVal"/>
<!-- throwing定义一个名字,该名字用于匹配通知实现方法的一个参数名,当目标方法抛出异常返回后,
将把目标方法抛出的异常传给通知方法 -->
<aop:after-throwing pointcut="execution(* cn.zhao.test..*Service.math(..))"
method="afterThrowingAdvice" arg-names="e1" throwing="e1"/>
<aop:after method="afterHello" pointcut="execution(* cn.zhao.test..*Service.sayHello(..))" />
<!-- 囊括前4种通知的功能,环绕通知第一个参数必须是org.aspectj.lang.ProceedingJoinPoint类型 ,其他四种第一个
参数可以是JoinPoint或JoinPoint.StaticPart,也可以不带-->
<aop:around pointcut-ref="hellopc" method="aroundAdvice"/>
</aop:aspect>
</aop:config>
三、实现类如下:
package cn.zhao.test.spring.aop.service.impl;
import cn.zhao.test.spring.aop.service.HelloService;
public class HelloServiceImpl implements HelloService{
@Override
public boolean sayHello(String name) {
System.out.println("你好:"+name);
return true;
}
@Override
public void math() {
try {
System.out.println(1/0);
} catch (Exception e1) {
throw e1;
}
}
@Override
public void m1() {
System.out.println("m1");
}
@Override
public String sayAdress(String address) {
System.out.println("sayAdress:"+address);
return address+"@163.com";
}
}
四、编写测试类:
/**
* 测试<aop:aspect>切面下的非异常通知和order
*/
@Test
public void testAop1(){
URL resource = TestSpring.class.getClassLoader().getResource("cn/zhao/test/spring/test-spring.xml");
ApplicationContext ac=new FileSystemXmlApplicationContext(resource.getPath());
HelloService helloService = ac.getBean("helloService", HelloService.class);
helloService.sayHello("http://www.roadjava.com");
}
运行上边这个测试类结果如下:
五、再编写个类测试一下异常通知:
/**
* 测试<aop:aspect>切面下的异常通知
*/
@Test
public void testAop2(){
URL resource = TestSpring.class.getClassLoader().getResource("cn/zhao/test/spring/test-spring.xml");
ApplicationContext ac=new FileSystemXmlApplicationContext(resource.getPath());
HelloService helloService = ac.getBean("helloService", HelloService.class);
helloService.math();
}
结果:
打印的结果正好是我们在异常通知里面写的代码,说明异常通知也是正常工作的,好,这个案例就说这么多,多注意看下注释。
----------------------------------------------spring+aspectj+注解方式实现aop-----------------------------------------------------
一、定义一个类,这个类不再是前边介绍的只是一个简简单单的pojo,而是使用了aspectj注解注释的类
package cn.zhao.test.spring.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
/**
* 同xml方式一样(都要配置被spring容器管理的,千万不能少了哦bean 或者component注解)。
*除了环绕通知,其他通知的参数都是可有可无的 .
*aspectJ切入点语法(有很多spring还不支持,下面只列出spring支持的):
* .. :(两个点)匹配任何数量字符的重复,如在类型模式中匹配任何数量子包;而在方法参数模式中匹配任何数量参数。
* "*":匹配任何数量字符
* && || !在xml(集成spring)中用and or !代替
* bean():spring扩展,aspectj没有
* args:手动获取参数,返回至,异常
*/
@Aspect //@Order(2) //相当于<aop:aspect>
public class AopByAnnotation {
@Pointcut(value="execution(* cn.zhao.test..*Service.sayAdress(..))")
public void pc1(){}
//同xml一样,切入点表达式只描述目标方法,argNames描述的是通知的参数。要求切入点处描述
//的参数(即目标方法的参数)的类型和argNames描述的参数名字一样且类型相同
@Before(value="execution(* cn.zhao.test..*Service.sayAdress(..)) and args(address)",argNames="address")
public void beforeAdvice(String address){//通知方法的参数为address//JoinPoint
System.out.println("beforeAdvice:"+address);
}
/*
* 同xml一样,要求argNames指定的通知参数和returning指定的全等,得到调用目标方法的返回值
*/
@AfterReturning(value="execution(* cn.zhao.test..*Service.sayAdress(..))",argNames="ret",returning="ret")
public void afterReturningAdvice(Object address){
System.out.println("afterReturningAdvice:"+address);
}
/*
* 同xml一样,要求argNames指定的通知参数和throwing指定的全等(异常包含)
*/
@AfterThrowing(value="execution(* cn.zhao.test..*Service.sayAdress(..))",argNames="ex",throwing="ex")
public void afterThrowingAdvice(Exception ex){
System.out.println("afterThrowingAdvice:"+ex.getMessage());
}
@After(value="execution(* cn.zhao.test..*Service.sayAdress(..)) and args(address)",argNames="address")
public void after(String address){
System.out.println("after通知:"+address);
}
@Around(value="pc1()")
public Object aroundAdvice(ProceedingJoinPoint jp) throws java.lang.Throwable {
System.out.println("aroundAdvice开始执行");
Object rvt = jp.proceed();
System.out.println("aroundAdvice执行结束");
return rvt ;
}
}
二、spring配置,因为直接使用aspectj的注解,所以这次的xml配置也相对来说简单多了,当然,你也可以把bean的配置使用注解来代替,我这里只是以前写的,又不是啥错误,也赖得改成注解了。
<!-- 启动对@AspectJ注解的支持 -->
<aop:aspectj-autoproxy /><!-- proxy-target-class="true" 指定使用cglib代理aspect风格 -->
<bean id="aopByAnnotation" class="cn.zhao.test.spring.aop.AopByAnnotation"></bean>
aop:aspectj-autoproxy个是必须要加上的,不然你在pojo类上加上了aspectj相关的注解也是没效果的。
三、要被拦截的方法所在的类如下:
package cn.zhao.test.spring.aop.service.impl;
import cn.zhao.test.spring.aop.service.HelloService;
public class HelloServiceImpl implements HelloService{
@Override
public boolean sayHello(String name) {
System.out.println("你好:"+name);
return true;
}
@Override
public void math() {
try {
System.out.println(1/0);
} catch (Exception e1) {
throw e1;
}
}
@Override
public void m1() {
System.out.println("m1");
}
@Override
public String sayAdress(String address) {
System.out.println("sayAdress:"+address);
return address+"@163.com";
}
}
四、测试类:
/**
* 测试注解风格的
*/
@Test
public void testAop4(){
URL resource = TestSpring.class.getClassLoader().getResource("cn/zhao/test/spring/test-spring.xml");
ApplicationContext ac=new FileSystemXmlApplicationContext(resource.getPath());
HelloService helloService = ac.getBean("helloService", HelloService.class);
helloService.sayAdress("http://www.roadjava.com");
}
执行结果:
好的,注解方式实现aop我们也说完了。