动态代理

学无止境,学无止境,根本停不下来。

什么是动态代理?

现在有一个Student类,里面有吃饭方法。

public class Student{
	public void eat(){
		System.out.println("吃饭");
	}
}

现在我要给这个方法添加其他功能,比如:

System.out.println("拿筷子");
System.out.println("盛饭");

按以前所学,我们只能把这两句话放入eat()方法中,直接修改代码,被称为侵入式修改。在一个成熟的项目中,我们很少会这么去干,因为可能会整个项目坍塌。那我既不能修改原有的代码,还要增加新的功能,那我怎么办?此时我们就得去找一个代理,什么是代理,说白了就是中介公司,他会帮你先去做拿筷子和盛饭的这些准备工作,等真正吃饭了,再去调用Student里的方法,这就叫做动态代理

动态代理,可以侵入式的给代码增加额外的功能。

那程序为什么需要代理?代理长什么样?将一个小故事,一个关于鸡哥的小故事(我不是小黑子哦)。鸡哥是一个大明星,他会唱歌、跳舞,在程序中反映出来就是唱歌和跳舞两个方法。鸡哥是一个大明星,唱歌跳舞肯定不能随便,得先准备话筒和场地,准备话筒收钱以及准备场地收钱。那这两个新增功能是写在自己的方法中吗?想一下鸡哥是一个大明星,他自己做这两件事烦不烦,就把这两件事交个中介公司,公司会派一个代理人打理这些事

唱歌(){
  	
	真正开始唱歌;
}

跳舞(){
  	
	真正开始跳舞;
}
代理{
	准备话筒,收钱;
  	准备场地,收钱;
}

所以为什么程序需要代理?就是因为对象如果嫌身上干的事太多的话,可以通过代理来转移部分职责。那这个代理长什么样呢?就是说这个代理里面能写什么方法,肯定是有唱歌和跳舞的方法,因为其他人找鸡哥都是要先找代理。但是代理肯定不会唱跳,而是你找我唱歌,那我先收你钱,然后再调用鸡哥的唱歌方法,让唱。

说白了代理长什么样子,其实是和对象差不多的。对象有什么方法想被代理,代理就一定要有对应的方法,只不过代理要把准备工作先做完,然后再去调用对象中的方法。那代理如何能知道鸡哥要代理唱歌和跳舞这两个方法呢?万一还有第三个打篮球呢?代码中通过接口搞定,事先定义一个接口,有唱歌和跳舞的方法,这个接口中所有的方法就是我们想代理的方法。左边的代理和右边的鸡哥都要实现接口才可以。

public class BigStar implements Star {
    private String name;


    public BigStar() {
    }

    public BigStar(String name) {
        this.name = name;
    }

    //唱歌
    @Override
    public String sing(String name){
        System.out.println(this.name + "正在唱" + name);
        return "谢谢";
    }

    //跳舞
    @Override
    public void dance(){
        System.out.println(this.name + "正在跳舞");
    }

    /**
     * 获取
     * @return name
     */
    public String getName() {
        return name;
    }

    /**
     * 设置
     * @param name
     */
    public void setName(String name) {
        this.name = name;
    }

    public String toString() {
        return "BigStar{name = " + name + "}";
    }
}
public interface Star {

    //我们可以把所有想要被代理的方法定义在接口当中

    //唱歌
    public abstract String sing(String name);
    //跳舞
    public abstract void dance();
}

如何为java对象创建一个代理对象呢?

  • java.lang.reflect.Proxy类:提供了为对象产生代理对象的方法:
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
//参数一:用于指定用哪个类加载器,去加载生成的代理类
//什么是类加载器呢?就是Java在运行的时候,需要有一个人把字节码文件加载到内存当中,就是类加载器去加载的
//参数二:指定接口,这些接口用于指定生成的代理长什么样,也就是有哪些方法
//参数三:用来指定生成的代理对象要干什么事情

