AOP(Aspect Oriented Programming),即面向切面编程,可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。就是将交叉业务逻辑封装成切面,利用AOP的功能将切面织入到主业务逻辑中。所谓交叉业务逻辑是指,通用的、与主业务逻辑无关的代码若不使用AOP,则会出现代码纠缠,即交叉业务逻辑与主业务逻辑混合在一起。这样,会使主业务逻辑变的混杂不清。在代码执行过程中,动态嵌入其他代码,叫做面向切面编程。常见的使用场景:日志、事务、数据库操作等。
二、AOP中关键性概念
| 术语 | 说明 |
|---|---|
| 连接点(Joinpoint) | 程序执行过程中明确的点,如方法的调用,或者异常的抛出. 注:完成具体的业务逻辑 |
| 目标(Target) | 被通知(被代理)的对象 注:完成切面编程 |
| 通知(Advice) | 在某种特定的连接点上所执行的动作 |
| 代理(Proxy) | 将通知应用到目标上所创建的对象(代理=通知+目标) 注:只有代理对象才有AOP功能,而AOP的代码是写在通知的方法里面的 |
| 切入点(Pointcut) | 多个连接点的集合,将通知应用到哪些连接点 |
| 适配器(Advisor) | 通知+切入点(相当于必须满足切入点的条件,才会应用通知部分) |
三、AOP的实现方式
| 通知类型 | 说明 |
|---|---|
| 前置通知(MethodBeforeAdvice) | 目标方法执行之前调用 |
| 后置通知(AfterReturningAdvice) | 目标方法执行完成之后调用 |
| 环绕通知(MethodInterceptor) | 目标方法执行前后都会调用方法,且能增强结果 |
| 异常处理通知(ThrowsAdvice) | 目标方法出现异常调用 |
代理=通知+目标
- 创建目标接口和实现类
public interface IBookBiz {
// 购书
public boolean buy(String userName, String bookName, Double price);
// 发表书评
public void comment(String userName, String comments);
}
public class BookBizImpl implements IBookBiz {
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;
}
public void comment(String userName, String comments) {
// 通过控制台的输出方式模拟发表书评
System.out.println(userName + " say:" + comments);
}
}
前置通知(MethodBeforeAdvice)
创建前置通知类
public class BeforeAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target)
throws Throwable {
//获取被调用的方法名
String methodName = method.getName();
//获取目标方法的类的全路径名
String className = target.getClass().getName();
System.out.println("[前置通知]"+className+"."+methodName+
"执行参数:"+ Arrays.toString(args));
}
}
配置spring文件中前置配置
定义前置通知
beforeAdvice
biz.IBookBiz
初始化spring.xml上下文并执行目标方法
//1.初始化Spring上下文容器(IOC)
ApplicationContext ac=new ClassPathXmlApplicationContext("spring.xml");
IBookBiz proxy = ac.getBean("proxy", IBookBiz.class);
//调用buy和comment方法时,会将前置通知应用到目标方法上得到代理对象
proxy.buy("李四","西游记",200d);
proxy.comment("李四","好看,名著");
在这里的遵循了里氏替换原则
IBookBiz proxy = ac.getBean("proxy", IBookBiz.class);
A
B C
B和C都实现了A,B和C有一个共同的父类A
A:IBookBiz
B:BookBizImpl
C:Proxy
代理(proxy)也实现了IBookBiz接口
BookBizImpl 实现了IBookBiz接口
所有用父类去接收,而不是用BookBizImpl
错误的方式接收
BookBizImpl proxy=ac.getBean(s:"proxy",BookBizImpl .class); 会出现 java.lang.ClassCastException: com.sun.proxy.$Proxy4 cannot be cast to 包路径.BookBizImpl 异常 这里相当于 BookBizImpl proxy=(BookBizImpl )proxy
正确方式
IBookBiz bookBiz1=new BookBizImpl(); IBookBiz bookBiz2=new Proxy();
执行结果为
后置通知(AfterReturningAdvice)
创建后置通知类
public class AfterAdvice implements AfterReturningAdvice {
@Override
public void afterReturning(Object returnValue, Method method,
Object[] args, Object target) throws Throwable {
//获取目标方法名
String methodName = method.getName();
//获取目标方法的类的全路径名
String className = target.getClass().getName();
System.out.println("[后置通知]"+className+"."+methodName+
"执行参数"+ Arrays.toString(args)+"返回值:"+returnValue);
}
}
配置spring文件中后置配置
定义后置通知
afterAdvice
biz.IBookBiz
初始化spring.xml上下文并执行目标方法
//1.初始化Spring上下文容器(IOC)
ApplicationContext ac=new ClassPathXmlApplicationContext("spring.xml");
IBookBiz proxy = ac.getBean("proxy", IBookBiz.class);
//调用buy和comment方法时,会将前置通知应用到目标方法上得到代理对象
proxy.buy("李四","西游记",200d);
proxy.comment("李四","好看,名著");
执行结果为
环绕通知(MethodInterceptor)
同上面步骤,只是实现MethodInterceptor接口
public class AroundAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
//获取目标对象
Object target = invocation.getThis();
//获取目标方法的执行参数
Method method = invocation.getMethod();
//获取目标方法的执行参数
Object[] arguments = invocation.getArguments();
System.out.println("[环绕通知]"+target.getClass().getName()+"."+
method.getName()+"执行参数:"+ Arrays.toString(arguments));
//执行目标方法
Object returnValue = invocation.proceed();
System.out.println("[环绕通知]"+target.getClass()+"."+
method.getName()+"执行参数:"+Arrays.toString(arguments)+
"返回值:"+returnValue);
return returnValue;
}
}
异常处理通知(ThrowsAdvice)
创建异常类
public class PriceException extends RuntimeException{
public PriceException() {
}
public PriceException(String message) {
super(message);
}
public PriceException(String message, Throwable cause) {
super(message, cause);
}
public PriceException(Throwable cause) {
super(cause);
}
public PriceException(String message, Throwable cause, boolean enableSuppression,
boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
创建异常通知类
public class ExceptionAdvice implements ThrowsAdvice {
public void afterThrowing( PriceException ex ) {
System.out.println("[异常通知] 买书价格异常,购买失败!");
}
}
我在实现类中写入了一个判断书本价格书否为空和小于0
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;
}
然后定义异常通知Bean
再在目标中使用异常通知Bean
价格为200d
价格为-200d
适配器(org.springframework.aop.support.RegexpMethodPointcutAdvisor)
适配器=通知+切入点
定义适配器Bena,在这里我用的通知是后置通知
这里的Value的值是方法名 .*方法名
.*buy
再在代理中加入此适配器
regexpmethod
com.zking.spring02.biz.IBookBiz
执行结果为
李四 buy 西游记, spend 200.0 [适配器]买书返利2元! 李四 say:好看,名著
至此,Spring之AOP介绍完毕,由于作者水平有限难免有疏漏,欢迎留言纠错。



