小黑子—Java从入门到入土过程:第十一章 - 网络编程、反射及动态代理

网络编程

1. 初识网络编程

在这里插入图片描述
Java中可以使用java.net包下的技术秦颂开发出常见的网络应用程序。

常见的软件架构:bs和cs
在这里插入图片描述

在这里插入图片描述
BS架构:
在这里插入图片描述
优点:
在这里插入图片描述
缺点:
在这里插入图片描述

CS架构:
在这里插入图片描述
优点:
在这里插入图片描述

缺点:
在这里插入图片描述
小结:
在这里插入图片描述

2. 网络编程三要素

1、确定对方电脑在互联网上的地址(IP)
在这里插入图片描述

2、确定接收数据的软件(端口号)
一个端口号只能被一个软件绑定使用
在这里插入图片描述

3、确定网络传输的规则(协议)
在这里插入图片描述

IP:

  • 设备在网络中的地址,是唯一的标识 端口号:应用程序在设备中唯一的标识(设备包括电脑、笔记本、手机等)

协议:

  • 数据在网络中传输的规则,常见的

协议:

  • 数据在网络传输的规则,常见的协议有UDP、TCP、http、ftp

小结:
在这里插入图片描述

3.IP三要素

IP:
在这里插入图片描述

IPV4:
在这里插入图片描述
IPV6:
在这里插入图片描述
在这里插入图片描述
小结:

在这里插入图片描述

3.1 IPV4的细节

在这里插入图片描述
比如:

在网吧里面的很多台电脑,不是都在连接外网的时候都有一个公网的IP,它们往往是共享同一个公网IP,再由路由器给每一台电脑分配局域网IP,这样就可以实现节约IP的效果
在这里插入图片描述

3.1.1特殊的IP地址
  • 127.0.0.1,也就是localhost:是回送地址也称本地回环地址,也称本机IP,永远只会寻找当前所在本机

提问:

假设192.168.1.100是我电脑的IP,那么这个IP跟127.0.0.1是一样的吗?
不一样

案例:

  1. 现在以192.168.1.100发送数据的时候,此时是先发到路由器,路由器再找到你当前的IP,这样子才能实现数据的发送,但是此时会有个小细节——每一个路由器给你分配的IP是不一样的

所以在不同的地方上网,局域网IP有可能会不一样
在这里插入图片描述

  1. 如果要往127.0.0.1发送数据,是不经过路由器的,数据在经过网卡的时候,网卡会发现要往127.0.0.1发送就会直接把这个数据给自己发送回来
    在这里插入图片描述
3.1.2 常用的CMD命令

ipconfig:查看本机IP地址
在这里插入图片描述

ping:检查网络是否连通

这里是引用
小结:
在这里插入图片描述

3.2 InetAddress 的使用

此类表示互联网协议(IP)地址

   public static void main(String[] args) throws IOException {
        //获取InetAddress的对象
        //IP的对象 一台电脑的对象
        InetAddress address = InetAddress.getByName("Javris");
        System.out.println(address);

        String name = address.getHostName();
        System.out.println(name);

        String ip = address.getHostAddress();
        System.out.println(ip);
    }

3.3 端口号

在这里插入图片描述
端口说白了就可以理解成为——电脑往外发送数据或者是接收数据的出口or入口

案例:
电脑A向电脑B发送数据
在这里插入图片描述

3.4 协议

在这里插入图片描述
OSI模型:
在这里插入图片描述

TCP/IP模型:

在该模型当中把OSI的应用层、表示层、会话层给合并了——应用层,最后两层数据链路层、物理层合并了——物理链路层
在这里插入图片描述

3.4.1 UDP协议

在这里插入图片描述
面向无连接什么意思?
假设左边的电脑要给右边的电脑发送数据,在发送数据之前按道理来讲,要先检查两台电脑之间的网络是否畅通。但是,UDP协议是不会检查的,能收到就收到,收不到就算了
在这里插入图片描述
应用场景:适用于丢一点数据,也不会产生影响的情况。比如:网络会议、语音通话、看视频

3.4.1 - I UDP 发送数据

在这里插入图片描述
在这里插入图片描述

    public static void main(String[] args) throws IOException {
        DatagramSocket ds = new DatagramSocket();

        //打包数据
        String s1 = "李在赣神魔?";
        byte[] bytes = s1.getBytes();

        InetAddress address = InetAddress.getByName("127.0.01");
        //端口
        int port = 10086;

        DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port);

        ds.send(dp);
        ds.close();
    }

