目录
1.2.3 服务工厂类:CodeServiceFactory(核心:SPI 服务发现)
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
客户端远程调用服务端登录的结果为:登录失败