Java入门级教程17——利用Java SPI机制制作验证码、利用Java RMI机制实现分布式登录验证系统

发布于:2025-09-12 ⋅ 阅读:(12) ⋅ 点赞:(0)

目录

1.制作验证码——java SPI机制

1.1 类所属包情况

1.2 具体实现

1.2.1 核心接口:ICode 

1.2.2 接口实现类:验证码的具体生成逻辑

1.2.3 服务工厂类:CodeServiceFactory(核心:SPI 服务发现)

1.2.4 SPI 配置文件

1.2.5 主程序:App(运行入口)

1.2.6 注释掉不同的配置

2.Java RMI (远程方法调用)

2.1 定义

2.2 实现分布式登录验证系统

2.2.1 数据库准备

2.2.2 在idea中启动Java的RMI服务

2.2.3 在eclipse实现登录


1.制作验证码——java SPI机制

1.1 类所属包情况

1.2 具体实现

1.2.1 核心接口:ICode 

定义验证码生成的标准接口,所有验证码生成实现类都需要实现该接口

package com.hy.interfaces;

public interface ICode {
    public String makeCode();
}

1.2.2 接口实现类:验证码的具体生成逻辑

① 数字验证码实现:NumberCodeImpl

package com.hy.interfaces.impl;

import java.util.Random;
import com.hy.interfaces.ICode;

public class NumberCodeImpl implements ICode {
    // 数字字符源(0-9)
    private String[] nums = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" };

    @Override
    public String makeCode() {
        String code = "";
        for (int i = 0; i < 4; i++) { // 生成4位验证码
            // 随机获取字符源中的一个元素
            String s = String.valueOf(new Random().nextInt(nums.length));
            
            // 确保字符不重复:如果已包含则重新生成(i--回退循环)
            if (!code.contains(s)) {
                code += s;
            } else {
                i--;
            }
        }
        return code;
    }
}

② 中文验证码实现:ChineseCodeImpl

package com.hy.interfaces.impl;

import java.util.Random;
import com.hy.interfaces.ICode;

public class ChineseCodeImpl implements ICode {
    // 中文字符源(自定义汉字)
    private String[] nums = { "赵", "钱", "孙", "李", "王", "五", "马", "六", "天", "地" };

    @Override
    public String makeCode() {
        String code = "";
        for (int i = 0; i < 4; i++) { // 生成4位验证码
            // 随机获取字符源中的一个元素
            String s = nums[new Random().nextInt(nums.length)];
            
            // 确保字符不重复:如果已包含则重新生成(i--回退循环)
            if (!code.contains(s)) {
                code += s;
            } else {
                i--;
            }
        }
        return code;
    }
}

1.2.3 服务工厂类:CodeServiceFactory(核心:SPI 服务发现)

利用 Java 的 ServiceLoader 类实现 SPI 机制的服务发现:通过接口(ICode.class)动态加载其所有实现类

package com.hy.service;

import java.util.Iterator;
import java.util.ServiceLoader;
import com.hy.interfaces.ICode;

public class CodeServiceFactory {
    public static String createCode(Class targetClass) {
        // 1. 通过ServiceLoader动态加载实现了targetClass(此处为ICode)的服务实现类
        ServiceLoader s = ServiceLoader.load(targetClass);
        
        // 2. 获取实现类的迭代器
        Iterator its = s.iterator();
        
        ICode code = null;
        // 3. 迭代获取最后一个实现类(若有多个实现,取最后一个)
        while (its.hasNext()) {
            code = (ICode) its.next();
        }
        
        // 4. 调用实现类的makeCode()生成验证码
        String checkCode = code.makeCode();
        return checkCode;
    }
}

1.2.4 SPI 配置文件

# 开头的行是注释,不会被加载,需要实现哪个,就把其余注释掉

com.hy.interfaces.impl.NumberCodeImpl
#com.hy.interfaces.impl.ChineseCodeImpl

1.2.5 主程序:App(运行入口)

主方法通过无限循环,每 6 秒调用一次工厂类的createCode方法,生成验证码

package com.hy.javaspi;

import com.hy.interfaces.ICode;
import com.hy.service.CodeServiceFactory;

