JAVA动态代理实现简单的AOP框架

- 背景

作业:

  1. 使用Java动态代理实现一个简单的AOP框架

  2. 定义两个注解 @Transaction ,@Log

  3. 定义一个接口Aspect,它有两个接口方法:before(); after();

  4. 实现连个Apect实现类:TransactionAspect,LogAspect;
    TransactionAspect:before():输出事务开始,after():输出事务结束
    LogAspect: before():输出调用前, after():输出调用后

  5. 实现测试类,这个类里有四个方法:

    – 第一个方法:标有@Transaction
    – 第二个方法:标有@Log
    – 第三个方法:标有@Transaction 和@Log
    – 第四 个方法:没有任何注个解

  6. 写main方法,使用aop框架执行测试类方法,要求:
    有@Transaction 方法执行的时候, TransactionAspect的两个方法被执行;
    有@Log方法执 行的时候, LogAspect的两个方法被执行。


- 代理的概念和作用

  1. 为其他对象提供一种代理以控制对这个对象的访问。 在某些情况下,一个客户不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
    现实中的例子:
    歌星或者明星都有一个自己的经纪人,这个经纪人就是他们的代理人,当我们需要找明星表演时,不能直接找到该明星,只能是找明星的代理人

总结: 在这里代理对象存在的价值:主要用于拦截对真实业务对象的访问。

  1. 为已存在的多个具有相同接口的目标类的各个方法增加一些系统功能,例如,异常处理、日志、计算方法的运行时间、事务管理、等等。
    例如:
    现实生活中,你有一个蛋糕机(接口),这个蛋糕机可以制造出草莓蛋糕、奶油蛋糕等多种蛋糕(实现了同一接口的类)。突然有一天,一位客人想在草莓蛋糕中加入其他的水果。但是你的蛋糕机只能生产出草莓蛋糕,现实中也不能因为一个需求就把蛋糕机(接口)修改了,这个时候就可以创建一个实现了蛋糕机的代理类,在代理类中添加相应的功能。

总结:
代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。简单的说就是,我们在访问实际对象时,是通过代理对象来访问的,代理模式就是在访问实际对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途。


- 静态代理和动态代理

根据加载被代理类的时机不同,将代理分为静态代理和动态代理。如果我们在代码编译时就确定了被代理的类是哪一个,那么就可以直接使用静态代理;如果不能确定,那么可以使用类的动态加载机制,在代码运行期间加载被代理的类这就是动态代理,比如RPC框架和Spring AOP机制。


- 静态代理的实现

静态代理: 由程序员创建或特定工具自动生成源代码,也就是在编译时就已经将接口,被代理类,代理类等确定下来。在程序运行之前,代理类的.class文件就已经生成。

这里实现一个用户管理类UserManagerImp,实现接口UserManager的两个方法,添加用户addUser()和删除用户deleteUser()。也可通过第三方代理类UserManagerImpProxy(实现了同样的UserManager接口)来代理执行添加用户和删除用户的操作。

首先创建一个UserManager接口。这个接口就是被代理类(UserManagerImp)和代理类(UserManagerImpProxy)的公共接口,它们都有添加用户和删除用户的行为。

package com.glodon.hongq.day01;

public interface UserManager {
    void addUser();
    void delUser();
    String findUser();
}

UserManagerImp实现UserManager接口:

package com.glodon.hongq.day01;

public class UserManagerImp implements UserManager{
    @Override
    public void addUser() {
        System.out.println("UserManagerImp.addUser");
    }

    @Override
    public void delUser() {
        System.out.println("UserManagerImp.delUser");
    }

    @Override
    public String findUser() {
        return "UserManagerImp.UserName";
    }
}

UserManagerImpProxy类也实现了UserManager接口,同时拥有一个UserManger引用,通过这个能够代理实现UserManager接口的任何类

package com.glodon.hongq.day01;

public class UserManagerImpProxy implements UserManager {
    private UserManager userManager;

