为什么要学习代理模式,因为AOP的底层机制就是动态代理!

代理模式:

  • 静态代理
  • 动态代理

学习aop之前 , 我们要先了解一下代理模式!

image-20230302201323345.png

代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式;即通过代理访问目标对象。这样好处:可以在目标对象实现的基础上,增强额外的功能操作。(扩展目标对象的功能)。

静态代理

静态代理角色分析

  • 抽象角色 : 一般使用接口或者抽象类来实现

  • 真实角色 : 被代理的角色

  • 代理角色 : 代理真实角色 ; 代理真实角色后 , 一般会做一些附属的操作 .

  • 客户 : 使用代理角色来进行一些操作 .

代码实现

Rent . java 即抽象角色

//抽象角色:租房
public interface Rent {
   public void rent();
}

Host . java 即真实角色

//真实角色: 房东,房东要出租房子
public class Host implements Rent{
   public void rent() {
       System.out.println("房屋出租");
  }
}

Proxy . java 即代理角色

//代理角色:中介
public class Proxy implements Rent {

   private Host host;
   public Proxy() { }
   public Proxy(Host host) {
       this.host = host;
  }

   //租房
   public void rent(){
       seeHouse();
       host.rent();
       fare();
  }
   //看房
   public void seeHouse(){
       System.out.println("带房客看房");
  }
   //收中介费
   public void fare(){
       System.out.println("收中介费");
  }
}

Client . java 即客户

//客户类,一般客户都会去找代理!
public class Client {
   public static void main(String[] args) {
       //房东要租房
       Host host = new Host();
       //中介帮助房东
       Proxy proxy = new Proxy(host);

       //你去找中介!
       proxy.rent();
  }
}

分析:在这个过程中,你直接接触的就是中介,就如同现实生活中的样子,你看不到房东,但是你依旧租到了房东的房子通过代理,这就是所谓的代理模式,程序源自于生活,所以学编程的人,一般能够更加抽象的看待生活中发生的事情。

静态代理的好处:

  • 可以使得我们的真实角色更加纯粹 . 不再去关注一些公共的事情 .
  • 公共的业务由代理来完成 . 实现了业务的分工 ,
  • 公共业务发生扩展时变得更加集中和方便 .

缺点 :

  • 类多了 , 多了代理类 , 工作量变大了 . 开发效率降低 .

我们想要静态代理的好处,又不想要静态代理的缺点,所以 , 就有了动态代理 !

1 静态代理再理解

我们再来举一个例子,巩固大家的学习!

练习步骤:

1、创建一个抽象角色,比如咋们平时做的用户业务,抽象起来就是增删改查!
//抽象角色:增删改查业务
public interface UserService {
   void add();
   void delete();
   void update();
   void query();
}
2、我们需要一个真实对象来完成这些增删改查操作
//真实对象,完成增删改查操作的人
public class UserServiceImpl implements UserService {

   public void add() {
       System.out.println("增加了一个用户");
  }

   public void delete() {
       System.out.println("删除了一个用户");
  }

   public void update() {
       System.out.println("更新了一个用户");
  }

   public void query() {
       System.out.println("查询了一个用户");
  }
}
3、新需求来了,现在我们需要增加一个日志功能,怎么实现!
  • 思路1 :在实现类上增加代码 【麻烦!】
  • 思路2:使用代理来做,能够不改变原来的业务情况下,实现此功能就是最好的了!
4、设置一个代理类来处理日志!代理角色
/代理角色,在这里面增加日志的实现
public class UserServiceProxy implements UserService {
   private UserServiceImpl userService;

   public void setUserService(UserServiceImpl userService) {
       this.userService = userService;
  }

   public void add() {
       log("add");
       userService.add();
  }

   public void delete() {
       log("delete");
       userService.delete();
  }

   public void update() {
       log("update");
       userService.update();
  }

   public void query() {
       log("query");
       userService.query();
  }

   public void log(String msg){
       System.out.println("执行了"+msg+"方法");
  }

}
5、测试访问类:
public class Client {
   public static void main(String[] args) {
       //真实业务
       UserServiceImpl userService = new UserServiceImpl();
       //代理类
       UserServiceProxy proxy = new UserServiceProxy();
       //使用代理类实现日志功能!
       proxy.setUserService(userService);

       proxy.add();
  }
}

OK,到了现在代理模式大家应该都没有什么问题了,重点大家需要理解其中的思想;

image-20230302202936411.png

聊聊AOP:纵向开发,横向开发

image-20230302203009467.png

2 动态代理

image-20230303161730100.png

2.1 JDK动态代理:

​ 利用拦截器(拦截器必须实现InvocationHanlder)加上反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。

JDK动态代理
1、实现InvocationHandler 
2、使用Proxy.newProxyInstance产生代理对象
3、被代理的对象必须要实现接口

2.2 JDK动态代理代码案例学习

  1. User.java
import lombok.Data;
@Data
public class User {
    private String id;
    private String name;
    private Integer age;
}
2. UserManager.java 创建一个接口
public interface UserManager {
    public void addUser(String id, String password);
}
3. UserManagerImpl.java 创建接口的实现类
public class UserManagerImpl implements UserManager {

    @Override
    public void addUser(String id, String password) {
        System.out.println("调用了UserManagerImpl.addUser()方法!"+id+"-"+password);
    }
}
4. DK动态代理-相关实现代码
package hx.dzxx.pojo;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;

/**
 * JDK动态代理具体实现原理:
 *
 * 1、通过实现InvocationHandler接口创建自己的调用处理器;
 * 2、通过为Proxy类指定ClassLoader对象和一组interface来创建动态代理;
 * 3、通过反射机制获取动态代理类的构造函数,其唯一参数类型就是调用处理器接口类型;
 * 4、通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数参入;
 */
