javab笔记

第十三周

Socket

一、Java 网络编程

网络编程是指编写运行在多个设备(计算机)的程序,这些设备都通过网络连接起来。

TCP:TCP(Transmission Control Protocol传输控制协议) 是一种面向连接的、可靠的、基于字节流的传输层通信协议,TCP 层是位于 IP 层之上,应用层之下的中间层。TCP 保障了两个应用程序之间的可靠通信。通常用于互联网协议,被称 TCP / IP。

UDP:UDP (User Datagram Protocol用户数据报协议),位于 OSI 模型的传输层。一个无连接的协议。提供了应用程序之间要发送数据的数据报。由于UDP缺乏可靠性且属于无连接协议,所以应用程序通常必须容许一些丢失、错误或重复的数据包。

1、Socket 编程

套接字使用TCP提供了两台计算机之间的通信机制。 客户端程序创建一个套接字,并尝试连接服务器的套接字。当连接建立时,服务器会创建一个 Socket 对象。客户端和服务器现在可以通过对 Socket 对象的写入和读取来进行通信。java.net.Socket 类代表一个套接字,并且 java.net.ServerSocket 类为服务器程序提供了一种来监听客户端,并与他们建立连接的机制。

  1. Socket整体流程

  1. Socket编程主要涉及到客户端和服务端两个方面,首先是在服务器端创建一个服务器套接字(ServerSocket),并把它附加到一个端口上,服务器从这个端口监听连接。端口号的范围是0到65536,但是0到1024是为特权服务保留的端口号,我们可以选择任意一个当前没有被其他进程使用的端口。

2、ServerSocket 类的方法

方法

方法描述

public ServerSocket(int port) throws IOException

创建绑定到特定端口的服务器套接字。

public ServerSocket(int port, int backlog) throws IOException

利用指定的 backlog 创建服务器套接字并将其绑定到指定的本地端口号。

public ServerSocket(int port, int backlog, InetAddress address) throws IOException

使用指定的端口、侦听 backlog 和要绑定到的本地 IP 地址创建服务器。

public ServerSocket() throws IOException

创建非绑定服务器套接字。

服务器应用程序通过使用java.net.ServerSocket 类以获取一个端口,并且侦听客户端请求。

ServerSocket 有以上四个构造方法。

创建非绑定服务器套接字。 如果 ServerSocket 构造方法没有抛出异常,就意味着你的应用程序已经成功绑定到指定的端口,并且侦听客户端请求。

这里有一些 ServerSocket 类的常用方法:

方法

方法描述

public int getLocalPort()

返回此套接字在其上侦听的端口。

public Socket accept() throws IOException

侦听并接受到此套接字的连接。

public void setSoTimeout(int timeout)

通过指定超时值启用/禁用 SO_TIMEOUT,以毫秒为单位。

public void bind(SocketAddress host, int backlog)

将 ServerSocket 绑定到特定地址(IP 地址和端口号)。


3、Socket 类的方法

java.net.Socket 类代表客户端和服务器都用来互相沟通的套接字。客户端要获取一个 Socket 对象通过实例化 ,而 服务器获得一个 Socket 对象则通过 accept() 方法的返回值。

Socket 类有五个构造方法.

方法

方法描述

public Socket(String host, int port) throws UnknownHostException, IOException.

创建一个流套接字并将其连接到指定主机上的指定端口号。

public Socket(InetAddress host, int port) throws IOException

创建一个流套接字并将其连接到指定 IP 地址的指定端口号。

public Socket(String host, int port,

 InetAddress localAddress, int localPort) throws IOException.

创建一个套接字并将其连接到指定远程主机上的指定远程端口。

public Socket(InetAddress host,

int port, InetAddress localAddress,

int localPort) throws IOException.

创建一个套接字并将其连接到指定远程地址上的指定远程端口。

public Socket()

通过系统默认类型的 SocketImpl 创建未连接套接字

当 Socket 构造方法返回,并没有简单的实例化了一个 Socket 对象,它实际上会尝试连接到指定的服务器和端口。

下面列出了一些感兴趣的方法,注意客户端和服务器端都有一个 Socket 对象,所以无论客户端还是服务端都能够调用这些方法。

方法

方法描述

public void connect(SocketAddress host, int timeout) throws IOException

将此套接字连接到服务器,并指定一个超时值。

public InetAddress getInetAddress()

返回套接字连接的地址。

public int getPort()

返回此套接字连接到的远程端口。

