1.AOP中的概念
Aspect(切面):指横切性关注点的抽象即为切面,它与类相似,只是两者的关注点不一样,类是对物体特征的抽象,而切面横切性关注点的抽象.
joinpoint(连接点):所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点,实际上joinpoint还可以是field或类构造器)
Pointcut(切入点):所谓切入点是指我们要对那些joinpoint进行拦截的定义.
Advice(通知):所谓通知是指拦截到joinpoint之后所要做的事情就是通知.通知分为前置通知,后置通知,异常通知,最终通知,环绕通知
Target(目标对象):代理的目标对象
Weave(织入):指将aspects应用到target对象并导致proxy对象创建的过程称为织入.
Introduction(引入):在不修改类代码的前提下, Introduction可以在运行期为类动态地添加一些方法或Field.
代理方式:
静态代理
动态代理
2.使用JDK的proxy技术实现AOP功能
JDK 动态代理 (采用的是JDK 反射技术)
public class JDKProxy implements InvocationHandler { private Object targetObject;//代理的目标对象 public Object createProxyInstance(Object targetObject){ this.targetObject = targetObject; /* * 第一个参数设置代码使用的类装载器,一般采用跟目标类相同的类装载器 * 第二个参数设置代理类实现的接口 * 第三个参数设置回调对象,当代理对象的方法被调用时,会委派给该参数指定对象的invoke方法 */ return Proxy.newProxyInstance(this.targetObject.getClass().getClassLoader(), this.targetObject.getClass().getInterfaces(), this); } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return method.invoke(this.targetObject, args);//把方法调用委派给目标对象 } }
当目标类实现了接口,我们可以使用jdk的Proxy来生成代理对象。
3.使用CGLIB实现AOP功能
public class CGLIBProxy implements MethodInterceptor { private Object targetObject;//代理的目标对象 public Object createProxyInstance(Object targetObject){ this.targetObject = targetObject; Enhancer enhancer = new Enhancer();//该类用于生成代理对象 enhancer.setSuperclass(this.targetObject.getClass());//设置父类 enhancer.setCallback(this);//设置回调用对象为本身 return enhancer.create(); } public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { return methodProxy.invoke(this.targetObject, args); } } CGLIB可以生成目标类的子类,并重写父类非final修饰符的方法。
注:使用CGLIB之前要导入相应的jar包,在下载的spring文件包的cglib目录下
测试:
新建一个接口:
package cn.itcast.service; public interface PersonService { public void save(String name); public void update(String name, Integer personid); public String getPersonName(Integer personid); }
新建一个接口的 实现类:
代码的目的是,在下面这个业务逻辑类中的方法执行之前都要被容器拦截,拦截了之后进行权限的判断,如果有权限才可以执行类中的方法
本例中的有权限是指 user!=null
package cn.itcast.service.impl; import cn.itcast.service.PersonService; public class PersonServiceBean implements PersonService{ private String user = null; public String getUser() { return user; } public PersonServiceBean(){} public PersonServiceBean(String user){ this.user = user; } public String getPersonName(Integer personid) { System.out.println("我是getPersonName()方法"); return "xxx"; } public void save(String name) { System.out.println("我是save()方法"); } public void update(String name, Integer personid) { System.out.println("我是update()方法"); } }
使用JDK完成动态代理:
package cn.itcast.aop; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import cn.itcast.service.impl.PersonServiceBean; public class JDKProxyFactory implements InvocationHandler{ private Object targetObject; public Object createProxyIntance(Object targetObject){ this.targetObject = targetObject; return Proxy.newProxyInstance(this.targetObject.getClass().getClassLoader(), this.targetObject.getClass().getInterfaces(), this); } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//环绕通知 PersonServiceBean bean = (PersonServiceBean) this.targetObject; Object result = null; if(bean.getUser()!=null){ //..... advice()-->前置通知 try { result = method.invoke(targetObject, args); // afteradvice() -->后置通知 } catch (RuntimeException e) { //exceptionadvice()--> 例外通知 }finally{ //finallyadvice(); -->最终通知 } } return result; } }
使用CGLIB实现AOP功能:
package cn.itcast.aop; import java.lang.reflect.Method; import cn.itcast.service.impl.PersonServiceBean; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; public class CGlibProxyFactory implements MethodInterceptor{ private Object targetObject; public Object createProxyIntance(Object targetObject){ this.targetObject = targetObject; Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(this.targetObject.getClass());//非final enhancer.setCallback(this); return enhancer.create(); } public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { PersonServiceBean bean = (PersonServiceBean) this.targetObject; Object result = null; if(bean.getUser()!=null){ result = methodProxy.invoke(targetObject, args); } return result; } }
新建一个单元测试:
package junit.test; import org.junit.BeforeClass; import org.junit.Test; import cn.itcast.aop.CGlibProxyFactory; import cn.itcast.aop.JDKProxyFactory; import cn.itcast.service.PersonService; import cn.itcast.service.impl.PersonServiceBean; public class AOPTest { @BeforeClass public static void setUpBeforeClass() throws Exception { } @Test public void proxyTest(){ JDKProxyFactory factory = new JDKProxyFactory(); PersonService service = (PersonService) factory.createProxyIntance(new PersonServiceBean("xxx")); service.save("888"); } @Test public void proxyTest2(){ CGlibProxyFactory factory = new CGlibProxyFactory(); PersonServiceBean service = (PersonServiceBean) factory.createProxyIntance(new PersonServiceBean("xxx")); service.save("999"); } }
两个测试结果都是:
我是save()方法
4.使用Spring框架实现AOP功能
要进行AOP编程,首先我们要在spring的配置文件中引入aop命名空间: <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"> </beans>
注意上面的 aop 命名空间 和 最后一行的两个schema文件路径
Spring提供了两种切面声明方式,实际工作中我们可以选用其中一种:
(1)基于注解方式声明切面。
(2)基于XML配置方式声明切面。
<1> 基于注解方式声明切面。
首先启动对@AspectJ注解的支持(下划线部分):
<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"> <aop:aspectj-autoproxy/> <bean id="orderservice" class="cn.itcast.service.OrderServiceBean"/> <bean id="log" class="cn.itcast.service.LogPrint"/> </beans>
启用@AspectJ支持后,在application context中定义的任意带有一个@Aspect切面(拥有@Aspect
注解)的bean都将被
Spring自动识别并用于配置Spring AOP。
注解方式的示例:
@Aspect public class LogPrint { @Pointcut("execution(* cn.itcast.service..*.*(..))") private void anyMethod() {}//声明一个切入点 @Before("anyMethod() && args(userName)")//定义前置通知 public void doAccessCheck(String userName) { } @AfterReturning(pointcut="anyMethod()",returning="revalue")//定义后置通知 public void doReturnCheck(String revalue) { } @AfterThrowing(pointcut="anyMethod()", throwing="ex")//定义例外通知 public void doExceptionAction(Exception ex) { } @After("anyMethod()")//定义最终通知 public void doReleaseAction() { } @Around("anyMethod()")//环绕通知 public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable { return pjp.proceed(); } }
<2>基于XML配置方式声明切面。
各种不同类型的通知
public class LogPrint { public void doAccessCheck() {}定义前置通知 public void doReturnCheck() {}定义后置通知 public void doExceptionAction() {}定义例外通知 public void doReleaseAction() {}定义最终通知 public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable { return pjp.proceed();环绕通知 } }
XML配置:
<bean id="orderservice" class="cn.itcast.service.OrderServiceBean"/> <bean id="log" class="cn.itcast.service.LogPrint"/> <aop:config> <aop:aspect id="myaop" ref="log"> <aop:pointcut id="mycut" expression="execution(* cn.itcast.service..*.*(..))"/> <aop:before pointcut-ref="mycut" method="doAccessCheck"/> <aop:after-returning pointcut-ref="mycut" method="doReturnCheck "/> <aop:after-throwing pointcut-ref="mycut" method="doExceptionAction"/> <aop:after pointcut-ref="mycut" method=“doReleaseAction"/> <aop:around pointcut-ref="mycut" method="doBasicProfiling"/> </aop:aspect> </aop:config>
测试:
重新建立一个接口:
package com.yinger.service; public interface PersonService2 { public void save(); public void exTest() throws Exception; public String test(String name); }
新建一个类,实现上面的接口:
package com.yinger.service.impl; import com.yinger.service.PersonService2; public class PersonServiceBean4 implements PersonService2{ //默认的构造器 public PersonServiceBean4(){ System.out.println("instance me"); } //exTest 方法 public void exTest() throws Exception { throw new Exception("发生了异常"); } //test 测试方法 public String test(String name){ System.out.println("test"); return name; } //save 方法 public void save(){ System.out.println("save"); } }
新建一个拦截器:
package com.yinger.service.intercepter; 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; @Aspect public class PersonServiceIntercepter { @Pointcut("execution(* com.yinger.service.impl.PersonServiceBean4.*(..))") private void anyMethod(){} @Before("anyMethod()") public void doAccessCheck(){ System.out.println("前置通知"); } @AfterReturning(pointcut="anyMethod()",returning="reValue") public void doReturnCheck(String reValue){ System.out.println("后置通知:"+reValue); } @AfterThrowing(pointcut="anyMethod()",throwing="ex") public void doExceptionAction(Exception ex) { System.out.println("例外通知:"+ex); } @After("anyMethod()") public void doReleaseAction(){ System.out.println("最终通知"); } @Around("anyMethod()") public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable { System.out.println("环绕通知"); return pjp.proceed(); } }
beans.xml:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" 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/context http://www.springframework.org/schema/context/spring-context-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd"> <aop:aspectj-autoproxy/> <!-- 注意记得要将自定义的拦截器交给Spring容器管理 --> <bean id="myIntercepter" class="com.yinger.service.intercepter.PersonServiceIntercepter"></bean> <bean id="personService4" class="com.yinger.service.impl.PersonServiceBean4"></bean> </beans>
添加测试方法:
@Test //用于测试AOP功能的方法 public void testAOP() throws Exception { AbstractApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml"); System.out.println("--------"); PersonService2 ps2 = (PersonService2)ctx.getBean("personService4"); ps2.test("name"); // ps2.exTest(); // ps2.save(); ctx.close(); }
首先测试 test 方法,结果:
log4j:WARN No appenders could be found for logger (org.springframework.context.support.ClassPathXmlApplicationContext). log4j:WARN Please initialize the log4j system properly. instance me -------- 前置通知 环绕通知 test 后置通知:name 最终通知
其次测试 save 方法:
log4j:WARN No appenders could be found for logger (org.springframework.context.support.ClassPathXmlApplicationContext). log4j:WARN Please initialize the log4j system properly. instance me -------- 前置通知 环绕通知 save 后置通知:null 最终通知
最后测试 exTest 方法:
log4j:WARN No appenders could be found for logger (org.springframework.context.support.ClassPathXmlApplicationContext). log4j:WARN Please initialize the log4j system properly. instance me -------- 前置通知 环绕通知 例外通知:java.lang.Exception: 发生了异常 最终通知
从上面的例子中可以看出,使用通知可以得到方法返回的值,当发生了异常时可以知道异常的类型和信息
并且通过给通知方法限制方法参数的类型,可以限制为指定的方法参数的方法
如果是异常的话,就只有抛出的异常和例外通知中的异常一样时,异常通知方法才会执行
参考文档中的内容:
基于xml配置的例子略过。。。(和上面的示例代码差不多)
5.aspectj的切入点的语法细节分析
expression="execution(* cn.itcast.service..*.*(..))"
第一个*表示方法的返回值可以是任何值
接着 cn.itcast.service..* 表示 cn.itcast.service 包以及它的子包(两个点,如果是一个点就不包括子包)中的任何类 ,第二个*表示任何类
接着 .* 表示类中的任何方法,第三个*表示任何方法
最后的 (..)表示方法的参数可以是任意的,可以没有,也可以有一个或者多个