程序运行完毕,控制台没有消息是因为UDP的特点:不管你接不接得到,发了就行

3.4.1 - II UDP 接收数据

在这里插入图片描述
接收数据的时候只要给它传递一个数组就可以了
在这里插入图片描述

注意:在运行的时候要先运行接收端,再运行发送端

发送端:

public class Test1 {
    public static void main(String[] args) throws IOException {
        //创建DatagramSocket对象
        //细节:
        //绑定端口:以后我们就是通过这个端口往外发送
        //空参:所有可用的端口中随机一个进行使用
        //有参:指定端口号进行绑定
        DatagramSocket ds = new DatagramSocket();

        //打包数据
        String s1 = "李在赣神魔?";
        byte[] bytes = s1.getBytes();

        InetAddress address = InetAddress.getByName("127.0.01");
        //端口
        int port = 10086;

        DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port);

        ds.send(dp);
        ds.close();
    }
}

接收端:

public class Test2 {
    public static void main(String[] args) throws IOException {
        //1.创建DatagramSocker对象
        //细节:
        //在接收的时候,一定要绑定端口
        //而且绑定的端口一定要跟发送的端口保持一致

        DatagramSocket ds = new DatagramSocket(10086);

        //2.接收数据包
        byte[] bytes = new byte[1024];
        DatagramPacket dp = new DatagramPacket(bytes,bytes.length);//后者是用多大的空间去接收数据
        //相当于在接收数据的时候,还是把这个数据放到bytes这个数组里面

		//该方法是阻塞的
		//程序执行到这一步的时候,会在这里等死
		//等发送端发送消息
        ds.receive(dp);

        //3.解析数据	包
        //获取包的数据、长度、地址、端口
        byte[] data = dp.getData();
        int len = dp.getLength();
        InetAddress address = dp.getAddress();
        int port = dp.getPort();

        System.out.println("接收到数据"+new String(data,0,len));
        System.out.println("该数据是从"+address+"这台电脑"+"port"+"端口发出的");

        //4.释放资源
        ds.close();

    }
}

在这里插入图片描述

3.4.1 - III UDP 练习(聊天室)

在这里插入图片描述
修改IDEA设置:
在发送端选择编辑7
在这里插入图片描述
修改选项
在这里插入图片描述
勾选多个实例:
一定要勾选,不然没法同时运行多个
在这里插入图片描述

接收端:

    public static void main(String[] args) throws IOException {
        //创建对象接收端口
        DatagramSocket ds = new DatagramSocket(10086);

        //接收数据包,用数组
        byte[] bytes = new byte[1024];//为什么该数组和数据包不写入循环里面呢?
        // 因为每一次都拿同一个数组去接收数据,第二次可以把第一次的覆盖
        DatagramPacket dp = new DatagramPacket(bytes, bytes.length);

        while (true){
            ds.receive(dp);
            //解析数据包
            byte[] data = dp.getData();
            int len = dp.getLength();
            String ip = dp.getAddress().getHostAddress();
            String name = dp.getAddress().getHostName();

            //打印
            System.out.println("IP为:"+ip+",主机民为"+name+"的人,发送了数据"+new String(data,0,len));
        }
    }

发送端:
发送端设置完成了之后,可以运行多个

   public static void main(String[] args) throws IOException {
        DatagramSocket ds = new DatagramSocket();//相当于一个快递公司,其不肯能进行循环

        //输入数据,进行打包
        Scanner sc = new Scanner(System.in);
        //下面的快递可以无休止地进行循环
        while(true){
            System.out.println("请输入想要说出的话:");
            String str = sc.nextLine();
            byte[] bytes = str.getBytes();
            if("886".equals(str)){
                break;
            }
            //建立地址、端口,然后打包进去
            InetAddress address = InetAddress.getByName("127.0.01");
            int port = 10086;
            DatagramPacket dp = new DatagramPacket(bytes, bytes.length, address, port);

            //3.发送数据
            ds.send(dp);
        }

        //4.释放资源
        ds.close();
    }

在这里插入图片描述

3.4.2 UDP的三种通信方式

单播、组播和广播

1、单播
一对一
以前的代码就是单播
在这里插入图片描述

2、组播
一对多(一组)、
组播地址:224.0.0.0~239.255.255.255
其中224.0.0.0~224.0.0.255为预留的组播地址
在这里插入图片描述

3、广播
一对全(全部)
广播地址:255.255.255.255

在这里插入图片描述

3.4.3 TCP协议

在这里插入图片描述
它在发数据的时候,会先去检查两台电脑之间的网络是否畅通,就是确保连接成功才会发送数据——面向连接
在这里插入图片描述
应用场景:下载软件、文字聊天、发送邮件

package socketnet.Dome4;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;

public class dome2 {
    public static void main(String[] args) throws IOException {
        MulticastSocket ms = new MulticastSocket();

        //打包数据
        String str = "你好啊。。。";
        byte[] bytes = str.getBytes();
        //发送的地址
        InetAddress address = InetAddress.getByName("224.0.0.1");
        //要发送的端口号
        int port = 10000;

        DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port);
        //发送数据
        ms.send(dp);
        //释放资源
        ms.close();
    }
}
package socketnet.Dome4;

import java.io.IOException;
import java.net.*;

public class dome1 {
    public static void main(String[] args) throws IOException {
        MulticastSocket ms = new MulticastSocket(10000);

        //将当前主机,添加到224.0.0.1的这一组当中
        InetAddress address = InetAddress.getByName("224.0.0.1");
        ms.joinGroup(address);

        //接收数据包
        byte[] bytes = new byte[1024];
        DatagramPacket dp = new DatagramPacket(bytes, bytes.length);

        //该方法是堵塞的
        //程序执行到这一步的时候,会在这里死等
        //等发送端发送消息
        ms.receive(dp);

        //解析数据包
        byte[] data = dp.getData();
        int len = dp.getLength();
        int port = dp.getPort();

        System.out.println("接收到的数据"+new String(data,0,len));
        System.out.println("该数据是从"+address+"这台电脑中的"+port+"这个端口发出的");

        //释放资源
        ms.close();
    }
}
package socketnet.Dome4;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;

public class dome {
    public static void main(String[] args) throws IOException {
        MulticastSocket ms = new MulticastSocket(10000);

        //将当前主机,添加到224.0.0.1的这一组当中
        InetAddress address = InetAddress.getByName("224.0.0.1");
        ms.joinGroup(address);

        //接收数据包
        byte[] bytes = new byte[1024];
        DatagramPacket dp = new DatagramPacket(bytes, bytes.length);

        //该方法是堵塞的
        //程序执行到这一步的时候,会在这里死等
        //等发送端发送消息
        ms.receive(dp);

        //解析数据包
        byte[] data = dp.getData();
        int len = dp.getLength();
        int port = dp.getPort();

        System.out.println("接收到的数据"+new String(data,0,len));
        System.out.println("该数据是从"+address+"这台电脑中的"+port+"这个端口发出的");

        //释放资源
        ms.close();
    }
}

package socketnet.Dome5;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;

public class dome {
    public static void main(String[] args) throws IOException {
        MulticastSocket ms = new MulticastSocket();

        //打包数据
        String str = "你好啊。。。";
        byte[] bytes = str.getBytes();
        //发送的地址
        InetAddress address = InetAddress.getByName("255.255.255.255");
        //要发送的端口号
        int port = 10000;

        DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port);
        //发送数据
        ms.send(dp);
        //释放资源
        ms.close();
    }
}

3.4.3 - I TCP 发送和接收数据

TCP通信协议是一种可靠的网络协议,它在通信的两端各建立一个Socket对象
通信之前要保证连接已经建立
通过Socket产生IO流来进行网络通信
在这里插入图片描述

在这里插入图片描述
客户端(Socket)

  1. 创建客户端的Socket对象(Socket)与指定服务端连接
    Socket(String host , int port)
  2. 获取输出流,写数据
    OutputStream getOutputstream()
  3. 释放资源
    void close

服务器(ServerSocket)

  1. 创建服务器端的Socket对象(ServerSocket)
    ServerSocket(int port)

  2. 监听客户端连接,返回一个Socket对象
    Socket accept()

  3. 获取输入流,读数据,并把数据显示在控制台
    InputStreamReader getInputStreamReader(socket.getInputStream )

  4. 释放资源
    void close

先来运行服务端(接收的):

   public static void main(String[] args) throws IOException {
        //TCP协议,接收数据
        //1.创建 ServerSocker对象
        ServerSocket ss = new ServerSocket(1000);
        //2.监听客户端的链接
        Socket socket = ss.accept();

        //3.从连接通道中获取输入流读取数据
        InputStream is = socket.getInputStream();
        int b ;
        while((b=is.read())!=-1){
            System.out.println((char) b);
        }

        socket.close();
        ss.close();


    }

再运行客户端(发送的):

    public static void main(String[] args) throws IOException {
     //TCP协议,发送数据

        //1.创建Socket对象
        //细节:在创建对象的同时会连接服务器
        //      如果连接不上,代码会报错
        Socket socket = new Socket("127.0.01",1000);
        //2.可以从连接通道中获取输出流
        OutputStream os = socket.getOutputStream();
        os.write("aaa".getBytes());//这种方式不能传中文

        //3.释放资源
        os.close();
        socket.close();
    }

在这里插入图片描述

3.4.3 - II TCP 中文乱码问题

为什么会出现乱码问题呢?
在这里插入图片描述
在使用IDEA默认的编码表(UTF-8),此时就会把一个中文变成3个字节,服务器在读的时候是一个字节一个字节地读,所以每次转码的时候只是1/3个中文,造成了乱码

解决方案:

  1. 不要拿InputStream字节流去读了,把其变成字符流InputStreamReader,就不会出现乱码
    在这里插入图片描述
  2. 如果说想要提高读取的效率,那么就再把字符流变成缓冲流BufferedReader
    在这里插入图片描述
3.4.3 - III TCP 代码细节

在这里插入图片描述

3.4.4 TCP通信程序
3.4.4 - I 三次挥手

在这里插入图片描述

3.4.4 - II 四次挥手

多了一次:需要保证已经把连接通道里面的数据处理完毕了,这个时候的连接才能断开
在这里插入图片描述

4. 综合练习

4.1 多发多收

在这里插入图片描述

   public static void main(String[] args) throws IOException {
        //服务器
        ServerSocket ss = new ServerSocket(10000);

        //等待客户端连接
        Socket socket = ss.accept();

        //读取数据
        InputStreamReader isr = new InputStreamReader(socket.getInputStream());
        int b;
        while((b=isr.read())!=-1){
            System.out.print((char)b);
            
        }

        socket.close();
        ss.close();
    }
    public static void main(String[] args) throws IOException {
     //客户端
        Socket socket = new Socket("127.0.01", 10000);

        Scanner sc = new Scanner(System.in);
        OutputStream os = socket.getOutputStream();

        while(true){
            System.out.println("请输入信息:");
            String str = sc.nextLine();
            if("886".equals(str)){
                break;
            }
            os.write(str.getBytes());//getBytes(): 使用平台的默认字符集将字符串编码为 byte
        }

        os.close();
    }

在这里插入图片描述

4.2 接收并反馈

在这里插入图片描述
思路:
在这里插入图片描述

服务器:

   public static void main(String[] args) throws IOException {
        //服务器
        ServerSocket ss = new ServerSocket(10000);

        //等待客户端连接
        Socket socket = ss.accept();

        //socket中获取输入流读取数据
        InputStream is = socket.getInputStream();
        InputStreamReader isr = new InputStreamReader(is);
        int b;
        //细节:
        //read方法会从连接通道中读取数据
        //但是,需要有一个结束标记,此处的循环才会停止
        //否则,程序会一致停在reead方法这里,等待读取下面的数据
        while((b=isr.read())!=-1){
            System.out.println((char)b);
        }

        //回写数据
        String str = "乖乖站好";
        OutputStream os = socket.getOutputStream();
        os.write(str.getBytes());

        socket.close();
        ss.close();
    }

客户端:

   public static void main(String[] args) throws IOException {
     //客户端
        Socket socket = new Socket("127.0.01", 10000);

        //写出数据
        String str = "李在赣神魔?";
        OutputStream os = socket.getOutputStream();
        os.write(str.getBytes());
        //写出一个结束标记
        socket.shutdownOutput();


        //接收服务端回写的数据
        InputStream is = socket.getInputStream();
        InputStreamReader isr = new InputStreamReader(is);
        int b;
        while((b= isr.read())!=-1){
            System.out.println((char) b);
        }
        socket.close();
    }

在这里插入图片描述

4.3 上传文件

结合本地IO流与网络IO流结合起来
在这里插入图片描述
服务器:

    public static void main(String[] args) throws IOException {
        //服务器
        ServerSocket ss = new ServerSocket(10000);

        //等待客户端连接
        Socket socket = ss.accept();

        //读取数据并保存到本地文件当中
        BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("javaprogram1\\b.txt"));
        int len;
        byte[] bytes = new byte[1024];
        while((len=bis.read())!=-1){
            bos.write(bytes,0,len);
        }

        //回写数据
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
        bw.write("上传成功");
        bw.newLine();
        bw.flush();

        socket.close();
        ss.close();

    }

