AOP中关键性概念
连接点(Joinpoint):程序执行过程中明确的点,如方法的调用,或者异常的抛出.
目标(Target):被通知(被代理)的对象
注1:完成具体的业务逻辑
通知(Advice):在某个特定的连接点上执行的动作,同时Advice也是程序代码的具体实现,例如一个实现日志记录的代码(通知有些书上也称为处理)
注2:完成切面编程
代理(Proxy):将通知应用到目标对象后创建的对象(代理=目标+通知),
例子:外科医生+护士
注3:只有代理对象才有AOP功能,而AOP的代码是写在通知的方法里面的
切入点(Pointcut):多个连接点的集合,定义了通知应该应用到那些连接点。
(也将Pointcut理解成一个条件 ,此条件决定了容器在什么情况下将通知和目标组合成代理返回给外部程序)
适配器(Advisor):适配器=通知(Advice)+切入点(Pointcut)
如何实现AOP
目标对象只负责业务逻辑代码
通知对象负责AOP代码,这二个对象都没有AOP的功能,只有代理对象才有
v1.0 只是实现了功能
1 biz 2 add(){ 3 4 dao.add(); 5 6 } 7 8 del(){ 9 dao.del(); 10 }
v2.0
浏览器调用了用户的新增、删除方法,客户需要知道是那个用户被删除了,是由谁删除了
新建日志表t_log
1 biz 2 add(){ 3 logDao.add(session.get("current_user"),user); 4 dao.add(user); 5 6 } 7 8 del(){ 9 logDao.add(session.get("current_user"),user); 10 dao.del(); 11 } 12 13 edit(){ 14 logDao.add(session.get("current_user"),user); 15 dao.del(); 16 } 17 18 .....
1. AOP
即面向切面编程
2. AOP带来的好处
让我们可以 “专心做事”
1 public void doSameBusiness (long lParam,String sParam){ 2 // 记录日志 3 log.info("调用 doSameBusiness方法,参数是:"+lParam); 4 // 输入合法性验证 5 if (lParam<=0){ 6 throws new IllegalArgumentException("xx应该大于0"); 7 } 8 if (sParam==null || sParam.trim().equals("")){ 9 throws new IllegalArgumentException("xx不能为空"); 10 } 11 // 异常处理 12 try{ ... 13 }catch(...){ 14 }catch(...){ 15 } 16 // 事务控制 17 tx.commit(); 18 }
3 工具类org.springframework.aop.framework.ProxyFactoryBean用来创建一个代理对象,在一般情况下它需要注入以下三个属性:
proxyInterfaces:代理应该实现的接口列表(List)
interceptorNames:需要应用到目标对象上的通知Bean的名字。(List)
target:目标对象 (Object)
4.核心
IBookBiz
1 package com.Spring.aop.biz; 2 3 public interface IBookBiz { 4 // 购书 5 public boolean buy(String userName, String bookName, Double price); 6 7 // 发表书评 8 public void comment(String userName, String comments); 9 }
BookBizImpl
1 package com.Spring.aop.impl; 2 3 import com.Spring.aop.biz.IBookBiz; 4 import com.Spring.aop.ex.PriceException; 5 6 public class BookBizImpl implements IBookBiz { 7 8 public BookBizImpl() { 9 super(); 10 } 11 12 public boolean buy(String userName, String bookName, Double price) { 13 // 通过控制台的输出方式模拟购书 14 if (null == price || price <= 0) { 15 throw new PriceException("book price exception"); 16 } 17 System.out.println(userName + " buy " + bookName + ", spend " + price); 18 return true; 19 } 20 21 public void comment(String userName, String comments) { 22 // 通过控制台的输出方式模拟发表书评 23 System.out.println(userName + " say:" + comments); 24 } 25 26 }
异常处理:
PriceException
1 package com.Spring.aop.ex; 2 3 public class PriceException extends RuntimeException { 4 5 public PriceException() { 6 super(); 7 } 8 9 public PriceException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { 10 super(message, cause, enableSuppression, writableStackTrace); 11 } 12 13 public PriceException(String message, Throwable cause) { 14 super(message, cause); 15 } 16 17 public PriceException(String message) { 18 super(message); 19 } 20 21 public PriceException(Throwable cause) { 22 super(cause); 23 } 24 25 }
前置通知(org.springframework.aop.MethodBeforeAdvice):在连接点之前执行的通知()
MyMethodBeforeAdvice
1 package com.Spring.aop.advice; 2 3 import java.lang.annotation.Target; 4 import java.lang.reflect.Method; 5 import java.util.Arrays; 6 7 import org.springframework.aop.MethodBeforeAdvice; 8 /** 9 * 前置通知 10 * 买书,评论前加系统日志 11 * @author Administrator 12 * 13 */ 14 public class MyMethodBeforeAdvice implements MethodBeforeAdvice { 15 16 @Override 17 public void before(Method method, Object[] args, Object target) throws Throwable { 18 // TODO Auto-generated method stub 19 20 String clzName = target.getClass().getName(); 21 String methodName = method.getName(); 22 String params = Arrays.toString(args); 23 System.out.println("买书,评论前加系统日志:"+clzName+"."+methodName+"("+params+")"); 24 } 25 26 }
后置通知(org.springframework.aop.AfterReturningAdvice):在连接点正常完成后执行的通知
MyAfterReturningAdvice
1 package com.Spring.aop.advice; 2 3 import java.lang.reflect.Method; 4 import java.util.Arrays; 5 /** 6 * 后置通知 7 * @author Administrator 8 * 9 */ 10 public class MyAfterReturningAdvice implements org.springframework.aop.AfterReturningAdvice { 11 12 @Override 13 public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { 14 // TODO Auto-generated method stub 15 16 String clzName = target.getClass().getName(); 17 String methodName = method.getName(); 18 String params = Arrays.toString(args); 19 System.out.println("买书返利后置通知:"+clzName+"."+methodName+"("+params+")"+" 目标对象方法调用后的返回值"+returnValue); 20 21 } 22 23 }
环绕通知(org.aopalliance.intercept.MethodInterceptor):包围一个连接点的通知,最大特点是可以修改返回值,由于它在方法前后都加入了自己的逻辑代码,因此功能异常强大。
它通过MethodInvocation.proceed()来调用目标方法(甚至可以不调用,这样目标方法就不会执行)
MyMethodInterceptor
1 package com.Spring.aop.advice; 2 3 import java.util.Arrays; 4 5 import org.aopalliance.intercept.MethodInterceptor; 6 import org.aopalliance.intercept.MethodInvocation; 7 /** 8 * 环绕通知 9 * @author Administrator 10 * 11 */ 12 public class MyMethodInterceptor implements MethodInterceptor { 13 14 @Override 15 public Object invoke(MethodInvocation invocation) throws Throwable { 16 // TODO Auto-generated method stub 17 String clzName = invocation.getThis().getClass().getName(); 18 String methodName = invocation.getMethod().getName(); 19 String params = Arrays.toString(invocation.getArguments()); 20 // System.out.println("买书返利后置通知:"+clzName+"."+methodName+"("+params+")"+" 目标对象方法调用后的返回值"+returnValue); 21 //sessionFactory.openSession,session.beginTransation 22 System.out.println("环绕通知:"+clzName+"."+methodName+"("+params+")"); 23 Object returnValue = invocation.proceed(); 24 //transation.commit(),session.close() 25 System.out.println("环绕通知: 目标对象方法调用后的返回值"+returnValue); 26 return returnValue; 27 } 28 29 }
异常通知(org.springframework.aop.ThrowsAdvice):这个通知会在方法抛出异常退出时执行
MyThrowsAdvice
这个接口里面没有定义方法,我们要求我们的类必须实现afterThrows这个方法
public void afterThrowing( [Method method,] [Object args,] [Object target,] Throwable throwable );
前面三个参数都是可选的,只有第三个参数是必须的,同时我们还可以在同一个类中定义这个方法的多个版本,如:
public void afterThrowing( MyException1 ex ) {}
public void afterThrowing( MyException2 ex ) {}
具体那个方法被调用则根据具体的Exception来判断,由AOP容器自动识别 执行
1 package com.Spring.aop.advice; 2 3 import org.springframework.aop.ThrowsAdvice; 4 5 import com.Spring.aop.ex.PriceException; 6 7 public class MyThrowsAdvice implements ThrowsAdvice { 8 public void afterThrowing( PriceException ex ) { 9 System.out.println("价格输入有误,购买失败,请重新输入!!!"); 10 } 11 }
适配器(org.springframework.aop.support.RegexpMethodPointcutAdvisor) 适配器=通知(Advice)+切入点(Pointcut)
不需要创建实体类,在Xml中直接配置
spring-context.xml
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xmlns:context="http://www.springframework.org/schema/context" 5 xmlns:tx="http://www.springframework.org/schema/tx" 6 xmlns:aop="http://www.springframework.org/schema/aop" 7 default-autowire="byType" 8 xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd 9 http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd 10 http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd 11 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd"> 12 13 <bean class="com.Spring.ioc.impl.UserBizmpl2" id="userBiz"></bean> 14 <bean class="com.Spring.ioc.web.UserAction" id="xxx"> 15 <!-- set注入用property标签 --> 16 <!-- <property name="userBiz" ref="userBiz"></property> --> 17 <!-- <property name="uname" value="CXK"></property> 18 <property name="age" value="22"></property> --> 19 <!-- set注入用constructor-arg标签 --> 20 <constructor-arg name="uname" value="CXK"></constructor-arg> 21 <constructor-arg name="age" value="22"></constructor-arg> 22 <property name="hobby" > 23 <list> 24 <value>唱</value> 25 <value>跳</value> 26 <value>Rap</value> 27 </list> 28 29 </property> 30 </bean> 31 <bean class="com.Spring.ioc.web.OrderAction" id="ttt"> 32 <!-- <property name="userBiz" ref="userBiz"></property> --> 33 </bean> 34 <!-- ********************AOP***************************** --> 35 <!-- 目标对象 --> 36 <bean id="bookBiz" class="com.Spring.aop.impl.BookBizImpl"></bean> 37 <!-- 通知 --> 38 <!-- 前置 --> 39 <bean id="myBefore" class="com.Spring.aop.advice.MyMethodBeforeAdvice"></bean> 40 <!-- 后置 --> 41 <bean id="myAfter" class="com.Spring.aop.advice.MyAfterReturningAdvice"></bean> 42 <!-- 环绕 --> 43 <bean id="myInterceptor" class="com.Spring.aop.advice.MyMethodInterceptor"></bean> 44 <!-- 异常 --> 45 <bean id="myThrowsAdvice" class="com.Spring.aop.advice.MyThrowsAdvice"></bean> 46 <!-- 过滤通知 --> 47 <bean id="myAfter2" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"> 48 <property name="advice" ref="myAfter"></property> 49 <property name="pattern" value=".*buy"></property> 50 51 </bean> 52 53 <!-- 有代理工厂来组装目标对象及通知 --> 54 <bean id="bookProxy" class="org.springframework.aop.framework.ProxyFactoryBean"> 55 <property name="target" ref="bookBiz"></property> 56 <!-- proxyInterfaces:代理应该实现的接口列表(List) --> 57 <property name="proxyInterfaces"> 58 <list> 59 <value>com.Spring.aop.biz.IBookBiz</value> 60 </list> 61 </property> 62 <!-- interceptorNames:需要应用到目标对象上的通知Bean的名字。(List) --> 63 <property name="interceptorNames"> 64 <list> 65 <value>myBefore</value> 66 <!-- <value>myAfter</value> --> 67 <value>myAfter2</value> 68 <value>myInterceptor</value> 69 <value>myThrowsAdvice</value> 70 </list> 71 </property> 72 73 </bean> 74 </beans>
测试:
AopTest
1 package com.Spring.aop.test; 2 3 import org.springframework.context.ApplicationContext; 4 import org.springframework.context.support.ClassPathXmlApplicationContext; 5 6 import com.Spring.aop.biz.IBookBiz; 7 import com.Spring.ioc.web.OrderAction; 8 import com.Spring.ioc.web.UserAction; 9 10 public class AopTest { 11 12 public static void main(String[] args) { 13 ApplicationContext springContext = new ClassPathXmlApplicationContext("/spring-context.xml"); 14 IBookBiz bean = (IBookBiz) springContext.getBean("bookProxy"); 15 System.out.println(bean.getClass()); 16 boolean buy = bean.buy("李四", "圣虚", 66d); 17 bean.comment("李四", "真的虚了"); 18 19 } 20 }