用动态代理实现AOP

前言:

我们都知道AOP是Spring的特征之一,通过AOP的方式可以在运行时动态地将代码切入到类的指定方法、指定位置上。

那么,AOP是怎么实现的呢?

Spring的AOP是通过动态代理来实现的。下面我们来学习一下。

什么是代理?

    先来理解一下,什么是代理?想想现实生活中的中介,就类似于代理的作用。假设我是一个保姆,现在去了家政中心进行登记我的信息,那么如果有人在家政中心找保姆的话,我就又了找到户主的机会。在这个过程中,家政中心其实就是你的代理,那么你的代理在中间干了一件什么事呢?当然是抽取了一部分费用。

    回过头来,在看一下我们的AOP(面向切面编程),这里的切面实际上就类似于家政中心,拦截到切点,执行相对应的advise(建言)。而执行的advise(建言)的操作类似于家政中心抽取部分费用的操作。是不是很形象呢?

    理解了代理的概念,帮助我们理解下面的代理类的概念。

Spring实现AOP

我们已经知道,Spring是通过动态代理来实现AOP的。它的实现机制有两种:基于JDK实现和基于CGLIB包实现。

先来试一下吧!

基于JDK实现动态代理

  JDK的代理方式主要就是通过反射动态编译来实现的,用到了两个重要的类或接口:Proxy(类)和InvocationHandler(接口)。

  首先我们定义如下类或接口:

UserService:类,需要被代理的类。(相当于保姆)

IUserService:接口,被代理类实现的接口。

LogInvocationHandler:类,产生代理类的类,织入代码并生成代理类。

代码如下:

IUserService接口:

package my.spring.service;

public interface IUserService {
    void say();
}

UserService类:

package my.spring.service.impl;

import my.spring.service.IUserService;
import org.springframework.stereotype.Service;

@Service
public class UserService implements IUserService {
    @Override
    public void say() {
        System.out.println("我想找工作!");
    }
}

LogInvocationHandler类:

package my.spring.invocationHandler;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class LogInvocationHandler implements InvocationHandler {

    private Object target;      // 目标对象

    // 构造方法
    public LogInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 编写指定切点处需要切入的逻辑
        if("say".equals(method.getName())) {
            System.out.println("我要抽取部分的费用!");
        }
        // 执行原有的逻辑
        Object recv = method.invoke(target,args);
        return null;
    }

    public Object getTarget() {
        return target;
    }

    public void setTarget(Object target) {
        this.target = target;
    }
}

运行:

package my.spring;

import my.spring.invokeHandler.LogInvocationHandler;
import my.spring.service.IUserService;
import my.spring.service.impl.UserService;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class Main {

    public static void main(String[] args) {
        // 被代理类实现的接口集合(如果别代理类实现了多个接口,必须都在这里定义。)
        Class[] proxyInterface = new Class[]{IUserService.class};
        // 获得一个类加载器
        ClassLoader loader = IUserService.class.getClassLoader();
        // 需要传入被代理的类的实例,生成织入器
        InvocationHandler handler = new LogInvocationHandler(new UserService());


        // 创建代理类
        IUserService userServiceProxy = (IUserService)          Proxy.newProxyInstance(loader,proxyInterface,handler);
        userServiceProxy.say();


    }
}

运行结果:


结合代码讲解

   前三个类已经说过了,主要是main()方法里面代码。变量proxyInterface、loader和handler都是下面的newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)的参数。

    所以,下面重点讲解一下这个方法。

    static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 。


Proxy:这个类的作用是用来动态的创建一个代理对象的类。

newProxyInstance方法:Proxy类静态方法,有3个参数,返回Object。

参数一:ClassLoader loader 类加载器,类加载器是负责加载类的对象。

参数二:Class<?>[] interfaces 被代理的类实现的接口,如果有多个,都必须在这里定义。

参数三:InvocationHandler h 是代理实例的调用处理程序实现的接口。

 

另外,处理切入编辑的类,即LogInvocationHandler必须实现InvocationHandler。