/*
	类的作用:创建一个代理
*/
public class ProxyUtil {
  	/*
		方法的作用:
			给一个明星的对象,创建一个代理
		形参:
			被代理的明星对象
		返回值:
			给明星创建的代理
			
		需求:
     外面的人想要大明星唱一首歌
     1. 获取代理的对象
        代理对象 = ProxyUtil.createProxy(大明星的对象);
     2. 再调用代理的唱歌方法 底层会调用代理类中的invoke()
        代理对象.唱歌的方法("只因你太美");
	*/
  	public static Star createProxy(BigStar bigStar){
	  	Star star = (Star)Proxy.newProxyInstance(
		  		//参数一:用于指定用哪个类加载器,去加载生成的代理类
		  		ProxyUtil.class.getClassLoader(),
		  		//参数二:指定接口,这些接口用于指定生成的代理长什么样,也就是有哪些方法
				new Class[]{Star.class},
				//参数三:用来指定生成的代理对象要干什么事情
                new InvocationHandler() {
                  	@Override
                  	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
							  /*
							  * 参数一:代理的对象
							  * 参数二:要运行的方法 sing
							  * 参数三:调用sing方法时,传递的实参
							  * */
							  if("sing".equals(method.getName())){
								  System.out.println("准备话筒,收钱");
							  }else if("dance".equals(method.getName())){
								  System.out.println("准备场地,收钱");
							  }
							  //去找大明星开始唱歌或者跳舞
							  //代码的表现形式:调用大明星里面唱歌或者跳舞的方法
					  		  //反射的知识
							  return method.invoke(bigStar,args);
						  }
					  }
				);
	  

创建代理对象并执行方法的过程有些复杂,下面这个图能帮助理解,一个过程图。

总结

最后我们来总结一下代理,要掌握如下几点:

  1. 为什么需要代理?
  2. 代理可以无侵入式的给对象增加其他的功能
  3. 代理长什么样?
  4. 代理里面就是对象要被代理的方法
  5. java通过什么来保证代理的样子?
  6. 通过接口保证,对象和代理需要实现同一个接口,接口中就是被代理的所有方法
#你觉得今年春招回暖了吗##牛客在线求职答疑中心##牛客解忧铺##23届找工作求助阵地#
java基础知识 文章被收录于专栏

我是一个转码的小白,平时会在牛客中做选择题,在做题中遇到不会的内容就会去找视频或者文章学习,以此不断积累知识。这个专栏主要是记录一些我通过做题所学到的基础知识,希望能对大家有帮助

全部评论
讲的通俗易懂,感谢大佬分享
1 回复 分享
发布于 2023-03-13 20:57 广东
是不是相当于硬件中的中断呢??
点赞 回复 分享
发布于 2023-03-13 20:12 山西

相关推荐

05-23 15:16
已编辑
门头沟学院 Java
1,缓存架构?答:讲了一下redis在项目中的具体实现注:其实面试官想问的是多层架构2,redsi缓存击穿,穿透,雪崩怎么解决?答:击穿可以通过设置热key永不过期穿透可以使用缓存空值和布隆过滤器来解决雪崩可以通过给键设置基础时间值+随机时间值来解决注:缓存击穿还可以还通过互斥锁进行解决(性能较低)关于雪崩上面只说了大量key过期的问题&nbsp;没有提到redis宕机解决方法:(1)设置多层架构 (2)建立redis主从或集群(3)提前演练redis宕机&nbsp;从而设计解决方法3,大量不存在的用户同时登录时会给数据库造成压力,怎么解决?答:使用redis缓存空值注:缓存空值不能有效解决这类缓存穿透问题这里要使用布隆过滤器进行拦截&nbsp;更加有效在实际业务开发中最好俩者结合使用4,jwt?答:说了一下jwt的生成和解析以及结构5,讲讲乐观锁和悲观锁答:讲了一遍sychronized的底层实现从无锁-&gt;偏向锁-&gt;轻量级锁-&gt;重量级锁这里轻量级锁就是乐观锁&nbsp;重量级锁就是悲观锁6,乐观锁和悲观锁最主要的区别?答:在低并发场景下乐观锁性能好在高并发场景下悲观锁性能好注:乐观锁是认为操作的时候没有线程和我并发操作通过cas判断&nbsp;不会让你的线程挂起&nbsp;可能会不断自旋去尝试获取锁悲观锁是认为有线程和我并发操作&nbsp;拿不到锁线程就会进入阻塞状态直到拿到锁的线程释放锁后唤醒该线程7,sychronized和reentrantlock有什么区别?答:sychronized由jvm释放锁&nbsp;reentrantlock手动释放sychronized不可重入&nbsp;reentrantlock可重入(避免死锁)注:这里答错了sychronized可重入 他们的主要区别在于sychronized不支持公平锁,不支持超时不可中断,不支持多条件 sychronized是java内置的关键字&nbsp;reentrantlock是由juc类库所提供的8,aop怎么理解?这里答的太乱了不清楚注:把那些非核心功能抽取出来封装成一个切面去掉冗余代码通过动态代理的方式&nbsp;将需要注入切面的对象进行代理在进行调用的时候直接将公共逻辑注入 侵入性较低1,缓存架构?答:讲了一下redis在项目中的具体实现注:其实面试官想问的是多层架构2,redsi缓存击穿,穿透,雪崩怎么解决?答:击穿可以通过设置热key永不过期穿透可以使用缓存空值和布隆过滤器来解决雪崩可以通过给键设置基础时间值+随机时间值来解决注:缓存击穿还可以还通过互斥锁进行解决(性能较低)关于雪崩上面只说了大量key过期的问题&nbsp;没有提到redis宕机解决方法:(1)设置多层架构 (2)建立redis主从或集群(3)提前演练redis宕机&nbsp;从而设计解决方法3,大量不存在的用户同时登录时会给数据库造成压力,怎么解决?答:使用redis缓存空值注:缓存空值不能有效解决这类缓存穿透问题这里要使用布隆过滤器进行拦截&nbsp;更加有效在实际业务开发中最好俩者结合使用4,jwt?答:说了一下jwt的生成和解析以及结构5,讲讲乐观锁和悲观锁答:讲了一遍sychronized的底层实现从无锁-&gt;偏向锁-&gt;轻量级锁-&gt;重量级锁这里轻量级锁就是乐观锁&nbsp;重量级锁就是悲观锁6,乐观锁和悲观锁最主要的区别?答:在低并发场景下乐观锁性能好在高并发场景下悲观锁性能好注:乐观锁是认为操作的时候没有线程和我并发操作通过cas判断&nbsp;不会让你的线程挂起&nbsp;可能会不断自旋去尝试获取锁悲观锁是认为有线程和我并发操作&nbsp;拿不到锁线程就会进入阻塞状态直到拿到锁的线程释放锁后唤醒该线程7,sychronized和reentrantlock有什么区别?答:sychronized由jvm释放锁&nbsp;reentrantlock手动释放sychronized不可重入&nbsp;reentrantlock可重入(避免死锁)注:这里答错了sychronized可重入 他们的主要区别在于sychronized不支持公平锁,不支持超时不可中断,不支持多条件 sychronized是java内置的关键字&nbsp;reentrantlock是由juc类库所提供的8,aop怎么理解?这里答的太乱了不清楚注:把那些非核心功能抽取出来封装成一个切面去掉冗余代码通过动态代理的方式&nbsp;将需要注入切面的对象进行代理在进行调用的时候直接将公共逻辑注入&nbsp;侵入性较低不想写了&nbsp;直接把问题都扔出来吧&nbsp;java线程池的七个参数?1.&nbsp;Java线程池,5核⼼、10最⼤、10队列,第6个任务来了是什么状态?任务扔到⼯作队列中2.&nbsp;如果在第6个任务过来的时候,5个核⼼线程都已经空闲了呢?⼀样扔到队列(线程池只关注数量)3.&nbsp;第16个任务来了怎么处理?创建⾮核⼼线程去处理第16个任务4.&nbsp;第16个任务来了的时候,要是有核⼼线程空闲了呢?如果这个空闲的线程,将⼯作队列中的10个任务,取⾛了⼀个,变为了9个,那任务扔队列。如果空闲的线程还没来得及取⾛任务,投递时,队列⻓度依然为10,那还是创建⾮核⼼。5.&nbsp;队列满了以后执⾏队列的任务是从队列头&nbsp;or&nbsp;队尾取?⼀般咱们的阻塞队列都是FIFO的,所以先进先出,从头取。6.&nbsp;核⼼线程和⾮核⼼线程执⾏结束后,谁先执⾏队列⾥的任务?谁空闲了,并且去等待任务,谁先去执⾏队列⾥的任务。7.为什么⾮核⼼优先执⾏投递的任务?8.核⼼线程与⾮核⼼线程有什么区别?9.MySQL中如何实现数据的读⼀致性?10.&nbsp;MySQL的InnoDB引擎是如何通过⽇志实现事务的?11.&nbsp;MySQL崩溃恢复为什么不⽤binLog?12.Redis的事务了解吗?13.Redis&nbsp;的持久化机制?总结:对底层的理解还是不够深入&nbsp;之前没有了解过redis事务&nbsp;有的时候答非所问容易跑题
点赞 评论 收藏
分享
评论
31
11
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务