客户端:

    public static void main(String[] args) throws IOException {
     //客户端
        Socket socket = new Socket("127.0.01", 10000);

        //读取本地文件中的数据,并写到服务器当中
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("javaprogram1\\a.txt"));
        BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
        byte[] bytes = new byte[1024];
        int len;
        while((len=bis.read())!=-1){
            bos.write(bytes,0,len);//在本地读了多少,就立马把多少数据写到客户端当中
        }
        //往服务器写出结束标记
        socket.shutdownOutput();

        //接受服务端的回写数据
        BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));//相当于就是先获取到连接通道里面的输入流,字节流->字符流->再用缓冲流进行包裹
        String line = br.readLine();//这个line就是服务器回写的数据
        System.out.println(line);

        socket.close();
    }

4.4 文件名重复

在这里插入图片描述
Java里面有个UUID类

  • 可以使用该类生成一个随机的文件名
  • 可以使用静态的randomUUID方法,返回一个UUID对象
    public static void main(String[] args) throws IOException {
        System.out.println(UUID.randomUUID());
        String str = UUID.randomUUID().toString().replace("-", "");//随机生成的UUID
        System.out.println(str);
    }

在这里插入图片描述
以此修改上方的服务端代码文件:

   public static void main(String[] args) throws IOException {
        //服务器
        ServerSocket ss = new ServerSocket(10000);

        //等待客户端连接
        Socket socket = ss.accept();

        //读取数据并保存到本地文件当中
        BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
        String name = UUID.randomUUID().toString().replace("-", "");
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("javaprogram1\\+"+name+"txt"));
        int len;
        byte[] bytes = new byte[1024];
        while((len=bis.read())!=-1){
            bos.write(bytes,0,len);
        }

        //回写数据
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
        bw.write("上传成功");
        bw.newLine();
        bw.flush();

        socket.close();
        ss.close();

    }

在这里插入图片描述

4.5 多线程版的服务端上传文件

在这里插入图片描述
MyRunnable类:

public class MyRunnable implements Runnable{

    Socket socket;
    public MyRunnable(Socket socket){
        this.socket = socket;
    }

    //有一个问题里面原本是没有socket的,这时该怎么办?
    //当开启一条线程的时候,需要把客户端的连接对象传递给线程,那么就可以通过构造方法来传递
    @Override
    public void run() {
        try {
            //读取数据并保存到本地文件当中
            BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
            String name = UUID.randomUUID().toString().replace("-", "");
            BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("javaprogram1\\+" + name + "txt"));
            int len;
            byte[] bytes = new byte[1024];
            while ((len = bis.read()) != -1) {
                bos.write(bytes, 0, len);
            }

            //回写数据
            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
            bw.write("上传成功");
            bw.newLine();
            bw.flush();

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(socket!=null){
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

服务器:

    public static void main(String[] args) throws IOException {
        //服务器
        ServerSocket ss = new ServerSocket(10000);
        while (true){
            //等待客户端连接
            Socket socket = ss.accept();
            //开启一条线程
            //一个用户就对应服务端的一条线程
            new Thread(new MyRunnable(socket)).start();
        }

    }

4.6 线程池的服务端

在这里插入图片描述

    public static void main(String[] args) throws IOException {
       //创建线程池对象,7个参数,ctrl+p提示
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                3,//核心线程数量
                16,//线程池总大小
                60,//空闲时间
                TimeUnit.SECONDS,//单位空闲时间
                new ArrayBlockingQueue<>(2),//2个队列
                Executors.defaultThreadFactory(),//线程工厂,让线程池如何创建线程对象
                new ThreadPoolExecutor.AbortPolicy()//阻塞队列
        );

        //服务器
        ServerSocket ss = new ServerSocket(10000);
        while (true){
            //等待客户端连接
            Socket socket = ss.accept();
            //开启一条线程
            //一个用户就对应服务端的一条线程
//            new Thread(new MyRunnable(socket)).start();
            pool.submit(new MyRunnable(socket));
        }

    }

多次运行后:
在这里插入图片描述

4.7 BS架构模型

在这里插入图片描述

  • 运行该代码然后直接在浏览器中打开该网站
  • 打开网站之后会将数据返回到IDEA控制台中

比如:
运行服务端

   public static void main(String[] args) throws IOException {
        //创建对象绑定端口
        ServerSocket ss = new ServerSocket(10000);
        //等待客户端来连接
        Socket socket = ss.accept();
        //读取数据
        InputStreamReader is = new InputStreamReader(socket.getInputStream());

        int len;
        while ((len=is.read())!=-1){
            System.out.print((char) len);
        }
        socket.close();
        ss.close();
    }

在这里插入图片描述
在IDEA返回的数据:
在这里插入图片描述

反射

5. 初识反射

什么是反射?

反射允许对成员变量,成员方法和构造方法的信息进行编程访问
在这里插入图片描述
能把这个类扒的干干净净,一点都不剩
IDEA里面的提示功能就是个反射
在这里插入图片描述
利用反射就可以获取这个类里面的所有信息

问题:既然是从里面拿东西,为什么一定要有反射,用IO流不行吗?

IO流是从上往下一行一行地去读程序

  • 当读到构造方法和普通成员方法的时候很难进行区分
    在这里插入图片描述
  • 如果说想用返回值去区分,那么成员变量和局部变量应该无法去区分
    在这里插入图片描述
    那么反射要学什么东西呢?
    学习反射的获取和解剖
    在这里插入图片描述
    反射的作用:
    在这里插入图片描述

6. 获取class对象的三种方式

在这里插入图片描述

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        //1.第一种方式,最为常用的
        //全类名:包名+类名
        Class<?> clazz1 = Class.forName("test6.Student");

        //2.第二种方式
        //一般更多的是当做参数进行传递
        Class clazz2 = Student.class;
        System.out.println(clazz1==clazz2);//true

        //3.第三种方式
        //当已经有了这个类的对象的时候,才可以进行时候
        Student stu = new Student();
        Class clazz3 = stu.getClass();
        System.out.println(clazz2==clazz3);//true
    }

7. 反射获取构造方法

在这里插入图片描述
student类:

public class Student {