public class JDKProxy implements InvocationHandler {

    private Object targetObject;

    /**
     * 将真实对象和代理对象建立联系,通过真实对象来返回一个代理对象(猜测该代理对象可能为真实对象的子类)
     * @param targetObject 真实对象
     * @return 代理对象
     */
    public Object newProxy(Object targetObject){
        this.targetObject = targetObject;

        /**
         * 参数1:类加载器,采用target本身的类加载器
         * 参数2:生成的动态代理对象挂在那个接口下,采用target实现的接口下,即Woker接口。
         * 参数3:定义实现方法逻辑的代理类,this表示当前对象,,它必须实现InvocationHandler的invoke方法。
         */
        return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),targetObject.getClass().getInterfaces(), this);
    }

    /**
     * 代理方法逻辑
     * @param proxy 代理对象
     * @param method 当前调度方法
     * @param args 当前方法参数
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        handle();
        System.out.println("拦截方法:" + method);
        System.out.println("方法参数:" + Arrays.toString(args));
        Object obj = method.invoke(targetObject,args);
        return obj;
    }


    private void handle(){
        //处理一些 增强的增强的增强的 业务逻辑
        System.out.println("JDKProxy处理业务逻辑, 增强的增强的增强的 业务逻辑");
    }
}

5. 创建一个测试类
public class ProxyTest {

    public static void main(String[] args) {
        JDKProxy jdkProxy = new JDKProxy();
        UserManager userManagerJDK = (UserManager)jdkProxy.newProxy(new UserManagerImpl());
        userManagerJDK.addUser("zhang3", "123456");
    }
}
6. 执行如上程序,输出结果
JDKProxy处理业务逻辑, 增强的增强的增强的 业务逻辑
拦截方法:public abstract void hx.dzxx.pojo.UserManager.addUser(java.lang.String,java.lang.String)
方法参数:[zhang3, 123456]
调用了UserManagerImpl.addUser()方法!zhang3-123456
7. 断点深入探究

debug执行一下对应的流程

1、首先是一个帮助类传入我们需要的对象后帮助我们生成一个代理对象
2、代理对象执行原对象的方法(功能)

image-20230303170137125.png

3、 真正执行方法的代码 Object obj = method.invoke(targetObject,args);

image-20230303170657589.png

8. 同理我们可以在此方法 Object obj = method.invoke(targetObject,args);前后新增方法可以增强原方法

JDK动态代理是java.lang.reflect.*包所提供的方式,它所代理的真实对象必须实现一个接口,依据该接口才能生成真实对象的代理

image-20230303171100055.png

3 CGlib代理

CGLIB(Code Generation Library),是一个强大的,高性能,高质量的 Code 生成类库,它可以在运行期扩展 Java 类与实现 Java 接口。

3.1 引入maven项目

        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.2.7</version>
        </dependency>

3.2 新建一个目标类

package com.hand.proxy.cglib;
 
public class HelloWorld {
	
	public void sayHello() {
		System.out.println("CGLIB动态代理模式!");
	}
 
}

3.3 创建代理类

package hx.dzxx.cglibproxy;
 
import java.lang.reflect.Method;
 
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
 
public class CGLIBExampleProxy implements MethodInterceptor{
	
	/**
	 * 指定cglib代理模式的代理类
	 */
 
	private Object target;
	
	public Object bind(Object target) {
		 this.target=target;
		 Enhancer enhancer=new Enhancer();
		 //设置超类方法
		 enhancer.setSuperclass(this.target.getClass());
		 //设置一个回调方法,用来设置哪个类为代理类,this表示当前类为代理类
		 enhancer.setCallback(this);
		 //创建代理对象
		return enhancer.create();
 
	}
 
	public Object intercept(Object obj, 
			Method method, Object[] args, 
			MethodProxy proxy) throws Throwable {
        //obj:表示目标对象
        //method:表示当前调度的方法,这里指的是
        //HelloWorld类里面的sayHello()方法args:表示执行方法的参数列表

MethodProxy: 表示执行目标方法的代理对象
		System.out.println("CGLIB代理前");
		Object object=proxy.invokeSuper(obj, args);
		System.out.println("CGLIB代理后");
		return object;
	}
	
}

3.4 客户端

package com.hand.proxy.cglib;

import hx.dzxx.cglibproxy.CGLIBExampleProxy;
import hx.dzxx.cglibproxy.HelloWorld;

public class CGLIBTest {
	
	public static void  main(String[]args) {
		//1.获取代理对象
		CGLIBExampleProxy proxy=new CGLIBExampleProxy();
		//2.获取代理对象
		HelloWorld hello=(HelloWorld)proxy.bind(new HelloWorld());
		//3.执行代理方法
		hello.sayHello();
	}
}

3.5 输出结果

CGLIB代理前
CGLIB动态代理模式!
CGLIB代理后

总结

image-20230303171213718.png

image-20230303162042001.png

Spring如何选择用JDK还是CGLiB?

image-20230303171308099.png

可能出现问题说明:Maven静态资源过滤问题

<resources>
   <resource>
       <directory>src/main/java</directory>
       <includes>
           <include>**/*.properties</include>
           <include>**/*.xml</include>
       </includes>
       <filtering>false</filtering>
   </resource>
   <resource>
       <directory>src/main/resources</directory>
       <includes>
           <include>**/*.properties</include>
           <include>**/*.xml</include>
       </includes>
       <filtering>false</filtering>
   </resource>
</resources>