public int getLocalPort()

返回此套接字绑定到的本地端口。

public SocketAddress getRemoteSocketAddress()

返回此套接字连接的端点的地址,如果未连接则返回 null。

public InputStream getInputStream() throws IOException

返回此套接字的输入流。

public OutputStream getOutputStream() throws IOException

返回此套接字的输出流。

public void close() throws IOException

关闭此套接字。


4、InetAddress 类的方法

方法

方法描述

static InetAddress getByAddress(byte[] addr)

在给定原始 IP 地址的情况下,返回 InetAddress 对象。

static InetAddress getByAddress(String host, byte[] addr)

根据提供的主机名和 IP 地址创建 InetAddress。

static InetAddress getByName(String host)

在给定主机名的情况下确定主机的 IP 地址。

String getHostAddress()

返回 IP 地址字符串(以文本表现形式)。

String getHostName()

获取此 IP 地址的主机名。

static InetAddress getLocalHost()

返回本地主机。

String toString()

将此 IP 地址转换为 String。

这个类表示互联网协议(IP)地址。上面列出了 Socket 编程时比较有用的方法:

Socket 服务端实例

如下的GreetingServer 程序是一个服务器端应用程序,使用 Socket 来监听一个指定的端口。

import java.net.*;

import java.io.*;

public class GreetingServer extends Thread

{

   private ServerSocket serverSocket;

  

   public GreetingServer(int port) throws IOException

   {

      serverSocket = new ServerSocket(port);

      serverSocket.setSoTimeout(10000);

   }

   public void run()

   {

      while(true)

      {

         try

         {

            System.out.println("等待远程连接,端口号为:" + serverSocket.getLocalPort() + "...");

            Socket server = serverSocket.accept();

            System.out.println("远程主机地址:" + server.getRemoteSocketAddress());

            DataInputStream in = new DataInputStream(server.getInputStream());

            System.out.println(in.readUTF());

            DataOutputStream out = new DataOutputStream(server.getOutputStream());

            out.writeUTF("谢谢连接我:" + server.getLocalSocketAddress() + "\nGoodbye!");

            server.close();

         }catch(SocketTimeoutException s)

         {

            System.out.println("Socket timed out!");

            break;

         }catch(IOException e)

         {

            e.printStackTrace();

            break;

         }

      }

   }

   public static void main(String [] args)

   {

      int port = Integer.parseInt(args[0]);

      try

      {

         Thread t = new GreetingServer(port);

         t.run();

      }catch(IOException e)

      {

         e.printStackTrace();

      }

   }

}

反射

一、反射概念

Java 反射机制是 Java 语言的一个重要特性。在学习 Java 反射机制前,先了解两个概念,编译期和运行期。

编译期是指把源码交给编译器编译成计算机可以执行的文件的过程。在 Java 中也就是把 Java 代码编成 class 文件的过程。编译期只是做了一些翻译功能,并没有把代码放在内存中运行起来,而只是把代码当成文本进行操作,比如检查错误。

运行期是把编译后的文件交给计算机执行,直到程序运行结束。所谓运行期就把在磁盘中的代码放到内存中执行起来。

Java 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为 Java 语言的反射机制。简单来说,反射机制指的是程序在运行时能够获取自身的信息

Java 反射机制主要提供了以下功能,这些功能都位于java.lang.reflect包。

  1. 在运行时判断任意一个对象所属的类。
  2. 在运行时构造任意一个类的对象。
  3. 在运行时判断任意一个类所具有的成员变量和方法。 ​
  4. 在运行时调用任意一个对象的方法。
  5. 生成动态代理。

想知道一个类的属性和方法,必须先获取到该类的字节码文件对象。

Java 类均继承了 Object 类,在 Object 类中定义了一个 getClass() 方法,该方法返回同一个类型为 Class 的对象。例如,下面的示例代码:

Class demo = new Demo01().getClass();    

利用 Class 类的对象 demo 可以访问 Demo01 对象的描述信息、Demo01 类的信息以及基类 Object 的信息。下表列出了通过反射可以访问的信息。

类型

访问方法

返回值类型

说明

包路径

getPackage()

Package 对象

获取该类的存放路径

类名称

getName()

String 对象

获取该类的名称

继承类

getSuperclass()

Class 对象

获取该类继承的类

实现接口

getlnterfaces()

Class 型数组

获取该类实现的所有接口

构造方法

getConstructors()

Constructor 型数组

获取所有权限为 public 的构造方法