    public UserManagerImpProxy(UserManager userManager){
        this.userManager = userManager;
    }

    @Override
    public void addUser() {
        System.out.println("我是代理商UserManagerImpProxy");
        System.out.println("start->addUser");
        userManager.addUser();
        System.out.println("success->addUser");
    }

    @Override
    public void delUser() {
        userManager.delUser();
    }

    @Override
    public String findUser() {
        return userManager.findUser();
    }
}

测试类

package com.glodon.hongq.day01;

public class ClientProxy {
    public static void main(String[] args){
        UserManager u = new UserManagerImpProxy(new UserManagerImp());

        // UserManagerImpProxy对象代理了UserManagerImp执行了addUser()方法
        u.addUser();
    }
}

运行结果:

在这里插入图片描述
说明:
代理模式最主要的就是有一个公共接口(UserManager),一个具体的类(UserManagerImp),一个代理类(UserManagerImpProxy),代理类持有具体类的实例,代为执行具体类实例方法。上面说到,代理模式就是在访问实际对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途。这里的间接性就是指不直接调用实际对象的方法,那么我们在代理过程中就可以加上一些其他用途。就这个例子来说,在执行addUser()方法的前后都添加了相应的说明,如下图所示:

JAVA动态代理实现简单的AOP框架_第1张图片

可以看到,只需要在代理类中运行addUser()之前,执行其他操作就可以了。这种操作,也是使用代理模式的一个很大的优点。最直白的就是在Spring中的面向切面编程(AOP),我们能在一个切点之前执行一些操作,在一个切点之后执行一些操作,这个切点就是一个个方法。这些方法所在类肯定就是被代理了,在代理过程中切入了一些其他操作。

以下结构图是一个典型的静态代理模式:
JAVA动态代理实现简单的AOP框架_第2张图片
其中:Subject角色负责定义RealSubject和Proxy角色应该实现的接口;RealSubject角色用来真正完成业务服务功能;Proxy角色负责将自身的Request请求,调用realsubject 对应的request功能来实现业务功能,自己不真正做业务。

当在代码阶段规定这种代理关系,Proxy类通过编译器编译成class文件,当系统运行时,此class已经存在了。这种静态的代理模式固然在访问无法访问的资源,增强现有的接口业务功能方面有很大的优点,但是大量使用这种静态代理,会使我们系统内的类的规模增大,并且不易维护;并且由于Proxy和RealSubject的功能 本质上是相同的,Proxy只是起到了中介的作用,这种代理在系统中的存在,导致系统结构比较臃肿和松散。

为了解决这个问题,就有了动态地创建Proxy的想法:在运行状态中,需要代理的地方,根据Subject 和RealSubject,动态地创建一个Proxy,用完之后,就会销毁,这样就可以避免了Proxy 角色的class在系统中冗杂的问题了。


- 动态代理

动态代理:代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理。

动态代理模式的结构跟上面的静态代理模式稍微有所不同,多引入了一个InvocationHandler角色
JAVA动态代理实现简单的AOP框架_第3张图片
有上图可以看出,代理类处理的逻辑很简单:在调用某个方法前及方法后做一些额外的业务。换一种思路就是:在触发(invoke)真实角色的方法之前或者之后做一些额外的业务。那么,为了构造出具有通用性和简单性的代理类,可以将所有的触发真实角色动作交给一个触发的管理器,让这个管理器统一地管理触发。这种管理器就是Invocation Handler。

动态代理工作的基本模式就是将自己的方法功能的实现交给 InvocationHandler角色,外界对Proxy角色中的每一个方法的调用,Proxy角色都会交给InvocationHandler来处理,而InvocationHandler则调用具体对象角色的方法。如下图所示:
JAVA动态代理实现简单的AOP框架_第4张图片
JDK的动态代理创建机制----通过接口
1. 获取 RealSubject上的所有接口列表;
2. 确定要生成的代理类的类名,默认为:com.sum.$ProxyXXXX ;
3. 根据需要实现的接口信息,在代码中动态创建该Proxy类的字节码;
4 . 将对应的字节码转换为对应的class 对象;
4. 创建InvocationHandler 实例handler,用来处理Proxy所有方法调用;
5. Proxy 的class对象 以创建的handler对象为参数,实例化一个proxy对象

在java的java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过这个类和这个接口可以生成JDK动态代理类和动态代理对象。

newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandler h)
返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序

在调用代理对象中的每一个方法时,在代码内部,都是直接调用了InvocationHandler 的invoke方法,而invoke方法根据代理类传递给自己的method参数来区分是什么方法。

invoke(Object proxy,Method method,Object[] args)
在代理实例上处理方法调用并返回结果

动态代理的实例

根据本文“背景”一栏的作业,运用Java动态代理实现简单的AOP框架。

  1. 定义两个注解 @Transaction ,@Log
package com.glodon.hongq.aop;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Transaction {
	String value() default "Transaction";
}
package com.glodon.hongq.aop;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
	String value() default "Log";
}

  1. 定义一个接口Aspect,它有两个接口方法:before(); after();
package com.glodon.hongq.aop;

public interface Aspect {
	void before();
	void after();
}
  1. 实现连个Apect实现类:TransactionAspect,LogAspect;

TransactionAspect:before():输出事务开始,after():输出事务结束

package com.glodon.hongq.aop;

public class TransactionAspect implements Aspect {

	@Override
	public void before() {
		// TODO Auto-generated method stub
		System.out.println("事物开始");
	}

	@Override
	public void after() {
		// TODO Auto-generated method stub
		System.out.println("事物结束");
	}

}

LogAspect: before():输出调用前, after():输出调用后

package com.glodon.hongq.aop;

public class LogAspect implements Aspect {

	@Override
	public void before() {
		// TODO Auto-generated method stub
		System.out.println("调用前");
	}

	@Override
	public void after() {
		// TODO Auto-generated method stub
		System.out.println("调用后");
	}

}
  1. 实现测试类,这个类里有四个方法:

    – 第一个方法:标有@Transaction
    – 第二个方法:标有@Log
    – 第三个方法:标有@Transaction 和@Log
    – 第四个方法:没有任何注个解

package com.glodon.hongq.aop;

public interface Test {
	
	@Transaction()
	public void method1() ;
	
	@Log
	public void method2() ;
	
	@Transaction()
	@Log
	public void method3() ;
	
	public void method4() ;

}
  1. 写main方法,使用aop框架执行测试类方法,要求:
    有@Transaction 方法执行的时候, TransactionAspect的两个方法被执行;
    有@Log方法执 行的时候, LogAspect的两个方法被执行。
package com.glodon.hongq.aop;

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


public class AopTest {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Test t1 = new Test() {
			@Transaction()
			public void method1() {
				System.out.println("method1");
			}
			
			@Log
			public void method2() {
				System.out.println("method2");
			}
			
			@Transaction()
			@Log
			public void method3() {
				System.out.println("method3");
			}
			
			public void method4() {
				System.out.println("method4");
			}
		};
		
		InvocationHandler testHandler = new AopInvocationHandler(t1);
		
		Test testProxy = (Test)Proxy.newProxyInstance(Test.class.getClassLoader(), new Class[] {Test.class}, testHandler);
		
		testProxy.method1();
		testProxy.method2();
		testProxy.method3();
		testProxy.method4();
	}

}

运行结果:
JAVA动态代理实现简单的AOP框架_第5张图片

- 总结
上面的动态代理的例子,其实就是AOP的一个简单实现了,在目标对象的方法的注解进行识别判断,并通过注解的不同执行相应的操作。Spring的AOP实现其实也是用了Proxy和InvocationHandler这两个东西的。

- 参考资料

Java动态代理机制详解(JDK 和CGLIB,Javassist,ASM)

你可能感兴趣的:(动态代理,AOP框架,Java)