第二章 Spring AOP
一.AOP?
AOP:面向切向,采用横向抽取机制,取代传统的纵向继承
AOP是一种面向切面的思想,但是我们平常说的spring使用了AOP,实际上说的是spring实现AOP思想的底层原理,而底层原理就是使用动态代理来增强某个方法。所以平常说AOP技术实际上就是指通过动态代理来对方法进行增强。
举个例子:如果你想加强一个类中写好的方法,一般来说我们用继承来写,但是随着子类过多,那么重写方法就变得麻烦了,
所以现在我们用AOP,将要增强的方法提取到一个类中,然后将需要增强的方法通过代理类将其方法增强
传统的纵向继承
二.动态代理的两个方式
1.JDK动态管理 (接口+实现类) 2.cglib代理(实现类)
下面是每个方式的实例
1.JDK动态管理(必须要有接口)
2.目标类:
3.切面类
4.代理类
package com.zlj; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * jdk代理类 * @author 邹龄晋 * */ public class JdkProxy implements InvocationHandler{ //目标接口 private UserDao userDao; //创建代理方法 public Object createProxy(UserDao userDao){ this.userDao = userDao; //1.类加载器 ClassLoader classLoader = JdkProxy.class.getClassLoader(); //2.被代理对象实现所有的接口 Class [] clazz = userDao.getClass().getInterfaces(); //3.使用代理类,进行增强,返回的是代理后的对象 return Proxy.newProxyInstance(classLoader, clazz, this); } /** * 所有动态代理类的方法调用都会在invoke方法中进行 * proxy是代理后的对象 * method将要被执行的方法信息(反射) * args执行方法时需要的参数 */ public Object invoke(Object proxy, Method method, Object[] args)throws Throwable { //声明切面 MyAspect myAspect = new MyAspect(); //前增强 myAspect.check_Permissions(); //在目标类上调用方法并且传入参数 Object obj = method.invoke(userDao, args); //后增强 myAspect.log(); return obj; } }
5.测试类
2.CGLIB代理
JDK代理使用前提示必须要有接口有一定的局限性,但是CGLIB是不需要的接口的
package com.zlj.cglib; import java.lang.reflect.Method; import org.springframework.cglib.proxy.Enhancer; import org.springframework.cglib.proxy.MethodInterceptor; import org.springframework.cglib.proxy.MethodProxy; public class CglibProxy implements MethodInterceptor{ //代理方法 public Object createProxy (Object target){ //创建一个动态类的对象 Enhancer enhancer = new Enhancer(); //确定需要增强的类.设置器父类 enhancer.setSuperclass(target.getClass()); //添加回调函数 enhancer.setCallback(this); return enhancer.create(); } /** * proxy cglib根据指定父类生成的代理对象 * method拦截的方法 * args拦截方法的参数数组 * methodProxy方法的代理对象,用于执行父类的对象 */ public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { //创建切面对象 MyAspect myAspect = new MyAspect(); //前增强 myAspect.check_Permissions(); //目标的执行方法 Object obj = methodProxy.invokeSuper(proxy, args); //后增强 myAspect.log(); return obj; } }
三.SpringAOP中的术语
总结:AOP代理就是由AOP框架动态生成的一个对象,该对象可以作为目标对象使用.
四.Spring如何实现AOP
下面为方法一:
1.导入环境
1.写切面类
package com.zlj.factorybean; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; //切面类:也就是增强&通知 public class MyAspect implements MethodInterceptor{ public Object invoke(MethodInvocation mi) throws Throwable { pre(); //执行目标方法 Object obj = mi.proceed(); next(); return obj; } public void pre(){ System.out.println("前"); } public void next(){ System.out.println("后"); } }
2.写配置文件相当于上面所说的创建代理
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> <!-- 1.目标类 --> <bean id="userDao" class="com.zlj.jdk.UserDaoImpl"/> <!-- 2.切面类 --> <bean id="myAspect" class="com.zlj.factorybean.MyAspect"/> <!-- 3.使用Spring代理工厂定义一个代理对象 --> <bean id="userDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean"> <!--3.1指定代理实现的接口 --> <property name="proxyInterfaces" value="com.zlj.jdk.UserDao"/> <!--3.2指定目标的对象 --> <property name="target" ref="userDao"/> <!--3.3指定切面,植入环绕通知 --> <property name="interceptorNames" value="myAspect"/> <!--3.4指定代理方式,true:使用cglib,false(默认):使用jdk动态代理 --> <property name="proxyTargetClass" value="true"/> </bean> </beans>
3.测试类
public class Test { public static void main(String[] args) { String xmlPath="ApplicationContext.xml"; ApplicationContext ac = new ClassPathXmlApplicationContext(xmlPath); UserDao userDao = (UserDao)ac.getBean("userDaoProxy"); userDao.addUser(); userDao.deleteUser(); } }
4.结果
方式二:基于XML声明式AspectJ开发(比较好用,上面的简化)
1.先导入下面这个jar包
2.
切面类
package com.zlj.aspectj.xml; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Aspect; import org.springframework.stereotype.Component; /** * 切面类,在此处中编写通知 * @author lenovo * */ public class MyAspect { //前置通知 public void myBefore(JoinPoint j){ System.out.println("前置通知:模拟执行权限检查...目标类是: "+j.getTarget()+" 被增强的目标方法 "+j.getSignature().getName()); } //后置通知 public void myAfterReturning(JoinPoint j){ System.out.println("后置通知:模拟记录日志...被植入增强处理的目标方法 : "+j.getSignature().getName()); } //环绕通知:ProceedingJoinPoint是Joinpoint的子接口,表示可执行的目标方法 public Object myAround(ProceedingJoinPoint p)throws Throwable{ //开始 System.out.println("环绕通知开始"); //执行当前目标方法 Object obj = p.proceed(); //结束 System.out.println("环绕通知结束"); return obj; } //异常通知 public void myThrow(JoinPoint j,Throwable e){ System.out.println("异常通知:出错了"+e.getMessage()); } //最终通知 public void myAfter(){ System.out.println("最终通知,模拟方法结束,释放资源"); } }
配置文件
头文件记住加这个
<?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: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-3.0.xsd "> <!-- 1.目标类 --> <bean id="userDao" class="com.zlj.jdk.UserDaoImpl"/> <!-- 2.切面类 --> <bean id="myAspect" class="com.zlj.aspectj.xml.MyAspect"/> <!-- 3.aop编程 --> <aop:config> <!-- 配置切面 --> <aop:aspect ref="myAspect"> <!-- 3.1配置切面点,及需要加强的方法 --> <aop:pointcut expression="execution(* com.zlj.jdk.*.*(..))" id="myPointcut"/> <!-- 关联通知Advice和切入点Pointcut --> <aop:before method="myBefore" pointcut-ref="myPointcut"/> <aop:after-returning method="myAfterReturning" pointcut-ref="myPointcut" returning="returnVal"/> <aop:around method="myAround" pointcut-ref="myPointcut"/> <!-- 若不发生异常不执行,throwing属性用于通知第二个参数的名称,类型为throwable --> <aop:after-throwing method="myThrow" pointcut-ref="myPointcut" throwing="e"/> <!-- 最终通知:无论程序发生任何事情,都将执行 --> <aop:after method="myAfter" pointcut-ref="myPointcut"/> </aop:aspect> </aop:config> </beans>
测试代码
方式三:基于注解的声明式AspectJ开发(非常方便,一定要掌握)
在方式二的基础上进行修改切面类
package com.zlj.aspectj.annotation; 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.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; @Aspect @Component public class MyAspect { //定义切入点表达式 @Pointcut("execution(* com.zlj.jdk.*.*(..))") //使用一个返回值为void,方法体为空的方法来命名切入点 private void myPointCut(){} //前置通知 @Before("myPointCut()") public void myBefore(JoinPoint j){ System.out.println("前置通知:模拟执行权限检查...目标类是: "+j.getTarget()+" 被增强的目标方法 "+j.getSignature().getName()); } //后置通知 @AfterReturning("myPointCut()") public void myAfterReturning(JoinPoint j){ System.out.println("后置通知:模拟记录日志...被植入增强处理的目标方法 : "+j.getSignature().getName()); } //环绕通知:ProceedingJoinPoint是Joinpoint的子接口,表示可执行的目标方法 @Around("myPointCut()") public Object myAround(ProceedingJoinPoint p)throws Throwable{ //开始 System.out.println("环绕通知开始"); //执行当前目标方法 Object obj = p.proceed(); //结束 System.out.println("环绕通知结束"); return obj; } //异常通知 @AfterThrowing(value="myPointCut()",throwing="e") public void myThrow(JoinPoint j,Throwable e){ System.out.println("异常通知:出错了"+e.getMessage()); } //最终通知 @After("myPointCut()") public void myAfter(){ System.out.println("最终通知,模拟方法结束,释放资源"); } }
写完后写配置文件发现,配置文件要写的好少,感觉第三个好简单....
最后结果也相同
方法一和方法二让我更好的理解了AOP如何工作,方法三让我们今后方便使用AOP