getDeclaredContruectors()

Constructor 对象

获取当前对象的所有构造方法

方法

getMethods()

Methods 型数组

获取所有权限为 public 的方法

getDeclaredMethods()

Methods 对象

获取当前对象的所有方法

成员变量

getFields()

Field 型数组

获取所有权限为 public 的成员变量

getDeclareFileds()

Field 对象

获取当前对象的所有成员变量

内部类

getClasses()

Class 型数组

获取所有权限为 public 的内部类

getDeclaredClasses()

Class 型数组

获取所有内部类

内部类的声明类

getDeclaringClass()

Class 对象

如果该类为内部类,则返回它的成员类,否则返回 null

Java 反射机制的优缺点

优点: 能够运行时动态获取类的实例,大大提高系统的灵活性和扩展性。 与 Java 动态编译相结合,可以实现无比强大的功能。 对于 Java 这种先编译再运行的语言,能够让我们很方便的创建灵活的代码,这些代码可以在运行时装配,无需在组件之间进行源代码的链接,更加容易实现面向对象。

缺点:

反射会消耗一定的系统资源,因此,如果不需要动态地创建一个对象,那么就不需要用反射; ​ 反射调用方法时可以忽略权限检查,获取这个类的私有方法和属性,因此可能会破坏类的封装性而导致安全问题。

Java 反射机制在一般的 Java 应用开发中很少使用,即便是 Java EE 阶段也很少使用。

二、反射机制API

实现 Java 反射机制的类都位于 java.lang.reflect 包中,java.lang.Class 类是 Java 反射机制 API 中的核心类。

1、java.lang.Class 类

java.lang.Class 类是实现反射的关键所在,Class 类的一个实例表示 Java 的一种数据类型,包括类、接口、枚举、注解(Annotation)、数组、基本数据类型和 void。Class 没有公有的构造方法,Class 实例是由 JVM 在类加载时自动创建的。

程序代码中获得 Class 实例可以通过如下代码实现:

// 1. 通过类型class静态变量

Class clz1 = String.class;

​

String str = "Hello";

​

// 2. 通过对象的getClass()方法

Class clz2 = str.getClass();

注:int 是基本数据类型,所以输出结果为 true;Integer 是类,是引用数据类型,所以输出结果为 false。

2、java.lang.reflect 包

java.lang.reflect 包提供了反射中用到类,主要的类说明如下:

Constructor 类:提供类的构造方法信息。

Field 类:提供类或接口中成员变量信息。

Method 类:提供类或接口成员方法信息。

Array 类:提供了动态创建和访问 Java 数组的方法。

Modifier 类:提供类和成员访问修饰符信息。

三、通过反射访问构造方法

为了能够动态获取对象构造方法的信息,首先需要通过下列方法之一创建一个 Constructor 类型的对象或者数组。 ​ getConstructors() ​ getConstructor(Class<?>…parameterTypes) ​ getDeclaredConstructors() ​ getDeclaredConstructor(Class<?>...parameterTypes)

​            创建的每个 Constructor 对象表示一个构造方法,然后利用 Constructor 对象的方法操作构造方法。Constructor 类的常用方法如表 1 所示。

方法名称

说明

isVarArgs()

查看该构造方法是否允许带可变数量的参数,如果允许,返回 true,否则返回 false

getParameterTypes()

按照声明顺序以 Class 数组的形式获取该构造方法各个参数的类型

getExceptionTypes()

以 Class 数组的形式获取该构造方法可能抛出的异常类型

newInstance(Object … initargs)

通过该构造方法利用指定参数创建一个该类型的对象,如果未设置参数则表示 采用默认无参的构造方法

setAccessiable(boolean flag)

如果该构造方法的权限为 private,默认为不允许通过反射利用 netlnstance() 方法创建对象。如果先执行该方法,并将入口参数设置为 true,则允许创建对 象

getModifiers()

获得可以解析出该构造方法所采用修饰符的整数

通过 java.lang.reflect.Modifier 类可以解析出 getMocMers() 方法的返回值所表示的修饰符信息。在该类中提供了一系列用来解析的静态方法,既可以查看是否被指定的修饰符修饰,还可以字符串的形式获得所有修饰符。表 2 列出了 Modifier 类的常用静态方法

静态方法名称

说明

isStatic(int mod)

如果使用 static 修饰符修饰则返回 true,否则返回 false

isPublic(int mod)

如果使用 public 修饰符修饰则返回 true,否则返回 false

