重点
- 1、AOP是什么;
- 2、 五大通知 ( 核心点);
- 3、代码分享;
重点解析:
如何实现aop?aop有什么优点?
简介:面向切面编程(Aspect Orient Programming,AOP)是软件编程思想发展到一定阶段的产物,是面向对象编程的有益补充。AOP一般适用于具有横切逻辑的场合,如访问控制、事务管理、性能检测等。
作用:面向切面编程,简单地说就是在不改变源程序的基础上为代码段增加新的功能,对代码进行增强处理。
aop的原理
aop面向切面编程的原理就是代理:
1、静态代理
采用一些工具类对原来的类生成一个代理类,代理类以.class存在
2、动态代理(重点,spring基于动态代理实现aop)
在运行中,通过反射生成类的代理对象,在代理对象中对原来的对象进行增强。
spring采用动态代理的技术包括:
1、基于接口生成代理对象
根据接口方法生成代理对象,对原来的代理对象进行增强
使用jdk提供反射实现
2、基于类生成代理对象
根据类生成一个子类(代理对象),在代理对象(子类)中对父类进行增强。
使用CGLib实现
目标:通过运行代理的测试代码了解通过代理对原来对象进行增强的过程!!!
AOP中关键性概念
1.1、连接点(Joinpoint):程序执行过程中明确的点,如方法的调用,或者异常的抛出.
1.2、目标(Target):被通知(被代理)的对象
1.2.1、注1:完成具体的业务逻辑
1.3、通知(Advice):在某个特定的连接点上执行的动作,同时Advice也是程序代码的具体实现,例如一个实现日志记录的代码(通知有些书上也称为处理)
1.3.1、注2:完成切面编程
1.4、代理(Proxy):将通知应用到目标对象后创建的对象(代理=目标+通知),
1.4.1、注3:只有代理对象才有AOP功能,而AOP的代码是写在通知的方法里面的
1.5、切入点(Pointcut):多个连接点的集合,定义了通知应该应用到那些连接点。
(也将Pointcut理解成一个条件 ,此条件决定了容器在什么情况下将通知和目标组合成代理返回给外部程序)
1.6、适配器(Advisor):适配器=通知(Advice)+切入点(Pointcut)
五大通知加一个工具类
工具类org.springframework.aop.framework.ProxyFactoryBean用来创建一个代理对象,在一般情况下它需要注入以下三个属性:
proxyInterfaces:代理应该实现的接口列表(List)
interceptorNames:需要应用到目标对象上的通知Bean的名字。(List)
target:目标对象 (Object)
-
前置通知(org.springframework.aop.MethodBeforeAdvice):在连接点之前执行的通知()
案例:在购书系统当中使用AOP方式实现日志系统 -
后置通知(org.springframework.aop.AfterReturningAdvice):在连接点正常完成后执行的通知
案例:在线购书系统中,要求不修改BookBizImpl代码的情况下增加如下功能:对买书的用户进行返利:每买本书返利3元。(后置通知)
即:每调用一次buy方法打印:“[销售返利][时间]返利3元。” -
环绕通知(org.aopalliance.intercept.MethodInterceptor):包围一个连接点的通知,最大特点是可以修改返回值,由于它在方法前后都加入了自己的逻辑代码,因此功能异常强大。
它通过MethodInvocation.proceed()来调用目标方法(甚至可以不调用,这样目标方法就不会执行)
案例:修改日志系统不光要输出参数,还要输出返回值(环绕通知)
2.1、 这个接口里面没有定义方法,我们要求我们的类必须实现afterThrows这个方法
2.2、 public void afterThrowing( [Method method,] [Object args,] [Object target,] Throwable throwable );
2.3、 前面三个参数都是可选的,只有第三个参数是必须的,同时我们还可以在同一个类中定义这个方法的多个版本,如:
2.4、 public void afterThrowing( MyException1 ex ) {}
2.5、 public void afterThrowing( MyException2 ex ) {}
2.6、 具体那个方法被调用则根据具体的Exception来判断,由AOP容器自动识别 执行
-
异常通知(org.springframework.aop.ThrowsAdvice):这个通知会在方法抛出异常退出时执行 public void afterThrowing( MyException1 ex ) {} 出现异常执行系统提示,然后进行处理。价格异常为例
-
适配器(org.springframework.aop.support.RegexpMethodPointcutAdvisor) 适配器=通知(Advice)+切入点(Pointcut)
案例:通过适配器解决发书评时也返利的问题;
代码分享
dao方法实现的 bookbiz
public interface BookBiz {
// 购买书籍
public boolean buy(String userName, String bookName, Double price);
// 评价书籍
public void comment(String userName, String comments);
}
对象dao 方法: BookBizImpl
public class BookBizImpl implements BookBiz {
public BookBizImpl() {
super();
}
public boolean buy(String userName, String bookName, Double price) {
// 通过控制台的输出方式模拟购书
if (null == price || price <= 0) {
throw new PriceException("book price exception");
}
System.out.println(userName + " buy " + bookName + ", spend " + price);
return true;
}
/**
* 无返回值的就是null
*/
public void comment(String userName, String comments) {
// 通过控制台的输出方式模拟发表书评
System.out.println(userName + " say:" + comments);
}
}
前置通知类: MyMethodBeforeAdvice
public class MyMethodBeforeAdvice implements MethodBeforeAdvice {
/**
* 案例:在购书系统当中使用AOP方式实现日志系统
*/
public void before(Method method, Object[] args, Object target) throws Throwable {
String targerName = target.getClass().getName();
String methodName = method.getName();
String params =Arrays.toString(args);
String msg ="【系统日记】购书系统:类:"+targerName+",方法 :"+methodName +",需要的参数:"+params;
System.out.println(msg);
}
}
后置通知类:MyAfterReturningAdvice
public class MyAfterReturningAdvice implements AfterReturningAdvice {
/**
* 在线购书系统中,要求不修改BookBizImpl代码的情况下增加如下功能:对买书的用户进行返利:每买本书返利3元。(后置通知)
即:每调用一次buy方法打印:“[销售返利][时间]返利3元。”
*/
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
String targerName = target.getClass().getName();
String methodName = method.getName();
String params =Arrays.toString(args);
String msg ="【返利日记】销售返利:类:"+targerName+",方法 :"+methodName +",需要的参数:"+params+", 目标对象的返回值:"+returnValue;
System.out.println(msg);
}
}
环绕通知类:MyMethodInterceptor
public class MyMethodInterceptor implements MethodInterceptor {
/**
* 修改日志系统不光要输出参数,还要输出返回值(环绕通知)
*/
public Object invoke(MethodInvocation invocation) throws Throwable {
Object target = invocation.getThis();
Method method = invocation.getMethod();
Object[] args = invocation.getArguments();
// a、页面windo.open(b.jsp)
// b页面、执行业务逻辑window.class() 参数: xxxx-->a.jsp
// dindow.getArguments();
String targerName = target.getClass().getName();
String methodName = method.getName();
String params =Arrays.toString(args);
String msg ="【环绕日记】购书系统:类:"+targerName+",方法 :"+methodName ;
System.out.println(msg);
Object returnValue = invocation.proceed();
String msg2 ="【环绕升级日记】返回值:"+returnValue;
System.out.println(msg2);
return returnValue;
}
}
异常通知类:MyThrowsAdvice
public class MyThrowsAdvice implements ThrowsAdvice {
public void afterThrowing( PriceException ex ) {
System.out.println("价格异常,请重新输入!!!!");
}
}
xml文件配置:spring-context.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:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
default-autowire="byType"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<!--。。。。。。。。。。。。。。。。。。。。。 aop 。。。。。。。。。。。。。。。。。。。。。。-->
<!-- 配置目标对象(即书籍) -->
<bean class="com.zking.aop.biz.impl.BookBizImpl" id="bookImpl"></bean>
<!-- 前置配置通知 (即日记)-->
<bean class="com.zking.aop.advice.MyMethodBeforeAdvice" id="myMethodBeforeAdvice"></bean>
<!-- 后置配置通知 (即返利)-->
<bean class="com.zking.aop.advice.MyAfterReturningAdvice" id="myAfter"></bean>
<!-- 环绕置配置通知 (即前置加后置通知)-->
<bean class="com.zking.aop.advice.MyMethodInterceptor" id="myMethodInterceptor"></bean>
<!--异常通知 -->
<bean class="com.zking.aop.advice.MyThrowsAdvice" id="myThrowsAdvice"></bean>
<!-- 过滤通知 -->
<bean class="org.springframework.aop.support.RegexpMethodPointcutAdvisor" id="myRegexpMethodPointcutAdvisor">
<!-- 过滤谁 -->
<property name="advice" ref="myAfter"></property>
<!-- 根据什么规则 (.*buy 代表了以buy结尾的方法都会切入后置通知, .*任意字符0~n)-->
<!--单个正则 -->
<!-- <property name="pattern" value=".*buy"></property> -->
<!-- 多个正则 -->
<property name="patterns">
<list>
<value>.*buy</value>
</list>
</property>
</bean>
<!-- 提供代理(工厂) -->
<bean class="org.springframework.aop.framework.ProxyFactoryBean" id="MeProxy">
<!-- 确定目标 -->
<property name="target" ref="bookImpl"></property>
<!--需要的工具,必须实现目标类中所实现的接口 -->
<property name="proxyInterfaces" >
<!-- 接口不一定是一个,所有需要list集合来实现 -->
<list>
<value>com.zking.aop.biz.BookBiz</value>
</list>
</property>
<!-- 通知是否完成,通知不只有前置还有后置、环绕等等 -->
<property name="interceptorNames">
<list>
<!--前置通知 -->
<value>myMethodBeforeAdvice</value>
<!-- 后置通知 -->
<!-- <value>myAfter</value> -->
<!-- 环绕置配置通知 (即前置加后置通知)-->
<value>myMethodInterceptor</value>
<!-- 异常置配置通知 (即报错提示)-->
<value>myThrowsAdvice</value>
<!-- 过滤通知 -->
<value>myRegexpMethodPointcutAdvisor</value>
</list>
</property>
</bean>
</beans>
测试类:AopTest
public class AopTest {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-context.xml");
BookBiz bookImpl = (BookBiz) applicationContext.getBean("MeProxy");
// 实际执行对象不再是目标对象了,而是通过ProxyFactoryBean生产出来的代理对象来执行业务
// Object bean = applicationContext.getBean("MeProxy");
// System.out.println(bean.getClass().getName());
bookImpl.buy("胡煜", "我想静静", 22d);
bookImpl.comment("胡煜", "静静是谁?");
}
}
结果: