从虚拟机加载类的角度来讲,本质上都是一样的,都是在原有类的行为基础上,加入一些多出的行为,甚至完全替换原有的行为。
静态代理以数据库连接为例:频繁的开关数据库连接是非常浪费服务器的CPU资源以及内存的,所以我们一般都是使用数据库连接池来解决这一问题,即创造一堆等待被使用的连接,等到用的时候就从池里取一个,不用了再放回去,数据库连接在整个应用启动期间,几乎是不关闭的,除非是超过了最大闲置时间。我们现在使用代理模式,替换掉connection的close行为。
connection类(省掉了很多方法):
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Wrapper;
public interface Connection extends Wrapper {
Statement createStatement() throws SQLException;
void close() throws SQLException;
}
静态代理类:
import java.sql.SQLException;
import java.sql.Statement;
public class ConnectionProxy implements Connection{
private Connection connection;
public ConnectionProxy(Connection connection) {
super();
this.connection = connection;
}
public Statement createStatement() throws SQLException{
return connection.createStatement();
}
public void close() throws SQLException{
DataSource.getInstance().recoveryConnection(connection);
System.out.println("不真正关闭连接,归还给连接池");
}
}
在构造方法中让调用者强行传入一个原有的连接,接下来我们将我们不关系的方法,交给真正的Connection接口去处理(像createStatement方法),而我们将真正关心的close方法用我们自己希望的方式去进行。
连接池DataSource类:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.LinkedList;
public class DataSource {
private static LinkedList<Connection> connectionList = new LinkedList<Connection>();
static{
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
private static Connection createNewConnection() throws SQLException{
return DriverManager.getConnection("url","username", "password");
}
private DataSource(){
if (connectionList == null || connectionList.size() == 0) {
for (int i = 0; i < 10; i++) {
try {
connectionList.add(createNewConnection());
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
public Connection getConnection() throws Exception{
if (connectionList.size() > 0) {
//return connectionList.remove(); 这是原有的方式,直接返回连接,这样可能会被程序员把连接给关闭掉
//下面是使用代理的方式,程序员再调用close时,就会归还到连接池
//LinkList.remove()等同于LinkList.removeFirst()删除第一个元素并将其返回
return new ConnectionProxy(connectionList.remove());
}
return null;
}
//重置数据库连接,归还到数据库连接池
public void recoveryConnection(Connection connection){
connectionList.add(connection);
}
//单例模式 获取对象实例
public static DataSource getInstance(){
return DataSourceInstance.dataSource;
}
//单例模式 静态变量声明
private static class DataSourceInstance{
private static DataSource dataSource = new DataSource();
}
}
1,代理类一般要持有一个被代理的对象的引用。
2,对于我们不关心的方法,全部委托给被代理的对象处理。
3,自己处理我们关心的方法。
静态代理对于这种,被代理的对象很固定,我们只需要去代理一个类或者若干固定的类,数量不是太多的时候可以使用,因为动态代理就是在运行期间动态生成代理类,消耗的时间会更久。
动态代理有两种方式:一种是JDK自带的功能,它需要你去实现一个InvocationHandler接口,并且调用Proxy的静态方法去产生代理类。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
public class ConnectionProxy implements InvocationHandler{
private Connection connection;
public ConnectionProxy(Connection connection) {
super();
this.connection = connection;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//这里判断是Connection接口的close方法的话
if (Connection.class.isAssignableFrom(proxy.getClass()) && method.getName().equals("close")) {
//我们不执行真正的close方法
//method.invoke(connection, args);
//将连接归还连接池
DataSource.getInstance().recoveryConnection(connection);
return null;
}else {
return method.invoke(connection, args);
}
}
public Connection getConnectionProxy(){
return (Connection) Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{Connection.class}, this);
}
}
InvocationHandler接口只有一个invoke方法需要实现,这个方法是用来在生成的代理类用回调使用的。动态代理是将每个方法的具体执行过程交给了我们在invoke方法里处理。我们只需要创造一个ConnectionProxy的实例,并且将调用getConnectionProxy方法的返回结果作为数据库连接池返回的连接就可以了。
通常情况下,动态代理的使用是为了解决这样一种问题,就是我们需要代理一系列类的某一些方法,最典型的应用就是我们前段时间讨论过的springAOP,我们需要创造出一批代理类,切入到一系列类当中的某一些方法中。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class DynamicProxy implements InvocationHandler{
private Object source;
public DynamicProxy(Object source) {
super();
this.source = source;
}
public void before(){
System.out.println("在方法前做一些事,比如打开事务");
}
public void after(){
System.out.println("在方法返回前做一些事,比如提交事务");
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//假设我们切入toString方法,其他其实也是类似的,一般我们这里大部分是针对特定的方法做事情的,通常不会对类的全部方法切入
//比如我们常用的事务管理器,我们通常配置的就是对save,update,delete等方法才打开事务
if (method.getName().equals("toString")) {
before();
}
Object result = method.invoke(source, args);
if (method.getName().equals("toString")) {
after();
}
return result;
}
public Object getProxy(){
return Proxy.newProxyInstance(getClass().getClassLoader(), source.getClass().getInterfaces(), this);
}
}
上面的代理类可以代理任何类,因为它被传入的对象是Object,而不再是具体的类,比如刚才的Connection,这些产生的代理类在调用toString方法时会被插入before方法和after方法。
动态代理有一个强制性要求,就是被代理的类必须实现了某一个接口,或者本身就是接口
因为动态代理生成的代理类是继承Proxy类的,并且会实现被你传入newProxyInstance方法的所有接口,所以我们可以将生成的代理强转为任意一个代理的接口或者Proxy去使用,但是Proxy里面几乎全是静态方法,没有实例方法,所以转换成Proxy意义不大,几乎没什么用。假设我们的类没有实现任何接口,那么就意味着你只能将生成的代理类转换成Proxy,那么就算生成了,其实也没什么用,而且就算你传入了接口,可以强转,你也用不了这个没有实现你传入接口的这个类的方法。
假设有个接口A,那我将接口A传给newProxyInstance方法,并代理一个没实现接口A的类B,但类B与接口A有一样的方法可以吗?
答案是可以的,并且JDK的动态代理只认你传入的接口,只要你传入,你就可以强转成这个接口,但是你无法在invoke方法里调用method.invoke方法,也就是说,你只能全部替换A接口的方法,而不能使用类B中原有与接口A方法描述相同的方法,这是因为invoke中传入的Method的class信息是接口A,而类B因为没实现接口A,所以无法执行传入的Method,会抛出非法参数异常。
下面贴出测试代码
先是一个普通接口。
public interface TestInterface {
void method1();
void method2();
void method3();
}
然后是一个类,和接口一模一样的方法,但是就是没实现这个接口。
public class TestClass{
public void method1() {
System.out.println("TestClass.method1");
}
public void method2() {
System.out.println("TestClass.method2");
}
public void method3() {
System.out.println("TestClass.method3");
}
}
测试类
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class DynamicProxy implements InvocationHandler{
private Object source;
public DynamicProxy(Object source) {
super();
this.source = source;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("接口的方法全部变成这样了");
//这里source是TestClass,但是我们不能使用反射调用它的方法,像下面这样,放开这一行会抛异常
return method.invoke(source, args);
}
public static void main(String[] args) {
//只要你传入就可以强转成功
TestInterface object = (TestInterface) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{TestInterface.class}, new DynamicProxy(new TestClass()));
object.method1();
object.method2();
object.method3();
}
}
运行的话就会有 java.lang.IllegalArgumentException: object is not an instance of declaring class。
但是如果要继续使用TestClass的方法也不是不行,只要你确认你传入的类包括了所有你传入的接口的方法,只是没实现这些接口而已,那么你可以在invoke中这样使用。
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Method sourceMethod = source.getClass().getDeclaredMethod(method.getName(), method.getParameterTypes());
//启用和禁用访问安全检查的开关, 为true是取消安全检查
sourceMethod.setAccessible(true);
Object result = sourceMethod.invoke(source, args);
return result;
}
这就与实现接口的表现行为一致了,但是我们本来就只需要一句method.invoke就可以了,就因为没实现接口就要多写两行,所以这种突破JDK动态代理必须实现接口的行为就有点画蛇添足了。因为你本来就实现了该接口的方法,只是差了那一句implements而已。
当然这个例子只是为了证明即使不实现接口也可以实现动态代理的特殊情况。
还有一种生成动态代理的方式是使用cglib
需要的4个jar包:http://pan.baidu.com/s/1eR97gK6
CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑,而且对类没有必须实现接口的要求。
例子:
父类
public class SayHello {
public void say(){
System.out.println("hello everyone");
}
}
代理类:该类实现了创建子类的方法与代理的方法。getProxy(SuperClass.class)方法通过入参即父类的字节码,通过扩展父类的class来创建代理对象。intercept()方法拦截所有目标类方法的调用,obj表示目标类的实例,method为目标类方法的反射对象,args为方法的动态入参,proxy为代理类实例。proxy.invokeSuper(obj, args)通过代理类调用父类中的方法。
public class CglibProxy implements MethodInterceptor{
private Enhancer enhancer = new Enhancer();
public Object getProxy(Class clazz){
//设置需要创建子类的类
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
//通过字节码技术动态创建子类实例
return enhancer.create();
}
//实现MethodInterceptor接口方法
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
System.out.println("前置代理");
//通过代理类调用父类中的方法
Object result = proxy.invokeSuper(obj, args);
System.out.println("后置代理");
return result;
}
}
测试类:
public class DoCGLib {
public static void main(String[] args) {
CglibProxy proxy = new CglibProxy();
//通过生成子类的方式创建代理类
SayHello proxyImp = (SayHello)proxy.getProxy(SayHello.class);
proxyImp.say();
}
}
CGLib创建的动态代理对象性能比JDK创建的动态代理对象的性能高不少,但是CGLib在创建代理对象时所花费的时间却比JDK多得多,所以对于单例的对象,因为无需频繁创建对象,用CGLib合适,反之,使用JDK方式要更为合适一些。同时,由于CGLib由于是采用动态创建子类的方法,对于final标识的类无法被继承,会抛出异常。对于final标识的方法,intercept不生效,直接调用父类方法,不能代理。
PS:instanceof, isinstance,isAssignableFrom的区别
instanceof运算符 只被用于对象引用变量,检查左边的被测试对象 是不是 右边类或接口的 实例化。如果被测对象是null值,则测试结果总是false。
自身实例或子类实例 instanceof 自身类 返回true
例: String s=new String("javaisland");
System.out.println(s instanceof String); //true
Class类的isInstance(Object obj)方法,obj是被测试的对象,如果obj是调用这个方法的class或接口 的实例,则返回true。这个方法是instanceof运算符的动态等价。
自身类.class.isInstance(自身实例或子类实例) 返回true
例:String s=new String("javaisland");
System.out.println(String.class.isInstance(s)); //true
Class类的isAssignableFrom(Class cls)方法,如果调用这个方法的class或接口 与 参数cls表示的类或接口相同,或者是参数cls表示的类或接口的父类,则返回true。
自身类.class.isAssignableFrom(自身类或子类.class) 返回true
例:System.out.println(ArrayList.class.isAssignableFrom(Object.class)); //false
System.out.println(Object.class.isAssignableFrom(ArrayList.class)); //true
热门评论
还是没太看懂,代理模式到底有什么用
class IpUtil{
}