isProtected(int mod)

如果使用 protected 修饰符修饰则返回 true,否则返回 false

isPrivate(int mod)

如果使用 private 修饰符修饰则返回 true,否则返回 false

isFinal(int mod)

如果使用 final 修饰符修饰则返回 true,否则返回 false

toString(int mod)

以字符串形式返回所有修饰符

例:下列代码判断对象 con 所代表的构造方法是否被 public 修饰,以及以字符串形式获取该构造方法的所有修饰符。

…java

int modifiers = con.getModifiers();    // 获取构造方法的修饰符整数

boolean isPublic = Modifier.isPublic(modifiers);    // 判断修饰符整数是否为public

string allModifiers = Modifier.toString(modifiers);

四、通过反射执行方法

要动态获取一个对象方法的信息,首先需要通过下列方法之一创建一个 Method 类型的对象或者数组。 ​ getMethods() ​ getMethods(String name,Class<?> …parameterTypes) ​ getDeclaredMethods() ​ getDeclaredMethods(String name,Class<?>...parameterTypes)

如果是访问指定的构造方法,需要根据该方法的入口参数的类型来访问。例如,访问一个名称为 max,入口参数类型依次为 int 和 String 类型的方法。

下面的两种方式均可以实现:

objectClass.getDeclaredConstructor("max",int.class,String.class);

​

objectClass.getDeclaredConstructor("max",new Class[]{int.class,String.class});

Method 类的常用方法如下表 所示。

静态方法名称

说明

getName()

获取该方法的名称

getParameterType()

按照声明顺序以 Class 数组的形式返回该方法各个参数的类型

getReturnType()

以 Class 对象的形式获得该方法的返回值类型

getExceptionTypes()

以 Class 数组的形式获得该方法可能抛出的异常类型

invoke(Object obj,Object...args)

利用 args 参数执行指定对象 obj 中的该方法,返回值为 Object 类型

isVarArgs()

查看该方法是否允许带有可变数量的参数,如果允许返回 true,否则返回 false

getModifiers()

获得可以解析出该方法所采用修饰符的整数

五、通过反射访问成员变量

通过下列任意一个方法访问成员变量时将返回 Field 类型的对象或数组。 ​

getFields()

getField(String name)

getDeclaredFields()

getDeclaredField(String name)

上述方法返回的 Field 对象代表一个成员变量。例如,要访问一个名称为 price 的成员变量,示例代码如下:

object.getDeciaredField("price");

Field 类的常用方法如下表 所示

方法名称

说明

getName()

获得该成员变量的名称

getType()

获取表示该成员变量的 Class 对象

get(Object obj)

获得指定对象 obj 中成员变量的值,返回值为 Object 类型

set(Object obj, Object value)

将指定对象 obj 中成员变量的值设置为 value

getlnt(0bject obj)

获得指定对象 obj 中成员类型为 int 的成员变量的值

setlnt(0bject obj, int i)

将指定对象 obj 中成员变量的值设置为 i

setFloat(Object obj, float f)

将指定对象 obj 中成员变量的值设置为 f

getBoolean(Object obj)

获得指定对象 obj 中成员类型为 boolean 的成员变量的值

setBoolean(Object obj, boolean b)

将指定对象 obj 中成员变量的值设置为 b

getFloat(Object obj)

获得指定对象 obj 中成员类型为 float 的成员变量的值

setAccessible(boolean flag)

此方法可以设置是否忽略权限直接访问 private 等私有权限的成员变量

getModifiers()

获得可以解析出该方法所采用修饰符的整数

六、获取继承关系

​当我们获取到某个`Class`对象时,实际上就获取到了一个类的类型:

…java

Class cls = String.class; // 获取到String的Class

还可以用实例的`getClass()`方法获取:

…java

String s = "";

Class cls = s.getClass(); // s是String,因此获取到String的Class

​最后一种获取`Class`的方法是通过`Class.forName("")`,传入`Class`的完整类名获取:

…java

Class s = Class.forName("java.lang.String");

​这三种方式获取的`Class`实例都是同一个实例,因为JVM对每个加载的`Class`只创建一个`Class`实例来表示它的类型。

 1、获取父类的Class

​有了`Class`实例,我们还可以获取它的父类的`Class`:

​运行上述代码,可以看到,`Integer`的父类类型是`Number`,`Number`的父类是`Object`,`Object`的父类是`null`。除`Object`外,其他任何非`interface`的`Class`都必定存在一个父类类型。

