目录
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" };@Overridepublic 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 = { "赵", "钱", "孙", "李", "王", "五", "马", "六", "天", "地" };@Overridepublic 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(); // 调用父类构造方法,自动处理对象的网络传输(序列化/反序列化)}// 实现远程方法:返回固定消息@Overridepublic String queryMessage() throws RemoteException {return "RMI分布式从远程传过来的数据为:RMI、webservice、hessian、thrift、googleRPC、Dubbo";}// 实现远程方法:调用Dao进行登录验证@Overridepublic 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
客户端远程调用服务端登录的结果为:登录失败