简单直白的去理解AOP,了解Spring AOP,使用 @AspectJ - 读书笔记
AOP = Aspect Oriental Programing 面向切面编程
文章里不讲AOP术语,什么连接点、切点、切面什么的,这玩意太绕,记不住也罢。旨在以简单、直白的方式理解AOP,理解Spring AOP, 应用 @AspectJ。
- 什么是AOP?
- Spring AOP 实现机制
- 使用Spring AOP,并通过xml配置(这个稍微看看就行了,你不一定用它)
- 使用@AspectJ (未完成)
1、什么是AOP?
| 方法1 | 方法2 | 方法3 |
| A | A | A |
| 代码x | 代码y | 代码z |
| B | B | B |
从纵向看,方法1、2、3 都执行了相同的A、B代码,这样重复代码是很无聊的。
一个典型的场景就是:开启事务,更新表里数据,提交事务; 开启事务,删除表里数据,提交事务。
所以我们从横向来,把重复代码抽取出来,变为
| A | A | A |
| 方法1(代码x) | 方法2(代码y) | 方法3(代码z) |
| B | B | B |
AOP希望将A、B 这些分散在各个业务逻辑中的相同代码,通过横向切割的方式抽取到一个独立的模块中,还业务逻辑类一个清新的世界。
当然,将这些重复性的横切逻辑独立出来很容易,但是如何将独立的横切逻辑 融合到 业务逻辑中 来完成和原来一样的业务操作,这是事情的关键,也是AOP要解决的主要问题。
2.Spring AOP 实现机制
Spring AOP使用动态代理技术在运行期织入增强的代码,使用了两种代理机制,一种是基于JDK的动态代理,另一种是基于CGLib的动态代理。
织入、增强 是AOP的两个术语,织入增强的代码简单点就是在你的代码上插入另一段代码。
JDK动态代理主要涉及到java.lang.reflect包中的两个类:Proxy 和 InvocationHandler(接口)。
直接上代码
package test;
public interface CalcService {
public void add(int x, int y);
}
CalcService 接口
package test;
public class CalcServiceImpl implements CalcService{
public void add(int x, int y) {
System.out.println("结果为" + (x + y));
}
}
CalcServiceImpl 实现类
package test; import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method; public class CalcHandler implements InvocationHandler { public Object target; public CalcHandler(Object target){
this.target = target;
} /**
* 实现接口的方法
* @param proxy 最终生成的代理实例
* @param method 被代理目标(也就是target)的某个具体方法
* @param args 某个具体方法的入参参数
* @return Object 方法返回的值*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("*******调用方法前执行代码******");
Object obj = method.invoke(this.target, args);
System.out.println("*******调用方法后执行代码******");
return obj;
} }
CalcHandler 实现InvocationHandler
package test;
import java.lang.reflect.Proxy;
public class Test {
public static void main(String[] args){
long start = System.nanoTime();
CalcService target = new CalcServiceImpl();
CalcHandler handler = new CalcHandler(target);
CalcService calcProxy = (CalcService)Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
handler);
System.out.println("创建时间:" + (System.nanoTime()-start));
start = System.nanoTime();
calcProxy.add(2, 3);
System.out.println("执行时间:" + (System.nanoTime()-start));
}
}
Test 测试
执行结果为
*******调用方法前执行代码******
结果为2
*******调用方法后执行代码******
但是JDK动态代理有一个限制,即它只能为接口创建代理实例*******************************。
看Proxy的方法 newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
interfaces 是需要代理实例实现的接口列表
那么对于一个没有通过接口定义业务方法的类,怎么创建代理实例?
CGLib
CGLib采用非常底层的字节码技术,可以在运行时为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用。
package test; import java.lang.reflect.Method; import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy; public class CalcProxy implements MethodInterceptor {
private Enhancer enhancer = new Enhancer(); public Object getProxy(Class clazz){
enhancer.setSuperclass(clazz); // 设置需要被代理的类 target
enhancer.setCallback(this);
return enhancer.create(); // 通过字节码技术动态创建子类
} // 拦截父类所有方法的调用
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("*******调用方法前执行代码******");
Object result = proxy.invokeSuper(obj, args); // 通过代理类调用父类中的方法
System.out.println("*******调用方法后执行代码******");
return result;
} }
CalcProxy 实现MethodInterceptor接口
package com.ycp.framework.test.proxyPattern.sample2;
public class Test2 {
public static void main(String[] args) {
CalcProxy proxy = new CalcProxy();
CalcServiceImpl calcImpl = (CalcServiceImpl)proxy.getProxy(CalcServiceImpl.class);
calcImpl.add(2, 3);
}
}
Test2 测试类
之后对两者做了一个效率对比
我在自己本机上通过System.nanoTime()对两者做了记录,结果如下
JDK动态代理 CGLiib
创建代理对象时间 720 1394 1 3473 7007 (时间单位为纳秒) 代理对象执行方法时间 97 7322 15 2080
一个创建花费时间长,一个执行时间长。
3.使用 Spring AOP,并通过XML配置
在Spring中,定义了 AopProxy接口,并提供了两个final类型的实现类
Cglib2AopProxy JdkDynamicAopProxy
以一个前置增强为例,也就是说在目标方法执行前执行的代码
package test; import java.lang.reflect.Method; import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.aop.framework.ProxyFactory; public class CalcBeforeAdvice implements MethodBeforeAdvice { public void before(Method method, Object[] args, Object obj) throws Throwable {
System.out.println("*******调用目标方法前执行代码******"); } public static void main (String [] args){
CalcService target = new CalcServiceImpl(); CalcBeforeAdvice advice = new CalcBeforeAdvice(); // 1 spring 提供的代理工厂
ProxyFactory pf = new ProxyFactory();
// 2 设置代理目标
pf.setInterfaces(target.getClass().getInterfaces());// 指定对接口进行代理,将使用JdkDynamicAopProxy
// 下面两行操作,有任意一行,都将使用Cglib2AopProxy
pf.setOptimize(true);// 启用代理优化,将使用Cglib2AopProxy
pf.setProxyTargetClass(true); // true表示对类进行代理,将使用Cglib2AopProxy pf.setTarget(target);
// 3 为代理目标添加增强
pf.addAdvice(advice);
// 4 生成代理实例 CalcService proxy = (CalcService) pf.getProxy();
System.out.println(proxy.getClass().getName());
proxy.add(2, 3);
}
}
通过Spring实现前置增强
以上通过ProxyFactory创建代理,下面我们通过Spring配置来声明代理
<bean id="calcBeforAdvice" class="test.CalcBeforeAdvice"/>
<bean id="calcTarget" class="test.CalcServiceImpl"/>
<bean id="calcProxy" class="org.springframework.aop.framework.ProxyFactoryBean"
p:proxyInterfaces="test.CalcService" //指定代理接口,如果有多个接口,可用,隔开
p:interceptorNames="calcBeforAdvice"//指定使用的增强,引用第一行,如果有多个可用,隔开
p:target-ref="calcTarget"//指定对那个Bean进行代理,引用第二行
p:proxyTargetClass="true" //指定是否对类进行代理
p:singleton="true"//指定返回的代理是否单实例,默认为true
/>
除了前置增强BeforeAdvice,还有后置增强AfterReturningAdvice、环绕增强MethodInterceptor、异常抛出增强
ThrowsAdvice、及引介增强IntroductionInterceptor,均为接口。
其中引介增强稍微强调一下,它会在目标类中增加一些新的方法和属性。
到了这里,可能对AOP稍有些了解了,那我们简单说一下AOP的几个名词
连接点Joinpoint:类初始化前、初始化后, 方法调用前、调用后,方法抛出异常后,这些特定的点,叫连接点。
切点Pointcut:想想数据库查询,切点就是通过其所设定的条件找到对应的连接点。
增强Advice:就是把代码加到某个连接点上。
引介Introduction:一种特殊的增强,它为类增加一些属性和方法,假设某个业务类没有实现A接口,我们给它添加方法,让其成为A的实现类。
织入Weaving:就是怎么将增强添加到连接点上。
三种织入方式:1、编译期织入,要求使用特殊的JAVA编译器
2.类装载期织入,要求使用特殊的类装载器
3.动态代理织入,在运行期为目标类添加增强
Spring采用动态代理,而AspectJ采用编译期织入和类装载期织入。
目标对象Target:也就是你自己的业务类,AOP就是对这个类做增强、引介。
代理Proxy: 目标对象被织入增强后产生的结果类。
切面:由切点和增强(引介)组成,Spring AOP就是负责实施切面的框架,它将切面所定义的横切逻辑(也就是代码)织入到切面所指定的连接点(也就是代码往哪加)中。
看完了名词,再看完之前的代码,我们发现增强被织入到了目标类的所有方法中(XX的,都木有选择的余地....)
现在我们要对某些类的某些方法织入增强,那这时候就涉及到切点概念了
以如下为例:我只想针对所有的以add开头的方法做处理
静态普通方法名匹配
package com.ycp.framework.test.proxyPattern.sample2; import java.lang.reflect.Method; import org.springframework.aop.ClassFilter;
import org.springframework.aop.IntroductionInterceptor;
import org.springframework.aop.ThrowsAdvice;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor; public class AddAdvisor extends StaticMethodMatcherPointcutAdvisor { //StaticMethodMatcherPointcutAdvisor 抽象类 // 实现父类 matches方法
public boolean matches(Method method, Class clazz) {
//只匹配add方法
return 0 == "add".indexOf(method.getName());
} // //切点类匹配规则为 CalcServiceImpl的类或子类,
// @Override
// public ClassFilter getClassFilter(){
// return new ClassFilter(){
// public boolean matches(Class clazz){
// return CalcServiceImpl.class.isAssignableFrom(clazz);
// }
// };
// } public static void main (String [] args){
CalcService target = new CalcServiceImpl();
CalcBeforeAdvice advice = new CalcBeforeAdvice(); // 1 spring 提供的代理工厂
ProxyFactory pf = new ProxyFactory(); // 2 设置代理目标
pf.setInterfaces(target.getClass().getInterfaces());// 指定对接口进行代理,将使用JdkDynamicAopProxy
// 下面两行操作,有任意一行,都将使用Cglib2AopProxy
pf.setOptimize(true);// 启用代理优化,将使用Cglib2AopProxy
pf.setProxyTargetClass(true); // true表示对类进行代理,将使用Cglib2AopProxy pf.setTarget(target); // 3 为代理目标添加增强
AddAdvisor advisor = new AddAdvisor();
advisor.setAdvice(advice); pf.addAdvisor(advisor); // 4 生成代理实例
CalcService proxy = (CalcService) pf.getProxy(); System.out.println(proxy.getClass().getName());
proxy.add(2, 3);
} }
AddAdvisor 继承StaticMethodMatcherPointcutAdvisor
通过Spring配置来定义切面
<bean id="calcBeforAdvice" class="test.CalcBeforeAdvice"/>
<bean id="calcTarget" class="test.CalcServiceImpl"/>
<bean id="addAdvisor" class="test.AddAdvisor"
p:advice-ref="calcBeforAdvice"//向切面注入一个前置增强
/> <bean id="parent" class="org.springframework.aop.framework.ProxyFactoryBean"
p:interceptorNames="addAdvisor"
p:proxyTargetClass="true"
/> <bean id="calc" parent="parent" p:target-ref="calcTarget"/> //CalcServiceImpl的代理
上面的忒麻烦,我们通过静态正则表达式来匹配
<bean id="calcBeforAdvice" class="test.CalcBeforeAdvice"/>
<bean id="calcTarget" class="test.CalcServiceImpl"/>
<bean id="addRegexpAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"
p:advice-ref="calcBeforAdvice"//向切面注入一个前置增强 >
<property name="patterns">
<list>
<value> .add*</value>//匹配模式串
</list>
</property>
</bean>
<bean id="calc" class="org.springframework.aop.framework.ProxyFactoryBean"
p:interceptorNames="addRegexpAdvisor"
p:target-ref="calcTarget"
p:proxyTargetClass="true"
/>
Spring提供了6种类型的切点,静态方法切点、动态方法切点、注解切点、表达式切点、流程切点,我能力有限,没有研究下去,仅以静态切点 StaticMethodMatcherPointcut 做个例子就算完事,啥时项目用到了啥时再研究吧。
4.使用AspectJ
Spring AOP应用是比较麻烦的,要实现这个那个接口,写这个那个XML描述,你头疼不?
使用@AspectJ的注解可以非常容易的定义一个切面,不需要实现任何的接口
简单直白的去理解AOP,了解Spring AOP,使用 @AspectJ - 读书笔记的更多相关文章
- AOP及spring AOP的使用
介绍 AOP是一种概念(思想),并没有设定具体语言的实现. AOP是对oop的一种补充,不是取而代之. 具体思想:定义一个切面,在切面的纵向定义处理方法,处理完成之后,回到横向业务流. 特征 散布于应 ...
- 【AOP】Spring AOP基础 + 实践 完整记录
Spring AOP的基础概念 ============================================================= AOP(Aspect-Oriented Pr ...
- 【Spring AOP】Spring AOP的使用方式【Q】
Spring AOP的三种使用方式 经典AOP使用方式 改进XML配置方式 基于注解的方式 第1种方式可以作为理解spring配置AOP的基础,是最原始的配置方式,也体现了spring处理的过程. 使 ...
- 死磕Spring之AOP篇 - Spring AOP常见面试题
该系列文章是本人在学习 Spring 的过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring 源码分析 GitHub 地址 进行阅读. Spring 版本:5.1 ...
- 死磕Spring之AOP篇 - Spring AOP总览
该系列文章是本人在学习 Spring 的过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring 源码分析 GitHub 地址 进行阅读. Spring 版本:5.1 ...
- 死磕Spring之AOP篇 - Spring AOP自动代理(二)筛选合适的通知器
该系列文章是本人在学习 Spring 的过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring 源码分析 GitHub 地址 进行阅读. Spring 版本:5.1 ...
- 死磕Spring之AOP篇 - Spring AOP自动代理(三)创建代理对象
该系列文章是本人在学习 Spring 的过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring 源码分析 GitHub 地址 进行阅读. Spring 版本:5.1 ...
- 自己实现 aop 和 spring aop
上文说到,我们可以在 BeanPostProcessor 中对 bean 的初始化前化做手脚,当时也说了,我完全可以生成一个代理类丢回去. 代理类肯定要为用户做一些事情,不可能像学设计模式的时候创建个 ...
- AOP和spring AOP学习记录
AOP基本概念的理解 面向切面AOP主要是在编译期或运行时,对程序进行织入,实现代理, 对原代码毫无侵入性,不破坏主要业务逻辑,减少程序的耦合度. 主要应用范围: 日志记录,性能统计,安全控制,事务处 ...
随机推荐
- Jquery 打开新页面
1.在click事件触发时,先打开空白页面 var newWeb=window.open('_blank'); 2.在事件操作后,写入空白页面的链接 newWeb.location='新页面的链接地址 ...
- centos7下采用Nginx+uwsgi来部署django
之前写过采用Apache和mod_wsgi部署django,因为项目需要,并且想比较一下Nginx和Apache的性能,尝试采用Nginx+uwsgi的模式来部署django. 1.安装uwsgi以及 ...
- <Android开源库> PagerSlidingTabStrip从头到脚
简介 PagerSlidingTabStrip,是我个人经常使用到的一个和ViewPager配合的页面指示器,可以满足开发过程中常用的需求,如类似于今日头条的首页新闻内容导航栏等等,之前自己开发的Ju ...
- Visual Studio 2012 Update 1 离线升级包(相当于VS2012 SP1离线补丁包)
Visual Studio 2012 Update 1 发布也有一段时间了,吾乐吧尝试了好几次在线升级,但是网络不给力啊,结果都失败了.于是一直都想找到官方提供的VS2012 SP1完整离线升级包,不 ...
- Oracle EBS AR 收款取数
-- 收款核销,贷项通知单核销也是通过ar_receivable_applications_all表 SELECT cr.receipt_number ,ad.amount_dr ,ad.amount ...
- SQL Server登录方式
SQL Server登录服务器有两种验证方式,一种是windows身份验证,也就是本机验证,另一种就是SQL Server验证,就是使用账号密码的方式验证. 在使用windows身份验证登录时,直接就 ...
- vscode对Vue文件的html部分格式化失效问题解决办法
使用vscode编辑vue文件时发现突然格式化代码不会对<template> </template>之间的html生效了,解决办法很简单 文件 --> 首选项 ---&g ...
- MySQl新特性 GTID
GTID简介 概念 全局事务标识符(GTID)是创建的唯一标识符,并与在源(主)服务器上提交的每个事务相关联.此标识符不但是唯一的,而且在给定复制设置中的所有服务器上都是唯一的.所有交易和所有GTID ...
- qt designer启动后不显示界面问题的原因与解决办法
Qt 5.6.1无论是在vs里双击ui文件还是直接启动designer.exe都一直无法显示界面,但任务管理器中可以看到该进程是存在的.前几天还正常的,但昨天加了一块NVIDIA的显卡(机器自带核显) ...
- 3.2Python的循环结构语句:
返回总目录 目录: 1.while循环 2.for循环 3.循环保留字:break与continue 循环总览: (一)while循环: (1)单个while循环: while 条件: 循环体 ...