Spring AOP使用动态代理技术在运行期织入增强的代码,两种代理机制包括:一是基于JDK的动态代理,另一种是基于CGLib的动态代理。之所以需要两种代理机制,很大程度上是因为JDK本身只提供接口的代理,而不支持类的代理。

 
1、带有横切逻辑的实例
ForumService:包含性能监视横切代码
package com.yyq.proxy;
public interface ForumService {
void removeTopic(int topicId);
void removeForum(int forumId);
}

ForumServiceImpl:实现类

package com.yyq.proxy;

public class ForumServiceImpl implements ForumService {
@Override
public void removeTopic(int topicId) {
PerformanceMonitor.begin("com.yyq.proxy.ForumServiceImpl.removeTopic");
System.out.println("模拟删除Topic记录:" + topicId);
try {
Thread.currentThread().sleep(20);
} catch (Exception e) {
throw new RuntimeException(e);
}
PerformanceMonitor.end();
}
@Override
public void removeForum(int forumId) {
PerformanceMonitor.begin("com.yyq.proxy.ForumServiceImpl.removeForum");
System.out.println("模拟删除Forum记录:" + forumId);
try {
Thread.currentThread().sleep(40);
}catch (Exception e){
throw new RuntimeException(e);
}
PerformanceMonitor.end();
}
}

PerformanceMonitor:性能监视的实现类

package com.yyq.proxy;

public class PerformanceMonitor {
private static ThreadLocal<MethodPerformance> performanceRecord = new ThreadLocal<MethodPerformance>();
public static void begin(String method) {
System.out.println("begin monitor...");
MethodPerformance mp = new MethodPerformance(method);
performanceRecord.set(mp);
}
public static void end(){
System.out.println("end monitor...");
MethodPerformance mp = performanceRecord.get();
mp.printPerformance();
}
}

MethodPerformance:记录性能监视信息

package com.yyq.proxy;

public class MethodPerformance {
private long begin;
private long end;
private String serviceMethod;
public MethodPerformance(String serviceMethod){
this.serviceMethod = serviceMethod;
this.begin = System.currentTimeMillis();
}
public void printPerformance(){
end = System.currentTimeMillis();
long elapse = end - begin;
System.out.println(serviceMethod + "花费" + elapse + "毫秒。");
}
}

TestProxy.testForumService测试方法:

 @Test
public void testForumService(){
ForumService forumService = new ForumServiceImpl();
forumService.removeForum(10);
forumService.removeTopic(1012);
}
输出结果:
begin monitor...
模拟删除Forum记录:10
end monitor...
com.yyq.proxy.ForumServiceImpl.removeForum花费40毫秒。
begin monitor...
模拟删除Topic记录:1012
end monitor...
com.yyq.proxy.ForumServiceImpl.removeTopic花费20毫秒。
 
  存在的问题:当某个方法需要进行性能监视,就必须调整方法代码,在方法体前后分别添加上开启性能监视和结束性能监视的代码。这些非业务逻辑的性能监视代码破坏了ForumServiceImpl业务逻辑的纯粹性。所以需要通过代理的方式,将业务类方法中开启和结束性能监视的这些横切代码从业务类中完全移除,并通过JDK动态代理技术或CGLib动态代理技术将横切代码动态织入到目标方法的相应位置。
 
2、JDK 动态代理
  JDK的动态代理主要涉及到java.lang.reflect包中的两个类:Proxy和InvocationHandler。其中InvocationHandler是一个接口,可以通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态将横切逻辑和业务逻辑编织在一起。而Proxy利用invocationHandler动态创建一个符合某一接口的实例,生成目标类的代理对象。
ForumServiceImpl:移除性能监视横切代码

package com.yyq.proxy;
public class ForumServiceImpl implements ForumService {
public void removeTopic(int topicId) {
System.out.println("模拟删除Topic记录:" + topicId);
try {
Thread.currentThread().sleep(20);
} catch (Exception e) {
throw new RuntimeException(e);
}
PerformanceMonitor.end();
} public void removeForum(int forumId) {
System.out.println("模拟删除Forum记录:" + forumId);
try {
Thread.currentThread().sleep(40);
}catch (Exception e){
throw new RuntimeException(e);
}
PerformanceMonitor.end();
}
}

PerformanceHandler:性能监视横切代码

package com.yyq.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class PerformanceHandler implements InvocationHandler {
private Object target; public PerformanceHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
PerformanceMonitor.begin(target.getClass().getName() + "." + method.getName());
Object obj = method.invoke(target, args);
PerformanceMonitor.end();
return obj;
}
}
   method.invoke()语句通过Java反射机制间接调用目标对象的方法,这样InvocationHandler的invoke()方法就将横切逻辑点和业务类方法的业务逻辑代码编织到一起了。
