介绍
应用: AOP实现、拦截器、房产中介、黄牛、媒婆、解耦、专人做专事、自己不想做但又不得不做的事。
静态代理
概念:静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同的父类。总之,在代理之前,所有东西都是已知的(人工)。
场景:大家都知道我们程序员找对象比较困难,因为我们比较宅,性格内向。张三是一名程序员,年龄接近30了却还没找对象。于是他父母开始替他着急了,他的父亲打算帮他去相亲,问张三想要找个什么类型的女朋友,张三说他喜欢白富美,大长腿。他父亲按着这个要求找到了一个叫凤姐的女孩子,然后安排了张三和凤姐的一次约会,之后两人正式成为男女朋友。这里面,张三就是被代理对象,张三的父亲就是代理对象。
代码:
1.先描述一个人的某些特征,比如人都要找对象、找工作、买东西等等。
public interface Person {
//找对象
public void findLove();
//找工作
public void findJob();
//买东西
public void buy();
}
2.这时候有个叫张三的人:
public class ZhangSan implements Person{
@Override
public void findLove() {
System.out.println("我想找一个白富美,大长腿");
}
@Override
public void findJob() {
System.out.println("我想找一个年薪50W的工作");
}
@Override
public void buy() {
System.out.println("我想去超市买东西");
}
}
3.然后就是张三的父亲:
public class Father implements Person{
//张三需要告诉父亲他找对象的要求,所以传入自己的引用
private ZhangSan zhangSan;
public Father(ZhangSan zhangSan) {
this.zhangSan = zhangSan;
}
@Override
public void findLove() {
System.out.println("帮张三去物色,最终找到凤姐并安排他俩约会");
zhangSan.findLove();
System.out.println("两人成功的在一起");
}
@Override
public void findJob() {
zhangSan.findJob();
}
@Override
public void buy() {
zhangSan.buy();
}
}
4.他父亲开始帮张三找对象了:
public class ProxyTest {
public static void main(String[] args) {
ZhangSan zhangSan=new ZhangSan();
Father father=new Father(zhangSan);
father.findLove();
}
// 运行结果:
// 帮张三去物色,最终找到凤姐并安排他俩约会
// 我想找一个白富美,大长腿
// 两人成功的在一起
}
总结:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类(比如:张三父亲帮张三找对象,张三母亲帮张三买东西…),类太多。同时,一旦接口增加方法,目标对象与代理对象都要维护。
jdk动态代理
概念: 使用jdk动态代理,代理对象不需要实现接口,代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象。总之,在代理之前,所有东西都是未知的。(自动化,智能化)
场景:张三需要买房子,但是自己找房子太麻烦了,于是联系中介,希望他们帮自己找到好的房源。这里需要这么理解,中介帮助张三找房子,但实际上真正搜索房源的是电脑,电脑提供了一系列的房源信息。
代码:
1.张三和中介都是人,所以开始我们先描述一个人:
public interface Person {
//找房子
public void buyHouse();
}
2.有个人叫张三:
public class ZhangSan implements Person {
@Override
public void buyHouse() {//张三买房子的要求
System.out.println("我是张三:我要买一个120平的房子");
}
}
3.房产中介出来了:
/**
* 房产中介
*/
public class HouseAgency implements InvocationHandler{
//张三需要把找房子的要求告诉中介,所以这里传自己的引用
private ZhangSan zhangSan;
public HouseAgency(ZhangSan zhangSan) {
this.zhangSan = zhangSan;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("我是中介:麻烦张三提供房源要求给我");
method.invoke(zhangSan,args);
System.out.println("我是中介:帮助张三找到合适房源");
return null;
}
}
4.中介开始在电脑上搜索房源信息:
/**
* 开始找房源
*/
public class HouseAgencyTest {
public static void main(String[] args) {
//张三出来
ZhangSan zhangSan=new ZhangSan();
//张三提供信息
Class clazz=zhangSan.getClass();
//房产中介出来
HouseAgency houseAgency=new HouseAgency(zhangSan);
//房产中介通过电脑开始搜索房源
Person result=(Person) Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),houseAgency);
//电脑搜索房源成功
result.buyHouse();
}
// 执行结果:
// 我是中介:麻烦张三提供房源要求给我
// 我是张三:我要买一个120平的房子
// 我是中介:帮助张三找到合适房源
}
总结:代理对象不需要实现接口,但是目标对象一定要实现接口,否则不能用动态代理。
cglib动态代理
概念: 使用cglib动态代理,目标对象不需要实现接口,引入cglib的jar包后就可以在内存中动态构建目标对象的子类,意思就是说,代理对象继承了目标对象,这样的话,代理对象就可以直接调用目标对象的方法。值得注意的是,目标类不要被final修饰,不然没法被代理类继承。
场景:张三想要找对象,于是他联系了媒婆,媒婆帮他物色了很久,终于帮他找到了对象。这里需要这么理解,媒婆虽然是代理张三找对象,但是后面还会有一个真实的代理人帮助张三找对象,只是这个代理人大家都不认识而已。
代码:
1.先有个张三:
public class ZhangSan {
public void findLove(){
System.out.println("肤白貌美,大长腿");
}
}
2.然后是媒婆:
public class CglibMeipo implements MethodInterceptor{
private ZhangSan zhangSan;
public CglibMeipo(ZhangSan zhangSan) {
this.zhangSan = zhangSan;
}
//真实的代理人帮助张三找对象
public Object getProxyInstance(){
//1.工具类
Enhancer en = new Enhancer();
//2.设置父类
en.setSuperclass(zhangSan.getClass());
//3.设置回调函数
en.setCallback(this);
//4.创建子类(代理对象)
return en.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("我是媒婆:麻烦张三给我提供找对象的要求");
//执行目标对象的方法
method.invoke(zhangSan, objects);
System.out.println("我是媒婆:开始找对象");
return null;
}
}
3.开始找对象:
public class ProxyTest {
public static void main(String[] args) {
//张三出来
ZhangSan zhangSan=new ZhangSan();
//媒婆出来找到真实代理人
ZhangSan result=(ZhangSan)new CglibMeipo(zhangSan).getProxyInstance();
//真实代理人开始找对象
result.findLove();
// 执行结果:
// 我是媒婆:麻烦张三给我提供找对象的要求
// 肤白貌美,大长腿
// 我是媒婆:开始找对象
}
}
总结: 目标对象不需要实现接口,在Spring的AOP编程中,如果加入容器的目标对象有实现接口,用JDK代理,如果目标对象没有实现接口,用cglib代理。
想了解jdk动态代理底层实现的,请参考我的下一个微博:jdk动态代理源码分析之自定义实现