spring AOP详解三
CGLib采用非常底层的字节码技术,可以为一个类创建子类,并在子类中采用方法拦截的结束拦截所有父类方法的调用,并顺势织入横切逻辑。我们采用CGLib技术可以编写一个可以为任何类创建织入横切逻辑代理对象的代理创建器,下面看一个使用CGLib代理技术实现横切的一个例子:
1.CglibProxy.java
package spring.aop.demo2; import java.lang.reflect.Method; import spring.aop.demo1.PerformanceMonitor; import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy; public class CglibProxy implements MethodInterceptor { // private static CglibProxy proxy = new CglibProxy();
private Enhancer enhancer = new Enhancer(); public Object getProxy(Class clazz) {
enhancer.setSuperclass(clazz);// 设置需要创建子类的类
enhancer.setCallback(this);
return enhancer.create();// 通过字节码技术动态创建子类实例
} @Override
public Object intercept(Object arg0, Method arg1, Object[] arg2,
MethodProxy arg3) throws Throwable {
PerformanceMonitor.begin(arg0.getClass().getName() + "."
+ arg1.getName());
Object result = arg3.invokeSuper(arg0, arg2);
PerformanceMonitor.end();
return result;
} }
2.UserServiceImpl.java
package spring.aop.demo2; public class UserServiceImpl{ public void removeUser(int userId) {
System.out.println("模拟删除用户:" + userId);
} public void addUser(int userId) {
// TODO Auto-generated method stub
} public static void main(String[] args) {
CglibProxy proxy = new CglibProxy();
UserServiceImpl userService =(UserServiceImpl)proxy.getProxy(UserServiceImpl.class);
userService.removeUser(7);
}
}
输出:
begin monitor...
模拟删除用户:7
end monitor...
spring.aop.demo2.UserServiceImpl$$EnhancerByCGLIB$$4d9bdf63.removeUser花费15毫秒
总结:用户通过getProxy(Class clazz)为一个类创建动态代理对象,该代理对象通过扩展clazz创建代理对象,在这个代理对象中,我们织入横切逻辑代码。intercept是CGLib定义的Interceptor接口的方法,它拦截所有目标方法的调用,参数arg0表示目标类的实例;参数arg1表示目标类方法的反射对象;arg2表示目标类方法的参数的反射对象;arg3表示代理类实例;
我们看到输出spring.aop.demo2.UserServiceImpl$$EnhancerByCGLIB$$4d9bdf63.removeUser,这个特殊的类就是CGLib为UserService动态创建的子类。
Spring AOP的底层就是通过代理(JDK动态代理或CGlib代理)来实现AOP的,但是这种实现方式存在三个明显需要改进的地方:
a.目标类的所有方法都添加了横切逻辑,而有时,这并不是我们所期望的,我们可能只希望对业务类中的某些特定的方法添加横切逻辑;
b.我们通过硬编码的方式制定了织入横切逻辑的织入点,即在目标业务方法的开始和结束前织入代码;
c.我们手工编写代理实例的创建过程,为不同类创建代理时,需要分别编写相应的创建代码,无法做到通用;
CGLib所创建的动态代理对象的性能比JDK的高大概10倍,但CGLib在创建代理对象的时间比JDK大概多8倍,所以对于singleton的代理对象或者具有实例池的代理,因为无需重复的创建代理对象,所以比较适合CGLib动态代理技术,反之选择JDK代理。值得一提的是由于CGLib采用动态创建子类的方式生成代理对象,所以不能对目标类中final的方法进行代理。
三、创建增强类
Spring使用增强定义横切逻辑,同时由于Spring只支持方法连接点,增强还包括了在方法的哪一点加入横切代码的方位信息,所以增强既包含横切逻辑,还包含部分连接点的信息。
前置增强:org.springframework.aop.BeforeAdvice 代表前置增强,因为Spring只支持方法级的增强,所以MethodBeforeAdvice是目前可用的前置增强,表示在目标方法执行前实施增强,而BeforeAdvice是为了将来版本扩展需要而定义的,下面我们看一个前置通知的小例子:
1.Waiter.java
package spring.aop.beforeadvicedemo; public interface Waiter {
void greetTo(String name);
void serverTo(String name);
}
2.GreetingBeforeAdvice.java 实现前置增强接口的横切逻辑
package spring.aop.beforeadvicedemo; import java.lang.reflect.Method; import org.springframework.aop.MethodBeforeAdvice; public class GreetingBeforeAdvice implements MethodBeforeAdvice { //arg0是目标类的方法,arg1是目标类方法的参数,arg2是目标类的实例
@Override
public void before(Method arg0, Object[] arg1, Object arg2)
throws Throwable {
String clientName = (String)arg1[];
System.out.println("How are you! Mr." + clientName);
} }
3.目标类NaiveWaiter.java和测试代码
package spring.aop.beforeadvicedemo; import org.springframework.aop.BeforeAdvice;
import org.springframework.aop.framework.ProxyFactory; public class NaiveWaiter implements Waiter { @Override
public void greetTo(String name) {
System.out.println("great to " + name);
} @Override
public void serverTo(String name) {
System.out.println("serving "+ name);
} public static void main(String[] args) { BeforeAdvice advice = new GreetingBeforeAdvice();
Waiter waiter = new NaiveWaiter(); //Spring提供的代理工厂
ProxyFactory pf = new ProxyFactory(); //设置代理目标
pf.setTarget(waiter); //为代理目标添加增强
pf.addAdvice(advice); //生成代理实例
Waiter waiterProxy = (Waiter)pf.getProxy();
waiterProxy.greetTo("nicholaslee");
waiterProxy.serverTo("nicholaslee");
} }
输出:
How are you! Mr.nicholaslee
great to nicholaslee
How are you! Mr.nicholaslee
serving nicholaslee
说明:在测试代码中,我们用到了org.springframework.aop.framework.ProxyFactory,这个内部就是使用了我们之前的JDK代理或者CGLib代理的技术,将增强应用到目标类中。Spring定义了org.springframework.aop.framework.AopProxy接口,并提供了两个final的实现类,其中:
Cglib2AopProxy使用CGLib代理技术创建代理,而JdkDynamicAopProxy使用JDK代理技术创建代理;
如果通过ProxyFactory的setInterfaces(Class[] interfaces)指定针对接口进行代理,ProxyFactory就使用JdkDynamicAopProxy;
如果是通过类的代理则使用Cglib2AopProxy,另外也可以通过ProxyFactory的setOptimize(true)方法,让ProxyFactory启动优化代理模式,这样针对接口的代理也会使用Cglib2AopProxy。
BeforeAdvice advice = new GreetingBeforeAdvice();
Waiter waiter = new NaiveWaiter(); //Spring提供的代理工厂
ProxyFactory pf = new ProxyFactory();
pf.setInterfaces(waiter.getClass().getInterfaces());
以上代码就指定了JdkDynamicAopProxy进行代理;
BeforeAdvice advice = new GreetingBeforeAdvice();
Waiter waiter = new NaiveWaiter(); //Spring提供的代理工厂
ProxyFactory pf = new ProxyFactory();
pf.setInterfaces(waiter.getClass().getInterfaces());
pf.setOptimize(true);
以上代码虽然指定了代理的接口,但由于setOptimize(true),所以还是使用了Cglib2AopProxy代理;
我们使用了addAdvice来添加一个增强,用户可以用该方法添加多个增强,形成一个增强链,调用顺序和添加顺序一致,下标从0开始:
package spring.aop.beforeadvicedemo; import java.lang.reflect.Method; import org.springframework.aop.MethodBeforeAdvice; public class GreetingBeforeAdvice2 implements MethodBeforeAdvice { @Override
public void before(Method arg0, Object[] arg1, Object arg2)
throws Throwable {
System.out.println( "我是第一个横切逻辑");
} }
public static void main(String[] args) { BeforeAdvice advice = new GreetingBeforeAdvice();
BeforeAdvice advice2 = new GreetingBeforeAdvice2();
Waiter waiter = new NaiveWaiter(); //Spring提供的代理工厂
ProxyFactory pf = new ProxyFactory();
pf.setInterfaces(waiter.getClass().getInterfaces());
pf.setOptimize(true); //设置代理目标
pf.setTarget(waiter); //为代理目标添加增强
pf.addAdvice(0,advice2);
pf.addAdvice(1,advice); //生成代理实例
Waiter waiterProxy = (Waiter)pf.getProxy();
waiterProxy.greetTo("nicholaslee");
waiterProxy.serverTo("nicholaslee");
}
输出:
我是第一个横切逻辑
How are you! Mr.nicholaslee
great to nicholaslee
我是第一个横切逻辑
How are you! Mr.nicholaslee
serving nicholaslee
我们还可以将以上代码更加优化一下,可以通过依赖注入来实例化:
<bean id="waiter" class="org.springframework.aop.framework.ProxyFactoryBean"
p:target-ref="targetWaiter"
p:proxyTargetClass="true">
<property name="interceptorNames">
<list>
<value>greetingAdvice</value>
<value>greetingAdvice2</value>
</list>
</property>
</bean>
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
Waiter waiter = (Waiter)ctx.getBean("waiter",Waiter.class);
waiter.greetTo("nicholaslee");
}
输出:
How are you! Mr.nicholaslee
我是第一个横切逻辑
great to nicholaslee
参数说明:
target:代理的目标对象;
proxyInterfaces:代理索要实现的接口,可以是多个接口,另一个别名属性是interfaces;
interceptorNames:需要织入目标对象的Bean列表,必须是实现了MethodInterceptor或者aop.Advisor的Bean,配置的顺序对应调用顺序;
singleton:返回的代理是否单实例,默认为单实例;
optimize:当设置为true的时候,强制使用CGLib代理;
proxyTargetClass:是否对类进行代理(而不是针对接口进行代理),设置为true后,使用CGLib代理;
这个时候我们使用了JDK代理技术,如果我们想使用CGLib代理,则可以更改参数:
p:proxyTargetClass="true"
因为CGLib代理创建代理慢,但是创建的代理对象效率非常高,所以比较适合singleton的代理;
下面我们看一个后置增强,org.springframework.aop.AfterReturningAdvice,表示在目标方法执行后试试增强:
package spring.aop.afteradvicedemo; import java.lang.reflect.Method; import org.springframework.aop.AfterReturningAdvice; public class GreetingAfterAdvice implements AfterReturningAdvice { @Override
public void afterReturning(Object arg0, Method arg1, Object[] arg2,
Object arg3) throws Throwable {
System.out.println("Please enjoy youself~");
} }
下面看一下环绕增强,org.aopalliance.intercept.MethodInterceptor,表示在目标方法执行前后实施增强:
package spring.aop.interceptoradvicedemo; import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation; public class GreetingInterceptor implements MethodInterceptor { @Override
public Object invoke(MethodInvocation arg0) throws Throwable {
Object[] args = arg0.getArguments();// 获取目标方法参数
String clientName = (String) args[0];
System.out.println("How are you:" + clientName);
Object obj = arg0.proceed();
System.out.println("just enjoy yourself");
return obj;
}
}
在环绕增强时,arg0.proceed()通过proceed反射调用目标实例相应的方法;
下面是异常抛出增强:org.springframework.aop.ThrowsAdvice,表示在目标方法抛出异常后实施增强;异常抛出增强最适合的应用场景是事务管理,当参与事务的某个Dao发生异常时,事务管理器就必须去回滚事务,下面看一个模拟的例子:
package spring.aop.throwsadvicedemo; import java.lang.reflect.Method; import org.springframework.aop.ThrowsAdvice; public class TransactionManager implements ThrowsAdvice { //ThrowsAdvice异常抛出增强接口没有定义任何方法,它只是一个标示接口
//在运行期Spring使用反射的机制自行判断,我们必须采用以下签名的增强方法
public void afterThrowing(Method method, Object[] args, Object target,Exception ex) throws Throwable{
System.out.println("---------------");
System.out.println("method:" + method.getName());
System.out.println("抛出异常:" + ex.getMessage());
System.out.println("成功回滚事务。");
} }
package spring.aop.throwsadvicedemo; import java.sql.SQLException; import org.springframework.aop.framework.ProxyFactory; public class UserServiceImpl { public void removeUser(int userId) {
System.out.println("模拟删除用户:" + userId);
throw new RuntimeException("运行异常。");
} public void addUser(int userId) {
System.out.println("添加用户" + userId);
throw new RuntimeException("数据库插入异常。");
} public static void main(String[] args) {
UserServiceImpl userService = new UserServiceImpl();
TransactionManager tran = new TransactionManager();
ProxyFactory pf = new ProxyFactory();
pf.setTarget(userService);
pf.addAdvice(tran);
UserServiceImpl user = (UserServiceImpl)pf.getProxy();
user.removeUser(0);
user.addUser(1);
}
}
输出:
模拟删除用户:0
---------------
method:removeUser
抛出异常:运行异常。
成功回滚事务。
添加用户1
---------------
method:addUser
抛出异常:数据库插入异常。
成功回滚事务。
也可以配置注入方式:
<bean id="throwsManager" class="spring.aop.throwsadvicedemo.TransactionManager" />
<bean id="throwsTarget" class="spring.aop.throwsadvicedemo.UserServiceImpl" />
<bean id="throwsAdvice" class="org.springframework.aop.framework.ProxyFactoryBean"
p:target-ref="throwsTarget"
p:proxyTargetClass="true"
p:singleton="false" >
<property name="interceptorNames">
<list>
<value>throwsManager</value>
</list>
</property>
</bean>
最后来看一下引介增强:org.springframework.aop.IntroductionInterceptor,表示在目标类中添加一些新的方法和属性;引介增强是一种比较特殊的增强类型,它不是在目标方法周围织入增强,而是为目标类创建新的方法和属性,所以引介增强的连接点是类级别的,而非方法级别的。通过引介增强,我们可以为目标类添加一个接口的实现,即原来目标类未实现某个接口,通过引介可以为目标类创建实现某接口的代理。
package spring.aop.introductionadvicedemo; import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.support.DelegatingIntroductionInterceptor; import spring.aop.demo1.PerformanceMonitor; public class ControllablePerformanceMonitor extends
DelegatingIntroductionInterceptor implements Monitorable { /**
*
*/
private static final long serialVersionUID = -5983845636084465442L; private ThreadLocal<Boolean> MonitorStatusMap = new ThreadLocal<Boolean>(); public Object invoke(MethodInvocation mi) throws Throwable {
Object obj = null;
// 通过判断其状态决定是否开启性能监控功能
if (MonitorStatusMap.get() != null && MonitorStatusMap.get()) {
PerformanceMonitor.begin(mi.getClass().getName() + "."
+ mi.getMethod().getName());
obj = super.invoke(mi);
PerformanceMonitor.end();
} else {
obj = super.invoke(mi);
}
return obj;
} @Override
public void setMonitorActive(boolean active) {
MonitorStatusMap.set(active);
} }
package spring.aop.introductionadvicedemo; public interface Monitorable { void setMonitorActive(boolean active); }
package spring.aop.introductionadvicedemo; import org.springframework.aop.framework.ProxyFactory; public class ForumService { public void removeUser(int userId) {
System.out.println("模拟删除用户:" + userId);
} public void addUser(int userId) {
System.out.println("添加用户" + userId);
} public static void main(String[] args) {
ForumService forumService = new ForumService();
ControllablePerformanceMonitor advice = new ControllablePerformanceMonitor();
ProxyFactory pf = new ProxyFactory();
pf.setInterfaces(Monitorable.class.getInterfaces());
pf.setTarget(forumService);
pf.setOptimize(true);
pf.addAdvice(advice);
ForumService forum = (ForumService)pf.getProxy();
Monitorable monitorAble =(Monitorable)forum;
monitorAble.setMonitorActive(true);
forum.removeUser(1); } }
输出:
begin monitor...
模拟删除用户:1
end monitor...
org.springframework.aop.framework.Cglib2AopProxy$CglibMethodInvocation.removeUser花费32毫秒
说明:
1. Monitorable monitorAble =(Monitorable)forum; 我们可以这么转换,说明返回的代理实例确实引入了Monitorable接口方法的实现;
2. pf.setInterfaces(Monitorable.class.getInterfaces()); 引介增强需要制定所实现的接口;
3. pf.setOptimize(true); 由于只能通过为目标类创建子类的方式生成音节增强的代理,所以必须选择CGLib代理;
spring AOP详解三的更多相关文章
- 【转载】Spring AOP详解 、 JDK动态代理、CGLib动态代理
Spring AOP详解 . JDK动态代理.CGLib动态代理 原文地址:https://www.cnblogs.com/kukudelaomao/p/5897893.html AOP是Aspec ...
- Spring AOP详解及简单应用
Spring AOP详解 一.前言 在以前的项目中,很少去关注spring aop的具体实现与理论,只是简单了解了一下什么是aop具体怎么用,看到了一篇博文写得还不错,就转载来学习一下,博文地址: ...
- 转:Spring AOP详解
转:Spring AOP详解 一.前言 在以前的项目中,很少去关注spring aop的具体实现与理论,只是简单了解了一下什么是aop具体怎么用,看到了一篇博文写得还不错,就转载来学习一下,博文地址: ...
- Spring AOP详解(转载)所需要的包
上一篇文章中,<Spring Aop详解(转载)>里的代码都可以运行,只是包比较多,中间缺少了几个相应的包,根据报错,几经百度搜索,终于补全了所有包. 截图如下: 在主测试类里面,有人怀疑 ...
- Spring Aop 详解二
这是Spring Aop的第二篇,案例代码很详解,可以查看https://gitee.com/haimama/java-study/tree/master/spring-aop-demo. 阅读前,建 ...
- [Spring学习笔记 5 ] Spring AOP 详解1
知识点回顾:一.IOC容器---DI依赖注入:setter注入(属性注入)/构造子注入/字段注入(注解 )/接口注入 out Spring IOC容器的使用: A.完全使用XML文件来配置容器所要管理 ...
- Spring系列(四):Spring AOP详解
一.AOP是什么 AOP(面向切面编程),可以说是一种编程思想,其中的Spring AOP和AspectJ都是现实了这种编程思想.相对OOP(面向过程编程)来说,提供了另外一种编程方式,对于OOP过程 ...
- Spring Aop 详解一
Aop 是一个编程思想,最初是一个理论,最后落地成了很多的技术实现. 我们写一个系统,都希望尽量少写点儿重复的东西.而很多时候呢,又不得不写一些重复的东西.比如访问某些方法的权限,执行某些方法性能的日 ...
- Spring AOP详解
一.前言 在以前的项目中,很少去关注spring aop的具体实现与理论,只是简单了解了一下什么是aop具体怎么用,看到了一篇博文写得还不错,就转载来学习一下,博文地址:http://www.cnbl ...
随机推荐
- Android(java)学习笔记113:Activity的生命周期
1.首先来一张生命周期的总图: onCreate():创建Acitivity界面 onStart():让上面创建的界面可见 onResume():让上面创建的界面 ...
- lca(最近公共祖先(离线))
转自大佬博客 : https://www.cnblogs.com/JVxie/p/4854719.html LCA 最近公共祖先 Tarjan(离线)算法的基本思路及其算法实现 首先是最近公共祖先 ...
- java abstraction and encapsulation
How is Abstraction different from Encapsulation? Abstraction happens at class level design. It resul ...
- js中异步方案比较完整版(callback,promise,generator,async)
JS 异步已经告一段落了,这里来一波小总结 1. 回调函数(callback) setTimeout(() => { // callback 函数体 }, 1000) 缺点:回调地狱,不能用 t ...
- java基础——Map集合
Map以键值对的形式存储数据,其中Map.entry,是Map的内部类,它用来描述Map中的键值对.Map是一个接口,HashMap是他的一个实现类 Map中有几个重要的方法: get(Object ...
- [转载]matlab图像处理为什么要归一化和如何归一化
matlab图像处理为什么要归一化和如何归一化,一.为什么归一化1. 基本上归一化思想是利用图像的不变矩寻找一组参数使其能够消除其他变换函数对图像变换的影响.也就是转换成唯一的标准形式以抵抗仿射变 ...
- 【主席树】bzoj1112: [POI2008]砖块Klo
数据结构划一下水 Description N柱砖,希望有连续K柱的高度是一样的. 你可以选择以下两个动作 1:从某柱砖的顶端拿一块砖出来,丢掉不要了. 2:从仓库中拿出一块砖,放到另一柱.仓库无限大. ...
- 初学redis,redis基本数据类型
String: 1. set key value 2. get key 3. del key 4. strlen key 5. getset key value 修改键值对 6. getrange ...
- C#基础-判断语句
switch语句 Console.WriteLine("请输入月份"); string strInput = Console.ReadLine(); switch(strInput ...
- 在windows7 32ibt安装MongoDB数据库的方法及连接失败解决方案
参考 https://www.cnblogs.com/cnblogs-jcy/p/6734889.html http://yunkus.com/mongodb-install-config-in-wi ...