什么是类的增强?
在我看来类增强就是通过一定的操作步骤使原本的类可以做到之前做不到的事情。
类增强的三种手段:
1、继承或者实现接口:特点是被增强对象不能变,增强的内容不能变。
2、装饰着模式:特点是被增强对象可变,但增强内容不可变。
3、动态代理:特点是被增强对象可变,增强内容可变。
1.通过继承的方式实现类的增强
下面通过代码告诉大家如何通过继承的方式来实现类的增强:
/**
* 这是一个人类
* @author king
*/
public class Person {
//是个人都有能说,所以给他一个说方法
public void say() {
System.out.println("我是给人,我能说话。。。。");
}
//是人都有吃方法,所以给他一个吃方法
public void eat() {
System.out.println("我是个人,我能吃。。。。");
}
}
下面是测试类的输出结果:
可以看出上面这个类有连个方法,一个是say(),一个是eat(),并且都可以输出一些特定的内容,下面我们就来增强它。
/**
* 不知道大家认识赵日天不,就是那个可以眼神吓死老虎徒手拆飞机的人
* @author king
*
*/
public class ZhaoRiTian extends Person {
//眼神吓死老虎
public void eye() {
System.out.println("眼神吓死老虎");
}
//徒手拆飞机
public void hand() {
System.out.println("徒手拆飞机");
}
@Override
public void say() {
super.say();
eye();
hand();
}
@Override
public void eat() {
// TODO Auto-generated method stub
super.eat();
}
}
我们可以看到,上面这个ZhaoRiTian类继承了Person类,并且重写了Person类的say方法,使得say()方法不仅可以输出原有内容还可以输出我们指定的内容。
测试结果为:
我们可以看出,输出结果已经发生了变化,证明我们的类得到了增强。
2.装饰者模式增强类
定义:装饰模式指的是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。
我们的装饰者模式就好比式一家理发店,你想要变帅一点,你通过理发店装饰自己。你作为待增强对象,而理发店就是包装类,包装后你就是一个拥有帅气发型的你,你就得到了增强。但是你要记住,你进入理发店这种增强类只能让发型得到增强,你的别的方面是不会产生变化的,如果想要再增强一下自己,使自己拥有一个好身材,那你就得通过健身房这个装饰类来增强了,这也就是装试者模式得一大特点,被装饰对象可变,装饰能容不可变。
下面通过代码告诉大家什么是装饰者模式增强类。
首先我们定义一个人类的接口,目的是让所有人类实现这个接口,拥有say()方法和eat()方法
这里我们通过实现Person接口得到了一个装饰器类,这个装饰器类就是一个抽象的装饰者类,它的作用就是让其它具体具体装饰功能的类继承它。也就是为其他具体的装饰者类提供一个模板的感觉。
上面是第一个具体的装饰这类,它具有“为原本的say()方法增加了一个eye()方法”的装饰的功能。
/**
* 这是一个装饰器,我把它命名为装饰器二号,它的主要功能就是让被增强对象获得徒手拆飞机的能力
* @author king
*/
public class Decorator_Two extends Decorator {
// 传入对象
private Person person;
// 带参构造器
public Decorator_Two(Person person) {
super(person);//将传入对象传递给父类,以便下面调用父类的方法
this.person = person;
}
//传入对象的say方法得到了增强,获得了“徒手拆飞机的能力”
@Override
public void say() {
person.say();
hand();
}
@Override
public void eat() {
person.eat();
}
//添加一个hand()方法。让它获得徒手拆飞机的能力
public void hand() {
System.out.println("徒手拆飞机。。。。");
}
}
上面这个是第二个具有装饰功能的包装类!
/**
* 这是一个装饰器,我把它命名为装饰器三号,它的主要功能就是让被增强对象大声说出自己的名字
* @author king
*/
public class Decorator_Three extends Decorator{
// 传入对象
private Person person;
// 带参构造器
public Decorator_Three(Person person) {
super(person);//将传入对象传递给父类,以便下面调用父类的方法
this.person = person;
}
//传入对象的say()方法得到了增强,拥有了“表明自己身份的能力”
@Override
public void say() {
person.say();
yell();
}
@Override
public void eat() {
person.eat();
}
// 给他添加一个yell()方法。让它获得“表明自己身份的能力”
public void yell() {
System.out.println("老子名叫赵日天。。。。");
}
}
上面这个是第三个具体的包装类。
通过上面三个包装类可以看出,它们都继承了Decorator接口,并且都对目标对象的某个方法进行了增强。
下面我们来对上面的包装类进行测试。
/**
* 这是一个普通人,他实现了Person接口
* @author king
*
* 我们要这样一个只会说和吃的人好像没有什么意义,所以我们创建他的目的就是通过装饰者模式增强他!!!
*
*/
public class SimplePerson implements Person {
@Override
public void say() {
System.out.println("我是个人,我能说话。。。。");
}
@Override
public void eat() {
System.out.println("我是个人,我能吃。。。。");
}
}
上面是一实现了Person接口实现类。
上面是对该实现类的测试。
上面是对Sperson对象进行包装,使其拥有特定的能力!
上面是对Sperson对象进行包装,使其拥有特定的能力!
上面是对Sperson对象进行包装,使其拥有特定的能力!
上面是对一个Sperson进行多次增强。
从对类的增强来看,装饰着模式是要优于类的继承的,因为一个包装类可以对任意个对象进行增强,但是继承每次只能实现对一个类的增强,也就是说如果需要增强的类特别多的情况下,就会出现类暴增的问题,但是装饰者模式也是有它的弊端的,那就是“装是内容不可变”,也就是说如果用户对类的需求提出别的需求我们就需要重写一个装饰者类,这其实是比较麻烦的,如果需要一不断被提出,我们就会很烦!那么,如何解决呢?我们通过动态代理就可以解决了,下面带大家看一下动态代理。
3.动态代理实现类的增强
什么是动态代理?
在我看来动态代理就是把你想要增强的对象传递给代理,代理帮你增强它然后再还给你一个和你传递给它的对象同类型的对象。
就好比说你把钱给投资顾问,投资顾问用你的钱投资,最终将本金和利润返还给你
这里你给理财顾问的钱就是 -->待增强对象
投资顾问就是你的的-->代理
返还给你的利润+本金就是-->代理给你返还回来的对象
那么它的“动态”体现在哪里呢?
它的动态就体现在他是在运行是动态生成的代理类,我们的项目中不存在代理类的源代码,也就是说代理类是没有通过编译就直接存在于JVM之中了。
1.JDK动态代理
JDK大家因该都不陌生在我看来jdk就是java
JDK 是整个Java的核心,包括了Java运行环境、Java工具和Java基础类库。(百度百科)
捞的不谈,我们直接上JDK动态代理。
下面是一个Person接口,他的目的就时让所有人都通过实现它得到say()和eat().
/**
* 定义一个人类的实现类目的是让所有人都实现这个接口并实现这个接口所包含的方法
* @author king
*/
public interface Person {
void say();
void eat();
}
下面我们通过实现Person接口得到实体类SimplePerson
public class SimplePerson implements Person {
@Override
public void say() {
System.out.println("我是个人,我能说话。。。。");
}
@Override
public void eat() {
System.out.println("我是个人,我能吃。。。。");
}
}
接下来我们就通过JDK动态代理增强这个类
/**
* 测试使用JDK动态代理实现对类的增强
* @author king
*/
public class Proxy_Enhance {
@Test
public void fun1() {
//先创建一个SimplePerson对象
SimplePerson Sperson= new SimplePerson();
//开始动态代理
/**
*我们直接调用java.lang.reflect.Proxy类的newProxyInstance()方法 (该方法是静态的)
*
*注意:这个方法的返回值就是我们要的到的代理对象
*
*这个方法有三个参数
*1.loader:ClassLoader,类加载器,一般我们调用本类的类加载器this.getClass.getClassLoader()
*2.interfaces:待增强类实现的所有接口组成的数组 待增强类.getClass.getInterfaces()
*3.h:InvocationHandler,方法调度器,是JDK动态代理最难的地方(采用匿名内部类的方式)
*/
Person proxyPerson=(Person) Proxy.newProxyInstance(this.getClass().getClassLoader(),Sperson.getClass().getInterfaces(),new InvocationHandler() {
/**
* InvocationHandler是一个接口,不能直接创建,所以我们选用匿名内部类来实现
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
/**
* 这个invoke方法会在我们调用代理对象中的接口方法时调用
*
* 三个参数:
* proxy:调用接口方法的对象
* method:调用的接口方法
* args[]:当前调用方法的参数
*
* invoke方法的返回值就为当前调用的接口方法的返回值
*/
Sperson.say();
System.out.println("眼神吓死老虎,徒手拆飞机");
return null;
}
});
//调用生成的代理对象的say方法,会发现say方法得到了增强
proxyPerson.say();
}
运行结果:
乍一看好是成功了,确实是成功了,但是还存在一系列问题。
我们发现我么得eat方法输出了和say方法相同得结果,这是为什么呢?
每调用一次代理类的接口方法其本质都是执行一次invoke方法,只是传入的参数不同罢了。
那么我们现在的需求是只对say方法进行增强,eat方法原模原样输出就可以,如何实现呢?请看下面的代码。
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
/**
* 这个invoke方法会在我们调用代理对象中的接口方法时调用
*
* 三个参数:
* proxy:调用接口方法的对象
* method:调用的接口方法
* args[]:当前调用方法的参数
*
* invoke方法的返回值就为当前调用的接口方法的返回值
*/
if(method.getName().equals("say")) {
method.invoke(Sperson, args);//这里的args就是我们传入的所有参数组成的数组
System.out.println("眼神吓死老虎,徒手拆飞机");
}
if(method.getName().equals("eat")) {
method.invoke(Sperson, args);
}
return null;
}
在invoke中判断方法的名称,选择型增强,就实现了我们的需求
运行结果如下:
那么如果我的方法有返回值又该怎么办呢?且往下看
我们修改一下接口何实现类使其的say方法返回值类型为String。
public interface Person {
String say();
void eat();
}
public class SimplePerson implements Person {
@Override
public String say() {
return "我是个人,我能说话。。。。";
}
@Override
public void eat() {
System.out.println("我是个人,我能吃。。。。");
}
}
Person proxyPerson=(Person) Proxy.newProxyInstance(this.getClass().getClassLoader(),Sperson.getClass().getInterfaces(),new InvocationHandler() {
/**
* InvocationHandler是一个接口,不能直接创建,所以我们选用匿名内部类来实现
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
/**
* 这个invoke方法会在我们调用代理对象中的接口方法时调用
*
* 三个参数:
* proxy:调用接口方法的对象
* method:调用的接口方法
* args[]:当前调用方法的参数
*
* invoke方法的返回值就为当前调用的接口方法的返回值
*/
String msg=(String) method.invoke(Sperson, args);//这里的args就是我们传入的所有参数组成的数组
return "我是一个有返回值的方法"+"-->"+"我被增强了"+"-->"+msg;
}
});
我们可以看出invoke方法的返回值就是接口方法的返回值!
运行结果如下:
到这里我们的JDK动态代理就说的差不多,但是你有没有什么疑问呢?如果我想要增强一个类但是这个类没有实现人何接口,我要怎么办呢?这就是下面要谈的了。
Cglib动态代理
我创建了一个Person类
public class Person {
public String say() {
return "我是个人,我能说话。。。。";
}
public void eat() {
System.out.println("我是个人,我能吃。。。。");
}
}
使用Cgbib进行增强
/**
* Cglib_Factory 负责生产代理对象
* @author king
*/
public class Cglib_Factory implements MethodInterceptor {
private Person target;
/**
* 创建代理对象的方法
* @param person
* @return
*/
public Cglib_Factory(Person target) {
this.target = target;
}
public Person Create_Proxy() {
//创建增强器类
Enhancer enhancer = new Enhancer();
//为增强其设置父类,因为我们的目的是增强目标类,二Cglib的增强机制是通过继承来实现的,所以我们将我们的待增强类甚至成enhancer的父类
//这样生成的对象一定可以向上转型成待增强对象
enhancer.setSuperclass(target.getClass());
//甚至掉方法,我们可以看出,我们当前类实现的接口就已经实现了CallBack所以直接传入当前类对象就可以了
//并且我们当前类实现俺的接口只有一个方法既我们的回调方法intercept();
enhancer.setCallback(this);
//通过enhancer创建代理对象并向上装箱为待增强对象类型,返回回去
return (Person)enhancer.create();
}
//我们主要关注点放在这个回调方法上就好了,因为我们通过过代理对象调用的每一个方法其实都是调用该方法的,它就类似于JDk动态代理中的invoke方法
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
//这里的target就是待增强对象,args就是当前调用方法的参数
method.invoke(target, args);
System.out.println("我得到了增强。。。。");
return null;
}
}
测试增强:
@Test
public void fun1() {
Person person=new Person();
Cglib_Factory cf=new Cglib_Factory(person);
Person proxy_ele=cf.Create_Proxy();
proxy_ele.eat();
}
输出结果为:
可以看出,我们的eat方法得到了增强。
现在我们的需求是只增强say方法,其他方法不变
其实我们只需要修改intercept方法即可,代码如下:
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
//这里的target就是待增强对象,args就是当前调用方法的参数
if(method.getName().equals("say")) {
String msg=(String) method.invoke(target, args);
return "我得到了增强"+"->>>>>"+msg;
}else{
method.invoke(target, args);
return null;
}
}
进行测试:
@Test
public void fun2() {
Person person=new Person();
Cglib_Factory cf=new Cglib_Factory(person);
Person proxy_ele=cf.Create_Proxy();
System.out.println(proxy_ele.say());
proxy_ele.eat();
}
运行结果为:
根据结果可知,我们的需求已经得到了实现
有人可能会有疑问,Cglib动态代理和JDK动态代理究竟如何选择性使用呢?
也就是说什么地方该使用哪一个代理模式?
JDK:待增强类实现了一个或者一个以上接口的类是选用JDK动态代理,在我看来,jdk动态代理是通过实现待增强的类的所有接口来创建代理对象的,也会是说,接口是重点!一般在Spring的非侵入式框架或者弱侵入式框架中建议使用JDK动态代理因为JDK动态代理式java自带的,所以采用JDK动态代理不需要引入外来的jar包,代码不会被侵入污染!
Cglib:待增强类没有实现任何接口,但是我们需要增强它,这是就要采用Cglib动态代理,在我看来Cglib动态代理就是通过得到待增强类的类类型然后通过反射得到它的子类对象,该对象就是代理对象,使用Cglib动态代理需要导入jar包,属于侵入式编程。
本人的建议是能使用JDK动态代理尽量使用JDK动态代理,不能使用JDK动态代理的情况下再考虑使用Cglib动态代理。
本人资历尚浅,如代码或思想存在问题,还望指教。。