存在问题

在我们开发过程中,会遇到很多大量改动代码的情况,因为需求在不断地变化不定,比如字段的更新,日志的处理,格式的变换等等一系列问题,少量的还可以使用替换等操作来改动,但是有可能改动的代码量过于庞大,替换似乎就不太那么可能了。

所以,加入一种切面的思想,就是在不改动原有的代码的基础上,实现整体的功能,比如:现在我所有的接口(1万个接口)已经开发好了,但是现在想在每个接口里面加入操作日志的记录,不可能手动去更改每个接口,那该如何操作呢?如下。

原来的具体实现

有这样一个接口

public interface UserService {
    User getUser();
    Integer addUser(User user);
    Integer updateUser(User user);
    Integer deleteUser(Integer id);
}

这是它对应的实现类

public class UserServiceImpl implements UserService {

    @Override
    public Integer addUser(User user) {
	addLog();//我们在这里使用增加日志的方法,但是懊恼的是,我们有很多类似addUser()的方法都要调用它。
        System.out.println("新增了一个用户");
        return 1;
    }

    //其余重写方法大同小异


    //增加日志方法
    public void addLog(){
	System.out.println("【log】执行了新增方法!");
    }  

}

然后调用的时候通过实现类来进行调用

public class UserController {
    public static void main(String[] args) {
        UserServiceImpl userService = new UserServiceImpl();
        userService.addUser(new User());
    }
}

输出

【log】执行了新增方法!
新增了一个用户

这是原有的方式实现一个接口的实现和调用,然后使用新的方式开始对每一个接口增加日志记录的操作

新的实现方式(使用动态代理)

原理

  • 动态代理的角色和静态代理的一样 .
  • 动态代理的代理类是动态生成的 . 静态代理的代理类是我们提前写好的
  • 动态代理分为两类 : 一类是基于接口动态代理 , 一类是基于类的动态代理
    • 基于接口的动态代理----JDK动态代理
    • 基于类的动态代理--cglib
    • 现在用的比较多的是 javasist 来生成动态代理 . 百度一下javasist
    • 我们这里使用JDK的原生代码来实现,其余的道理都是一样的

JDK的动态代理需要了解两个类

核心 : InvocationHandlerProxy,打开JDK帮助文档看看

  • InvocationHandler:调用处理程序

InvocationHandler.png

  • invoke方法
    invoke.png

  • Proxy:代理类
    Proxy.png

  • newProxyInstance 方法
    newProxyInstance.png

我们需要定义一个动态代理类

public class ProxyInvocationHandler implements InvocationHandler {
    private Object target;

    //生成代理类
    public Object getProxy(){
        return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(),this);
    }
    public void setTarget(Object target){
        this.target = target;
    }

    //proxy:代理类
    //method:代理类的调用处理程序的方法对象
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        addLog(method.getName());//在这个方法中,在我们需要真正执行的方法前后我们可以使用这样的方式来实现。

        Object result = method.invoke(target,args);//这是执行的具体我们调用的方法,也就是下面调用中的addUser()方法。

        showLog(method.getName());

        return result;//这里的result也就是我们真正执行方法addUser()返回的结果。
    }

    //如果我们再要想加入其他的方法,直接在这个代理类中添加即可,无需再每一个接口上面进行修改

    //具体要新增的切入方法
    public void addLog(String methodName){
        System.out.println("执行了"+methodName+"方法!");
    }
    //具体要新增的切入方法
    public void showLog(String methodName){
        System.out.println("打印一下:"+methodName+"方法!");
    }
}

如何调用

public class UserController {
    public static void main(String[] args) {
        //真实的对象
        UserServiceImpl userService = new UserServiceImpl();
        ProxyInvocationHandler pih = new ProxyInvocationHandler();
        //设置要代理的对象
        pih.setTarget(userService);
        //动态生成代理类
        UserService prozy = (UserService) pih.getProxy();
        prozy.addUser(new User());
    }
}

输出

执行了addUser方法!
新增了一个用户
打印一下:addUser方法!

当我们再需要更改对接口的大量更改就可是使用这样的方式来实现,但是这样写起来似乎也比较麻烦,还有种通过Spring注解的方式实现,前往查看