    private static final long serialVersionUID = 650157805827481085L;
    private String name;
    private int age;
    private String address;


    public Student() {
    }

    public Student( String name, int age, String address) {
        this.name = name;
        this.age = age;
        this.address = address;
    }

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

    protected Student(int age){
        this.age = age;
    }

    private Student(String name,int age){
        this.name = name;
        this.age = age;
    }

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

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

    /**
     * 获取
     * @return age
     */
    public int getAge() {
        return age;
    }

    /**
     * 设置
     * @param age
     */
    public void setAge(int age) {
        this.age = age;
    }

    /**
     * 获取
     * @return address
     */
    public String getAddress() {
        return address;
    }

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

    public String toString() {
        return "Student{name = " + name + ", age = " + age + ", address = " + address + "}";
    }
}

    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        //1.获取class字节码文件对象
        Class<?> clazz1 = Class.forName("test6.Student");

        //2.获取构造方法
        Constructor[] cons1 = clazz1.getConstructors();
        for (Constructor con : cons1) {
            System.out.println(con);
        }
        System.out.println("====================");

        Constructor[] cons2 = clazz1.getDeclaredConstructors();
        for (Constructor con : cons2) {
            System.out.println(con);
        }
        System.out.println("===================");

        //获取单个的,getDeclaredConstructor只是能让我们看到这个构造,但是还是无法直接用这个构造去创建对象
        Constructor con3 = clazz1.getDeclaredConstructor();
        System.out.println(con3);

        Constructor con4 = clazz1.getDeclaredConstructor(String.class);
        System.out.println(con4);

        Constructor con5 = clazz1.getDeclaredConstructor(int.class);
        System.out.println(con5);

        Constructor con6 = clazz1.getDeclaredConstructor(String.class,int.class);
        System.out.println(con6);

        int modifiers = con6.getModifiers();
        System.out.println(modifiers);//返回当前方法的权限修饰符个数
        System.out.println("======================");

        Parameter[] parameters = con4.getParameters();//获取该构造方法的参数
        for (Parameter parameter : parameters) {
            System.out.println(parameter);
        }

        //暴力反射:表示临时取消权限校验
        con6.setAccessible(true);
        Object stu = (Student)con6.newInstance("麻瓜", 25);
        System.out.println(stu);
    }

在这里插入图片描述

8. 反射获取成员变量

在这里插入图片描述
Student类:

public class Student {

    private static final long serialVersionUID = 650157805827481085L;
    private String name;
    private int age;
    private String address;


    public Student() {
    }

    public Student(String name, int age, String address) {
        this.name = name;
        this.age = age;
        this.address = address;
    }

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

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

    /**
     * 获取
     * @return age
     */
    public int getAge() {
        return age;
    }

    /**
     * 设置
     * @param age
     */
    public void setAge(int age) {
        this.age = age;
    }

    /**
     * 获取
     * @return address
     */
    public String getAddress() {
        return address;
    }

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

    public String toString() {
        return "Student{name = " + name + ", age = " + age + ", address = " + address + "}";
    }
}