TestProxy.testForumService2:创建代理实例

 @Test
public void testForumService2(){
ForumService target = new ForumServiceImpl();
PerformanceHandler handler = new PerformanceHandler(target);
ForumService proxy = (ForumService) Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),handler);
proxy.removeTopic(10);
proxy.removeForum(1012);
}
  通过Proxy的newProxyInstance()静态方法为编织了业务类逻辑和性能监视逻辑的handler创建一个符合ForumService接口的代理实例。该方法的第一个入参为类加载器;第二个入参为创建代理实例所需要实现的一组接口;第三个参数是整合了业务逻辑和横切逻辑的编织器对象。
输出结果:
begin monitor...
模拟删除Topic记录:10
end monitor...
com.yyq.proxy.ForumServiceImpl.removeTopic花费22毫秒。
begin monitor...
模拟删除Forum记录:1012
end monitor...
com.yyq.proxy.ForumServiceImpl.removeForum花费41毫秒。
 
3、CGLib动态代理
  使用JDK创建代理有一个限制,即它只能为接口创建代理实例。而CGLib采用非常底层的字节码技术,可以为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,并顺势织入横切逻辑。
CglibProxy:为任何类创建织入性能监视横切逻辑代理对象的代理创建器

package com.yyq.proxy;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CglibProxy implements MethodInterceptor {
private Enhancer enhancer = new Enhancer();
public Object getProxy(Class clazz) {
enhancer.setSuperclass(clazz); //设置需要创建子类的
enhancer.setCallback(this);
return enhancer.create(); //通过字节码技术动态创建子类实例
}
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { //拦截父类所有方法的调用
PerformanceMonitor.begin(obj.getClass().getName() + "." + method.getName());
Object result = proxy.invokeSuper(obj, args); //通过代理类调用父类中的方法
PerformanceMonitor.end();
return result;
}
}
  intercept(Object obj, Method method, Object[] args, MethodProxy proxy)是CGLib定义的Interceptor接口的方法,它拦截所有目标类方法的调用,obj表示目标类的实例;method为目标类方法的反射对象;args为方法的动态入参;而proxy为代理类实例。    
TestProxy.testForumService3:测试Cglib创建的代理类

 @Test
public void testForumService3(){
CglibProxy proxy = new CglibProxy();
ForumServiceImpl forumService = (ForumServiceImpl)proxy.getProxy(ForumServiceImpl.class);
forumService.removeForum(10);
forumService.removeTopic(1012);
}
输出结果:
begin monitor...
模拟删除Forum记录:10
end monitor...
com.yyq.proxy.ForumServiceImpl$$EnhancerByCGLIB$$70ce1267.removeForum花费80毫秒。
begin monitor...
模拟删除Topic记录:1012
end monitor...
com.yyq.proxy.ForumServiceImpl$$EnhancerByCGLIB$$70ce1267.removeTopic花费20毫秒。
 
4、代理知识小结
    Spring AOP的底层就是通过使用JDK动态代理或CGLib动态代理技术为目标Bean织入横切逻辑。但是以上实现方式存在三个明显需要改进的地方:
    1)目标类的所有方法都添加了性能监视横切逻辑,而有时,这并不是我们所期望的,我们可能只希望对业务类中的某些特定方法添加横切逻辑;
    2)我们通过硬编码的方式指定了织入横切逻辑的织入点,即在目标类业务方法的开始和结束前织入代码;
    3)我们手工编写代码实例的创建过程,为不同类创建代理时,需要分别编写相应的程序代码,无法做到通用。
    Spring AOP的主要工作围绕以上三点展开:Spring AOP通过Pointcut(切点)指定在哪些类的哪些方法上织入横切逻辑,通过Advice(增强)描述横切逻辑和方法的具体织入点(方法前、方法后、方法的两端等)。此外,Spring通过Advisor(切面)将Pointcut和Advice两者组装起来。有了Advisor的信息,Spring就可以利用JDK或CGLib的动态代理技术采用统一的方式为目标Bean创建织入切面的代理对象了。
   