2、获取interface

​由于一个类可能实现一个或多个接口,通过`Class`我们就可以查询到实现的接口类型。例如,查询`Integer`实现的接口:

要特别注意:`getInterfaces()`只返回当前类直接实现的接口类型,并不包括其父类实现的接口类型:

​`Integer`的父类是`Number`,`Number`实现的接口是`java.io.Serializable`。

​            此外,对所有`interface`的`Class`调用`getSuperclass()`返回的是`null`,获取接口的父接口要用`getInterfaces()`:

```java

System.out.println(java.io.DataInputStream.class.getSuperclass()); // java.io.FilterInputStream,因为DataInputStream继承自FilterInputStream

System.out.println(java.io.Closeable.class.getSuperclass()); // null,对接口调用getSuperclass()总是返回null,获取接口的父接口要用getInterfaces()

```

​            如果一个类没有实现任何`interface`,那么`getInterfaces()`返回空数组。

3、继承关系

4、小结

通过`Class`对象可以获取继承关系:

- `Class getSuperclass()`:获取父类类型;

- `Class[] getInterfaces()`:获取当前类实现的所有接口。

通过`Class`对象的`isAssignableFrom()`方法可以判断一个向上转型是否可以实现。

七、动态代理
我们来比较Java的`class`和`interface`的区别:
- 可以实例化`class`(非`abstract`);
- 不能实例化`interface`。
所有`interface`类型的变量总是通过某个实例向上转型赋值接口类型变量的:
```java
CharSequence cs = new StringBuilder();
Java标准库提供了一种动态代理(Dynamic Proxy)的机制:可以在运行期动态创建某个interface的实例。
什么叫运行期动态创建?听起来好像很复杂。所谓动态代理,是和静态相对应的。我们来看静态代码怎么写:
定义接口:
 
 
```java
public interface Hello {
    void morning(String name);
}
```
编写实现类:
```java
public class HelloWorld implements Hello {
    public void morning(String name) {
        System.out.println("Good morning, " + name);
    }
}
```
​创建实例,转型为接口并调用:
```java
Hello hello = new HelloWorld();
hello.morning("Bob");
```
这种方式就是我们通常编写代码的方式。
还有一种方式是动态代码,我们仍然先定义了接口`Hello`,但是我们并不去编写实现类,而是直接通过JDK提供的一个`Proxy.newProxyInstance()`创建了一个`Hello`接口对象。这种没有实现类但是在运行期动态创建了一个接口对象的方式,我们称为动态代码。JDK提供的动态创建接口对象的方式,就叫动态代理。
一个最简单的动态代理实现如下:
 
```java
public class Main {
    public static void main(String[] args) {
        
        InvocationHandler handler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println(method);
                if (method.getName().equals("morning")) {
                    System.out.println("Good morning, " + args[0]);
                }
                return null;
            }
        };
        Hello hello = (Hello) Proxy.newProxyInstance(
            Hello.class.getClassLoader(), // 传入ClassLoader
            new Class[] { Hello.class }, // 传入要实现的接口
            handler); // 传入处理调用方法的InvocationHandler
        hello.morning("Bob");
    }
}interface Hello {
    void morning(String name);
}
```
 
 
在运行期动态创建一个`interface`实例的方法如下:
1、定义一个InvocationHandler实例,它负责实现接口的方法调用;
2、通过Proxy.newProxyInstance()创建interface实例,它需要3个参数:
2.1、使用的ClassLoader,通常就是接口类的ClassLoader;
2.2、需要实现的接口数组,至少需要传入一个接口进去;
2.3、用来处理接口方法调用的InvocationHandler实例。
3、将返回的Object强制转型为接口。
将上面的动态代理改写为静态实现类大概长这样:
```java
public class HelloDynamicProxy implements Hello {
 
 
    InvocationHandler handler;
    
    public HelloDynamicProxy(InvocationHandler handler) {
        this.handler = handler;
    }
    
    public void morning(String name) {
        handler.invoke(
           this,
           Hello.class.getMethod("morning", String.class),
           new Object[] { name });
    }
}
```
JVM帮我们自动编写了一个上述类(不需要源码,可以直接生成字节码),并不存在可以直接实例化接口的黑魔法。

小结

Java标准库提供了动态代理功能,允许在运行期动态创建一个接口的实例;

动态代理是通过Proxy创建代理对象,然后将接口方法“代理”给InvocationHandler完成的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值