总结

    动态代理在运行期通过接口动态生成代理类,这为其带来了一定的灵活性,但这个灵活性却带来了两个问题:

    第一,被代理的类必须实现一个接口,如果没实现接口会抛出一个异常

    第二,性能影响,因为动态代理是使用反射机制实现的,首先反射肯定比直接调用要慢,其次使用反射大量生成类文件可能引起full gc,因为字节码文件加载后会存放在JVM运行时方法区(或者叫永久代、元空间)中,当方法区满时会引起full gc,所以当你大量使用动态代理时,可以将永久代设置大一些,减少full gc的次数

(第二点,由于小编对JAVA JVM知识欠缺,期待以后提高。)

基于cglib实现动态代理

    Cglib:Code Generation Library,是一个强大的,高性能,高质量的Code生成类库。它可以在运行期扩展Java类与实现Java接口。由于cglib封装了asm,所以可以在运行期动态生成新的class。

    需要注意的是:基于JDK实现动态代理,要求被代理的类必须实现至少一个接口,而基于cglib是没有要求的。

首先,我们需要配置pom.xml,引入cglib依赖

<!-- cglib依赖 -->
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>2.2.2</version>
</dependency>

同样的,我们需要编写需要被代理的类,插入切面逻辑的类,以及运行类。

UserService2类:(需要被代理的)

package my.spring.service;

import org.springframework.stereotype.Service;

@Service
public class UserService2 {

    public void say2() {
        System.out.println("我想找工作222!");
    }

}

LogCglib类:(生产代理类,并插入切面逻辑)

package my.spring.invokeHandler;

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 LogCglib implements MethodInterceptor {

    private Object targetObject;      // 目标对象

    // 创建代理对象
    public Object getInstance(Object target) {
        this.targetObject = target;
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(targetObject.getClass());
        // 回调方法,设置要代理的拦截器
        enhancer.setCallback(this);
        // 创建代理对象
        Object proxy = enhancer.create();
        return proxy;
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        // 编写指定切点处需要切入的逻辑
        if("say2".equals(method.getName())) {
            System.out.println("----before");
        }
        // 执行原有的逻辑
        methodProxy.invoke(targetObject,objects);
        return null;
    }
}

运行:

package my.spring;

import my.spring.invokeHandler.LogCglib;
import my.spring.service.UserService2;

public class Main {

    public static void main(String[] args) {

        LogCglib cglib = new LogCglib();
        UserService2 proxy = (UserService2)cglib.getInstance(new UserService2());
        proxy.say2();

    }
}

运行结果:



结合代码讲解

   LogCglib类的getInstance()方法:

   Enhancer可以用来动态的生成一个类,这个类可以继承指定的一个类,实现指定的一些接口。

   同时,Enhancer在生成一个类之前需要指定一个Callback,当类方法调用时,方法的执行被分配给这个Callback

   通过Enhancer类的方法,设置要扩展的父类、回调方法设置拦截器(拦截器即intercept()方法),创建返回代理实例。

   这个方法可以单独提出来,作为产生代理类的工厂。


 public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {return null;}

第一个参数:需要被代理的类实例;

第二个参数:调用的方法;

第三个参数:调用的方法需要的参数;

第四个参数:调用的方法的代理。

两种方式的对比总结

原理区别:

    java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。


Sping实现AOP:

1、如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP

2、如果目标对象实现了接口,可以强制使用CGLIB实现AOP

3、如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换


JDK动态代理和CGLIB字节码生成的区别?
① JDK动态代理只能对实现了接口的类生成代理,而不能针对类
② CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法。因为是继承,所以该类或方法最好不要声明成final

AOP的应用场景

Authentication 权限

Caching 缓存

Context passing 内容传递

Error handling 错误处理

Lazy loading 懒加载

Debugging 调试

logging, tracing, profiling and monitoring 记录跟踪 优化 校准

Performance optimization 性能优化

Persistence 持久化

Resource pooling 资源池

Synchronization 同步

Transactions 事务

这里,提供一些应用场景,应用在项目中,多多练习!

参考链接:

http://blog.csdn.net/yakoo5/article/details/9099133/

https://www.cnblogs.com/xiaoxiao7/p/6057724.html

http://blog.csdn.net/u012291108/article/details/52973710

http://www.360doc.com/content/14/0618/11/454045_387708106.shtml

 

(欢迎指导,共同进步)


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值