Spring AOP基础知识的更多相关文章

  1. [Spring框架]Spring AOP基础入门总结一.

    前言:前面已经有两篇文章讲了Spring IOC/DI 以及 使用xml和注解两种方法开发的案例, 下面就来梳理一下Spring的另一核心AOP. 一, 什么是AOP 在软件业,AOP为Aspect ...

  2. [Spring框架]Spring AOP基础入门总结二:Spring基于AspectJ的AOP的开发.

    前言: 在上一篇中: [Spring框架]Spring AOP基础入门总结一. 中 我们已经知道了一个Spring AOP程序是如何开发的, 在这里呢我们将基于AspectJ来进行AOP 的总结和学习 ...

  3. CgLib动态代理学习【Spring AOP基础之一】

    如果不了解JDK中proxy动态代理机制的可以先查看上篇文章的内容:Java动态代理学习[Spring AOP基础之一] 由于Java动态代理Proxy.newProxyInstance()的时候会发 ...

  4. 4-1 Spring框架基础知识

    Spring框架基础知识 1.Spring 框架作用 主要解决了创建对象和管理对象的问题. 自动装配机制 2.Spring 框架 (Spring容器,JavaBean容器,Bean容器,Spring容 ...

  5. 【Spring】AOP实现原理(一):AOP基础知识

    AOP相关概念 在学习AOP实现原理之前,先了解下AOP相关基础知识. AOP面向切面编程,它可以通过预编译方式或者基于动态代理对我们编写的代码进行拦截(也叫增强处理),在方法执行前后可以做一些操作, ...

  6. Spring框架基础知识

    本人博客文章网址:https://www.peretang.com/basic-knowledge-of-spring-framework/ Spring框架简介 Spring , 一个开源的框架 , ...

  7. 【AOP】Spring AOP基础 + 实践 完整记录

    Spring AOP的基础概念 ============================================================= AOP(Aspect-Oriented Pr ...

  8. Java动态代理学习【Spring AOP基础之一】

    Spring AOP使用的其中一个底层技术就是Java的动态代理技术.Java的动态代理技术主要围绕两个类进行的 java.lang.reflect.InvocationHandler java.la ...

  9. Spring AOP基础概念及自定义注解式AOP初体验

    对AOP的理解开始是抽象的,看到切点的匹配方式其实与正则表达式性质大致一样就基本了解AOP是基本是个什么作用了.只是整个概念更抽象,需要具化理解.下图列表是AOP相关概念解释,可能也比较抽象^_^ 比 ...

随机推荐

  1. Unity3D中灵活绘制进度条

    有时我们需要在Unity3D中绘制进度条,如:           或        如果使用4.6版本以下的unity绘制环形的进度条可能需要费点劲.我搜到的大多数方法都是用NGUI插件,但有时只是 ...

  2. mono for andorid 引用外部的dll问题

    这几天玩mono for android 心想,咱c#终于可以开发移动应用了,心里那个美啊------------ 先开发个什么呢,想起来前几天看到微博里一个用姓名笔画来算两个人关系的小测试,开发个这 ...

  3. JAVA 显示图片的简单源码 分类: Java Game 2014-08-14 10:10 77人阅读 评论(0) 收藏

    此代码的原理就是用JLabel来加载图片,再将JLabel放入JFrame中, package com.mywork; import javax.swing.ImageIcon; import jav ...

  4. flex Chrome flash调试时 出现Shockwave flash has crashed的解决办法

    在Chrome中输入:chrome://plugins/     PPAPI的Flash Player停用. 使用NPAPI的Flash player. 这里好像没有显示是Debug版本. 但是我在调 ...

  5. IE浏览器上传文件时本地路径变成”C:\fakepath\”的问题【转】

    转自:http://www.iefans.net/ie-shangchuan-bendi-lujing-fakepath/ 在使用<input id="file_upl" t ...

  6. maven 常用5个命令

    1. mvn help:describe 你是否因为记不清某个插件有哪些goal而痛苦过,你是否因为想不起某个goal有哪些参数而苦恼,那就试试这个命令吧,它会告诉你一切的. 参数: 1. -Dplu ...

  7. 13test07;字符排序,去重,三三输出

    #include<iostream> #include<string> using namespace std; void buddle(char*,int);//对输入字符的 ...

  8. RDLC报表BC4000错误

    如果RDLC报表报 BC4000错误,那是因为在矩阵外上面添加了文本框的缘故,导致系统不能读到RDLC报表表体,告诉你报表没有定义: 解决办法: 把表头文字放入矩阵中.

  9. Redis与Memcached的incr/decr差异对比

    目前广泛使用的分布式缓存Redis和Memcached均支持对整数型Value值的增减,对应到具体命令中就是incr和decr命令. incr/decr是原子性操作(memcached 1.2.4及以 ...

  10. POJ 1488

    #include <iostream> #include <string> using namespace std; int main() { string s; int i; ...