测试类:

    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {
        //1.获取class字节码文件的对象
        Class clazz = Class.forName("test6.Student");
        //2.获取所有的成员变量
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            System.out.println(field);
        }
        System.out.println("============================");

        //获取单个的成员变量
//        Field address = clazz.getField("address");//获取不到私有的
        Field address = clazz.getDeclaredField("address");//可以获取到私有的
        System.out.println(address);
        System.out.println("==============================");

        //获取权限修饰符
        Field name = clazz.getDeclaredField("name");
        System.out.println(name);

        //获取成员变量的名字
        String n = name.getName();
        System.out.println(n);

        //获取成员变量的数据类型
        Class<?> type = name.getType();
        System.out.println(type);
        System.out.println("===================================");

        //获取成员变量记录的值,这个值和对象有关系
        //所以想要获取值就要先创建一个对象
        Student stu = new Student("里皮",26,"a");
        name.setAccessible(true);//允许私有访问
        Object value = name.get(stu);
        System.out.println(value);

        //修改对象里面记录的值
        name.set(stu,"麻瓜");
        System.out.println(stu);
    }

在这里插入图片描述

9. 反射获取成员方法

在这里插入图片描述

    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {
        //1.获取class字节码文件的对象
        Class clazz = Class.forName("test6.Student");

        //2.获取里面所有方法对象,包括继承了父类中的所有公共方法
        Method[] methods = clazz.getMethods();
        for (Method method : methods) {
            System.out.println(method);
        }
        System.out.println("===================================");

        //获取里面所有的方法对象,不能获取父类的,能获取本类中私有的方法
        Method[] methods2 = clazz.getDeclaredMethods();
        for (Method method : methods2) {
            System.out.println(method);
        }
        System.out.println("========================================");

        //获取指定的单一方法
        Method m = clazz.getDeclaredMethod("eat", String.class);//获取指定的方法避免重复
        System.out.println(m);

        //获取方法的修饰符
        int modifiers = m.getModifiers();
        System.out.println(modifiers);

        //获取方法的名字
        String name = m.getName();
        System.out.println(name);

        //获取方法的形参
        Parameter[] parameters = m.getParameters();
        for (Parameter parameter : parameters) {
            System.out.println(parameter);
        }

        //获取方法抛出的异常
        Class<?>[] exceptionTypes = m.getExceptionTypes();
        for (Class<?> exceptionType : exceptionTypes) {
            System.out.println(exceptionType);
        }

        //方法运行
        /*
        Method类中用于创建对象的方法
        object invoke(object obj, object. . . args):运行方法
         */
        Student stu = new Student();
        m.setAccessible(true);
        //参数一:表示方法的调用者
        //参数二:表示在调用方法的时候传递的时间参数
        Object result = m.invoke(stu, "小老板");
        System.out.println(result);//获取返回值

    }

在这里插入图片描述

10. 综合练习

10.1 保存信息

在这里插入图片描述

public class Test {
    public static void main(String[] args) throws IllegalAccessException, IOException {
        Student stu = new Student("麻瓜",26,'男',10.11,"装老师傅");
        Teacher t = new Teacher("小老板",60);

         saveObject(stu);
    }

    // 把对象里面所有的成员变量名和值保存到本地文件当中
    public  static void saveObject(Object obj) throws IllegalAccessException, IOException {
        //1.获取字节码文字的对象
        Class clazz = obj.getClass();

        //IO流
        BufferedWriter bw = new BufferedWriter(new FileWriter("javaprogram1\\b.txt"));

        //2.获取所有的成员变量
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            //获取成员变量的名字
            String name = field.getName();
            //获取成员变量的值
            Object value = field.get(obj);
            bw.write(name+"="+value);
            bw.newLine();

        }
        bw.close();

    }
}

在这里插入图片描述

10.2 跟配置文件结合动态创建

在这里插入图片描述

Techaer类:

public class Teacher {
    private String name;
    private int salary;


    public Teacher() {
    }

    public Teacher(String name, int salary) {
        this.name = name;
        this.salary = salary;
    }

    public void teach(){
        System.out.println("老师在睡觉!");
    }

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

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

    /**
     * 获取
     * @return salary
     */
    public int getSalary() {
        return salary;
    }

    /**
     * 设置
     * @param salary
     */
    public void setSalary(int salary) {
        this.salary = salary;
    }

    public String toString() {
        return "Teacher{name = " + name + ", salary = " + salary + "}";
    }
}

配置文件:
在这里插入图片描述
测试类:

