IT干货网

Java动态编程---动态代理

luoye 2022年03月12日 编程设计 176 0

java中动态编程用到的技术有:反射(动态代理),javassist和ASM,这几种动态编程方法相比较,在性能上Javassist高于反射,但低于ASM,因为Javassist增加了一层抽象。在实现成本上Javassist和反射都很低,而ASM由于直接操作字节码,相比Javassist源码级别的api实现成本高很多。几个方法有自己的应用场景,比如Kryo使用的是ASM,追求性能的最大化。而NBeanCopyUtil采用的是Javassist,在对象拷贝的性能上也已经明显高于其他的库,并保持高易用性。实际项目中推荐先用Javassist实现原型,若在性能测试中发现Javassist成为了性能瓶颈,再考虑使用其他字节码操作方法做优化。

下面以代码的方式实现一个动态代理。

目标:

       在调用持久层UserDAO将业务数据写入数据库的前后加入日志的功能。代码结构如下图所示:

User:

package dynamicproxy.model; 
 
public class User { 
    public User(String name, int age) { 
        super(); 
        this.name = name; 
        this.age = age; 
    } 
    private String name; 
    private int age; 
    public String getName() { 
        return name; 
    } 
    public void setName(String name) { 
        this.name = name; 
    } 
    public int getAge() { 
        return age; 
    } 
    public void setAge(int age) { 
        this.age = age; 
    } 
}

IUserDAO:

package dynamicproxy.dao; 
 
import dynamicproxy.model.User; 
 
public interface IUserDAO { 
 
    String addUser(User user); 
 
    String updateUser(User user); 
}

UserDAO:

package dynamicproxy.dao; 
 
import dynamicproxy.model.User; 
 
public class UserDAO implements IUserDAO { 
    public String addUser(User user) { 
        System.out.println("开始向数据库中写入数据..."); 
        return String.format("添加用户[%s]成功", user.getName()); 
    } 
 
    public String updateUser(User user) { 
        System.out.println("开始向数据库中写入数据..."); 
        return String.format("修改用户[%s]成功", user.getName()); 
    } 
}

关键的InvocationHandler类LogInterceptor:

package dynamicproxy.interceptor; 
 
import java.lang.reflect.InvocationHandler; 
import java.lang.reflect.Method; 
 
public class LogInterceptor implements InvocationHandler { 
    private Object target; 
 
    /* 
     * 在执行指定方法之前调用 
     */ 
    private void beforeMethod(Method method) { 
        System.out.println(String.format("日志:用户开始执行[%s]方法...", method.getName())); 
    } 
 
    /* 
     * 在执行指定方法之后调用 
     */ 
    private void afterMethod(Method method) { 
        System.out.println(String.format("日志:用户执行[%s]方法完成.", method.getName())); 
    } 
 
    public LogInterceptor(Object target) { 
        this.target = target; 
    } 
 
    @Override 
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 
        beforeMethod(method);// 加入日志处理逻辑 
        Object result = method.invoke(target, args);// 调用被代理对象的指定method方法 
        afterMethod(method);// 加入日志处理逻辑 
        return result; 
    } 
 
}

测试代码:

package dynamicproxy; 
import java.lang.reflect.InvocationHandler; 
import java.lang.reflect.Proxy; 
import org.junit.Test; 
import dynamicproxy.dao.IUserDAO; 
import dynamicproxy.dao.UserDAO; 
import dynamicproxy.interceptor.LogInterceptor; 
import dynamicproxy.model.User; 
 
public class UserDAOTest { 
 
    @Test 
    public void test() { 
        IUserDAO dao = new UserDAO(); 
        InvocationHandler logInterceptor = new LogInterceptor(dao);// InvocationHandler 
        dao = (IUserDAO) Proxy.newProxyInstance(dao.getClass().getClassLoader(), dao.getClass().getInterfaces(), 
                logInterceptor); 
        System.out.println(dao.addUser(new User("franson", 21))); 
        System.out.println(System.lineSeparator()); 
        System.out.println(dao.updateUser(new User("franson", 21))); 
    } 
 
}

运行JUnit测试结果如下:

日志:用户开始执行[addUser]方法... 
开始向数据库中写入数据... 
日志:用户执行[addUser]方法完成. 
添加用户[franson]成功 
 
 
日志:用户开始执行[updateUser]方法... 
开始向数据库中写入数据... 
日志:用户执行[updateUser]方法完成. 
修改用户[franson]成功

这样就达到了我们在不修改原有业务逻辑代码的情况下实现添加日志的功能。


评论关闭
IT干货网

微信公众号号:IT虾米 (左侧二维码扫一扫)欢迎添加!