public class App {
    public static void main(String[] args) {
        while (true) { // 无限循环
            // 调用工厂类生成验证码(基于ICode接口的实现类)
            String checkCode = CodeServiceFactory.createCode(ICode.class);
            System.out.println("获取的验证码为:" + checkCode);
            
            try {
                Thread.sleep(6000); // 每6秒生成一次
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

1.2.6 注释掉不同的配置

① 使用数字验证码

com.hy.interfaces.impl.NumberCodeImpl
#com.hy.interfaces.impl.ChineseCodeImpl

输出结果:

获取的验证码为:2834
获取的验证码为:9651
获取的验证码为:0753
获取的验证码为:4702

...

② 使用中文验证码

#com.hy.interfaces.impl.NumberCodeImpl
com.hy.interfaces.impl.ChineseCodeImpl

输出结果:

获取的验证码为:五李孙地
获取的验证码为:钱孙六李
获取的验证码为:赵马地王
获取的验证码为:马五李地

...

2.Java RMI (远程方法调用)

2.1 定义

Java RMI(Remote Method Invocation,远程方法调用)是 Java 原生的分布式通信机制,允许一个 JVM 中的对象(客户端调用另一个 JVM 中的对象(服务端)的方法,就像调用本地方法一样,无需显式处理网络通信细节。

2.2 实现分布式登录验证系统

2.2.1 数据库准备

-- 创建t_emps表 --
CREATE TABLE t_emps(
	eid INT PRIMARY KEY auto_increment, -- 员工的编号
	ename VARCHAR(20) NOT NULL, -- 员工的姓名
	epwd CHAR(8) NOT NULL, -- 员工的密码
	ebirthday datetime, -- 员工的出生年月,不设计年龄字段,会造成字段冗余
	esalary DOUBLE NOT NULL, -- 员工的工资
	eaddress VARCHAR(100), -- 员工的地址
	estate INT NOT NULL -- 员工的状态
)

-- 删除t_emps表 --
DROP TABLE t_emps

-- 插入数据 --
INSERT INTO t_emps(ename,epwd,ebirthday,esalary,eaddress,estate)
VALUES('张三','11111','2000-05-28',90000.56,'南京',1);

INSERT INTO t_emps(ename,epwd,ebirthday,esalary,eaddress,estate)
VALUES('李四','22222','2004-06-15',88000.69,'盐城',1);

INSERT INTO t_emps(ename,epwd,ebirthday,esalary,eaddress,estate)
VALUES('李老八','22222','1996-12-30',5600,'无锡',1);

INSERT INTO t_emps(ename,epwd,ebirthday,esalary,eaddress,estate)
VALUES('赵二','22222','1996-12-30',5800,'无锡',0);

-- 查询表 --
SELECT * FROM t_emps

实现效果:

2.2.2 在idea中启动Java的RMI服务

步骤一:在idea中新建Maven项目,并在pom.xml文件中添加依赖

<dependency>
	<groupId>mysql</groupId>
	<artifactId>mysql-connector-java</artifactId>
	<version>8.0.24</version>
</dependency>

步骤二:具体实现

① 类所属包情况

② 数据访问层:Dao类(数据库交互)

通过 JDBC 连接 MySQL 数据库,实现 “登录验证” 的数据库交互逻辑

package com.hy.dao;

import java.sql.*;

public class Dao {
    Connection conn; // 数据库连接对象

    // 构造方法:初始化数据库连接
    public Dao() {
        try {
            // 1. 加载MySQL JDBC驱动(MySQL 8.0+使用com.mysql.cj.jdbc.Driver)
            Class.forName("com.mysql.cj.jdbc.Driver");

            // 2. 建立数据库连接
            // 连接URL:jdbc:mysql://主机:端口/数据库名
            // 用户名:root,密码:修改为自己的密码
            conn = DriverManager.getConnection(
                "jdbc:mysql://127.0.0.1:3306/mysql2025", 
                "root", 
                "yourpassword"
            );
        } catch (ClassNotFoundException e) {
            e.printStackTrace(); // 驱动类未找到异常
        } catch (SQLException e) {
            e.printStackTrace(); // 数据库连接异常
        }
    }

    // 登录验证方法:检查用户名和密码是否匹配
    public int checkLogin(String username, String userpwd) {
        // SQL查询:统计符合条件的用户数量(ename=用户名且epwd=密码)
        String sql = "select count(ename) from t_emps where ename = ? and epwd =?";
        int count = 0; // 匹配的用户数量(0或1)

        try {
            // 使用PreparedStatement预编译SQL,防止SQL注入
            PreparedStatement pstmt = this.conn.prepareStatement(sql);
            pstmt.setString(1, username); // 填充第一个参数(用户名)
            pstmt.setString(2, userpwd);  // 填充第二个参数(密码)

            // 执行查询,获取结果集
            ResultSet rs = pstmt.executeQuery();

            // 读取结果集中的计数(count(ename))
            while (rs.next()) {
                count = rs.getInt(1); // 第一列的结果(0或1)
            }

        } catch (SQLException e) {
            e.printStackTrace(); // SQL执行异常
        } finally {
            // 关闭数据库连接(避免资源泄露)
            if (null != conn) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }

        return count; // 返回匹配数量(1表示登录成功,0表示失败)
    }
}

③ 远程接口:IData(定义 RMI 通信标准)

定义客户端与服务端的 “通信协议”,明确可远程调用的方法,是 RMI 通信的基础(客户端与服务端必须完全一致)。

package com.hy.data.interfaces;

import java.rmi.Remote;
import java.rmi.RemoteException;

// 客户端与服务端的远程通信接口(必须继承Remote)
public interface IData extends Remote {
    // 远程方法:查询消息(必须声明抛出RemoteException)
    public String queryMessage() throws RemoteException;

    // 远程方法:登录验证(必须声明抛出RemoteException)
    public String checkLogin(String username, String userpwd) throws RemoteException;
}

④ 远程接口实现:UserDataImpl(服务端业务逻辑)

实现IData远程接口,封装服务端业务逻辑(调用 Dao 层操作数据库),并通过 RMI 框架导出为 “可远程访问的对象”。

package com.hy.impl;

import com.hy.dao.Dao;
import com.hy.data.interfaces.IData;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

// 远程接口的实现类(必须继承UnicastRemoteObject或手动实现序列化)
public class UserDataImpl extends UnicastRemoteObject implements IData {

    // 构造方法:必须抛出RemoteException(因为父类UnicastRemoteObject的构造方法抛出该异常)
    public UserDataImpl() throws RemoteException {
        super(); // 调用父类构造方法,自动处理对象的网络传输(序列化/反序列化)
    }

    // 实现远程方法:返回固定消息
    @Override
    public String queryMessage() throws RemoteException {
        return "RMI分布式从远程传过来的数据为:RMI、webservice、hessian、thrift、googleRPC、Dubbo";
    }

    // 实现远程方法:调用Dao进行登录验证
    @Override
    public String checkLogin(String username, String userpwd) throws RemoteException {
        Dao dao = new Dao(); // 创建数据访问对象
        // 调用Dao的checkLogin方法,若返回值>0(即存在匹配用户),返回"登录成功",否则返回"登录失败"
        if (dao.checkLogin(username, userpwd) > 0) {
            return "登录成功";
        }
        return "登录失败";
    }
}

⑤ RMI 服务端:ServerRMI(启动并发布服务)

启动 RMI 注册表(服务注册中心)、创建远程对象实例、将远程对象绑定到指定 URL,供客户端查找和调用。

package com.hy.serverrmi;

import com.hy.data.interfaces.IData;
import com.hy.impl.UserDataImpl;
import java.net.MalformedURLException;
import java.rmi.AlreadyBoundException;
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;

public class ServerRMI {
    public static void main(String[] args) {
        try {
            // 1. 创建远程对象实例(UserDataImpl实现了IData接口)
            IData datas = new UserDataImpl();

            // 2. 在本地9200端口注册RMI注册表(类似“服务注册中心”)
            LocateRegistry.createRegistry(9200);

            // 3. 将远程对象绑定到RMI URL(客户端通过该URL查找服务)
            // 格式:rmi://主机:端口/服务名称
            Naming.bind("rmi://127.0.0.1:9200/userdatas", datas);

            System.out.println("Java的RMI服务已经启动成功");

        } catch (RemoteException e) {
            e.printStackTrace(); // 远程对象创建或注册表启动异常
        } catch (MalformedURLException e) {
            e.printStackTrace(); // RMI URL格式错误
        } catch (AlreadyBoundException e) {
            e.printStackTrace(); // 服务名称已被绑定(重复发布)
        }
    }
}

输出结果:

Java的RMI服务已经启动成功

2.2.3 在eclipse实现登录

① 类所属包情况

② 远程接口:IData(客户端与服务端的通信契约)

客户端与服务端的 “通信协议”,定义客户端可以远程调用的方法。

package com.hy.data.interfaces;

import java.rmi.Remote;
import java.rmi.RemoteException;

// 远程接口:客户端和服务端必须共享此接口(包路径、方法定义完全一致)
public interface IData extends Remote {
    // 远程方法1:查询消息(服务端返回预设字符串)
    public String queryMessage() throws RemoteException;
    
    // 远程方法2:登录验证(接收用户名和密码,返回登录结果)
    public String checkLogin(String username, String userpwd) throws RemoteException;
}

③ 客户端实现类:App(发起远程调用的核心逻辑)

接收用户输入(账号密码),通过 RMI 框架查找服务端远程对象,发起远程调用,最后展示调用结果。

package com.hy.javamiclinet;

import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.util.Scanner;
import com.hy.data.interfaces.IData;

public class App {
    // 远程对象引用:客户端通过该引用调用服务端方法
    static IData data = null;

    // 静态代码块:初始化远程对象引用(程序启动时执行)
    static {
        try {
            // 关键:通过RMI URL查找服务端绑定的远程对象
            // URL格式:rmi://服务端IP:端口/服务名称(需与服务端绑定的URL完全一致)
            data = (IData) Naming.lookup("rmi://127.0.0.1:9200/userdatas");
        } catch (MalformedURLException e) {
            e.printStackTrace(); // URL格式错误(如端口无效、协议错误)
        } catch (RemoteException e) {
            e.printStackTrace(); // 远程通信异常(如服务端未启动、网络不通)
        } catch (NotBoundException e) {
            e.printStackTrace(); // 服务名称未绑定(服务端未发布该服务)
        }
    }

    // 调用远程方法:queryMessage(查询消息)
    public void queryMsg() {
        try {
            // 看似调用本地对象方法,实际通过网络调用服务端的实现
            String message = data.queryMessage();
            System.out.println("客户端远程调用服务端的结果为:" + message);
        } catch (RemoteException e) {
            e.printStackTrace(); // 远程调用过程中发生异常
        }
    }

    // 调用远程方法:checkLogin(登录验证)
    public void checkLogin(String username, String userpwd) {
        try {
            // 传递参数(用户名和密码)到服务端,调用远程验证方法
            String result = data.checkLogin(username, userpwd);
            System.out.println("客户端远程调用服务端登录的结果为:" + result);
        } catch (RemoteException e) {
            e.printStackTrace(); // 远程调用异常(如参数传输失败、服务端处理出错)
        }
    }

    // 主方法:程序入口,接收用户输入并发起登录验证
    public static void main(String[] args) {
        App app = new App(); // 创建客户端实例

        // 接收用户输入(用户名和密码)
        System.out.println("请输入用户姓名:");
        Scanner s1 = new Scanner(System.in);
        String username = s1.next(); // 读取用户名

        System.out.println("请输入用户密码:");
        Scanner s2 = new Scanner(System.in);
        String userpwd = s2.next(); // 读取密码

        // 调用登录验证方法(远程调用)
        app.checkLogin(username, userpwd);

        // 可选:调用查询消息方法(注释掉了,取消注释可执行)
        // app.queryMsg();
    }
}

输出结果:

请输入用户姓名:
张三
请输入用户密码:
11111
客户端远程调用服务端登录的结果为:登录成功

请输入用户姓名:
张三
请输入用户密码:
22222
客户端远程调用服务端登录的结果为:登录失败


网站公告

今日签到

点亮在社区的每一天
去签到