public class Test {
    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        //1.读取配置文件中的信息
        Properties prop = new Properties();
        //把配置文件的信息加载到IO流
        FileInputStream fis = new FileInputStream("javaprogram1\\prop.properties");
        prop.load(fis);//把配置文件的信息读取到了集合当中
        fis.close();
        System.out.println(prop);

        //2.获取全类名
        String classsName = (String) prop.get("classname");
        String  methodName = (String) prop.get("method");
        System.out.println(classsName);
        System.out.println(methodName);

        //3.利用反射创建对象并运行方法
        //获取其字节码文件对象
        Class clazz = Class.forName(classsName);

        //获取构造方法
        Constructor con = clazz.getDeclaredConstructor();
        Object o = con.newInstance();//获取其对象
        System.out.println(o);

        //获取成员方法并运行
        Method method = clazz.getDeclaredMethod(methodName);
        method.setAccessible(true);
        method.invoke(o);
    }
}

在这里插入图片描述
小结:
在这里插入图片描述

动态代理

11. 动态代理的思想分析

假设给下面这个方法去增加其他的功能
在这里插入图片描述
按照以前所学,只能直接修改代码,把其写在该方法当中——侵入式修改

在这里插入图片描述
但是在一个成熟的项目中很少会这么去做,因为一旦修改了可能就会引起连锁反应

这时就需要用到动态代理:
在这里插入图片描述
比如:
在这里插入图片描述
那么这个代理是如何能知道用户要代理唱歌和跳舞的呢?

通过接口来搞定
在这里插入图片描述

代码展示:

star类:
public class star implements mystar{
    private String name;
    public star() {
    }
    @Override
    public String sing(String name){
        System.out.println(this.name+"唱:"+name);
        return "谢谢大家";
    }
    @Override
    public void dance(String name){
        System.out.println(this.name+"正在跳"+name);
    }
    public star(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String toString() {
        return "star{name = " + name + "}";
    }
}

接口:
public interface mystar {
    //把所有想要代理的方法写到接口当中
    public abstract  String sing(String name);
    public abstract void dance(String name);
}

小结:
在这里插入图片描述

12. 动态代理的代码实现

如何为Java对象创建一个代理对象?
在这里插入图片描述
MyStar接口:

public interface MyStar {
    //唱歌
    public abstract String sing(String name);

    //跳舞
    public abstract void dance();
}

BigStar类:

public class BigStar implements MyStar{
    private String name;


    public BigStar() {
    }

    public BigStar(String name) {
        this.name = 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 + "}";
    }

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

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

代理:

public class ProxyUtil {
    /*
    该方法的作用:
    给一个明星的对象,创建一个代理

    形参:被代理的明星对象

    返回值:给明星创建的代理

    需求:
        外面的人想要大明星唱一首个
        1.获取代理的对象
            代理对象 = ProxyUtil.createProxy(大明星的对象)

         2.再调用代理的唱歌方法
            代理对象.唱歌的方法
     */

    public static MyStar createProxy(BigStar bigStar){
        /*
        参数一:用于指定哪个类加载器,去加载生成的代理类
        参数二:指定接口,这些接口用于指定生成的代理长什么,也就是有哪些方法
        参数三:用来指定生成的代理对象要干什么事情
         */

        MyStar star = (MyStar) Proxy.newProxyInstance(
                ProxyUtil.class.getClassLoader(),//类加载器加载字节码文件
                new Class[]{MyStar.class},//以数组的方式来体现,中介它可以代MyStart这个接口的所有方法
                new InvocationHandler() {
                    //当没有强转成接口的类时,Proxy.newProxyInstance返回的是Object类型,而用Mystart去接收就肯定会报错
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        /*
                        参数一:代理的对象
                        参数二:
                         */
                        if("sing".equals(method.getName())){
                            System.out.println("准备话筒,收钱");
                        }else if("dance".equals(method.getName())){
                            System.out.println("准备场地,收钱");
                        }
                        //去找大明星开始唱歌或者跳舞
                        //代码的表现形式:调用大明星里面唱歌或者跳舞的方法
                        return method.invoke(bigStar,args);
                    }
                }
        );
        return star;
    }
}

测试类:

public class Test {
    public static void main(String[] args) {
        //1.获取代理的对象
        BigStar bigStar = new BigStar("小黑子");
        MyStar proxy = ProxyUtil.createProxy(bigStar);

        //2.调用唱歌的方法
        String res = proxy.sing("只因");
        System.out.println(res);

        //3.调用跳舞的方法
        proxy.dance();
    }
}

在这里插入图片描述
代码思路:
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值