函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。
函数式接口可以被隐式转换为 lambda 表达式。
Lambda 表达式和方法引用(实际上也可认为是Lambda表达式)上。
一般函数式接口可以在类上加一个
@FunctionalInterface
注解以标识其为函数式接口
优点:
缺点:
下面举一个比较典型的例子,码云上的GVP
开源权限认证框架Sa-token中有一段代码:
/**
* 封装 注销、踢人、顶人 三个动作的相同代码(无API含义方法)
* @param loginId 账号id
* @param device 设备标识
* @param appendFun 追加操作
* @param isLogoutSession 是否注销 User-Session
*/
protected void clearTokenCommonMethod(Object loginId, String device, Consumer<String> appendFun, boolean isLogoutSession) {
// 1. 如果此账号尚未登录,则不执行任何操作
SaSession session = getSessionByLoginId(loginId, false);
if(session == null) {
return;
}
// 2. 循环token签名列表,开始删除相关信息
for (TokenSign tokenSign : session.getTokenSignList()) {
if(device == null || tokenSign.getDevice().equals(device)) {
// -------- 共有操作
// s1. 获取token
String tokenValue = tokenSign.getValue();
// s2. 清理掉[token-last-activity]
clearLastActivity(tokenValue);
// s3. 从token签名列表移除
session.removeTokenSign(tokenValue);
// -------- 追加操作
appendFun.accept(tokenValue);
}
}
// 3. 尝试注销session
if(isLogoutSession) {
session.logoutByTokenSignCountToZero();
}
}
clearTokenCommonMethod
这个方法是为了在执行某些操作后,从系统中将token删除掉,并清空用户信息,注销session,因为在注销、踢人、顶人这几个地方都有类似的逻辑,但是相似的逻辑中有又一部分不太一样,这种情况下,直接抽成一个方法是不能直接通用的,那么这个时候一般有两个方案
doAfterClearToken
方法。在Shiro
和Spring Security
的代码中,经常可以看到有这种方式,即在本类中写一个抽象方法doAfterClearToken
,在删除token后调用这个方法,这个方法在这个类中不做任何事情,交由子类来实现。当然,此时这个类必须是抽象类,然后再写注销类、踢人类、顶人类来继承这个类,并重写doAfterClearToken
方法来实现各自的逻辑,大概就是下面这样public abstract class StpLogic{
protected void clearTokenCommonMethod(Object loginId, String device, boolean isLogoutSession) {
// 1. 如果此账号尚未登录,则不执行任何操作
SaSession session = getSessionByLoginId(loginId, false);
if(session == null) {
return;
}
// 2. 循环token签名列表,开始删除相关信息
for (TokenSign tokenSign : session.getTokenSignList()) {
if(device == null || tokenSign.getDevice().equals(device)) {
// -------- 共有操作
// s1. 获取token
String tokenValue = tokenSign.getValue();
// s2. 清理掉[token-last-activity]
clearLastActivity(tokenValue);
// s3. 从token签名列表移除
session.removeTokenSign(tokenValue);
// -------- 追加操作
doAfterClearToken(tokenValue);
}
}
// 3. 尝试注销session
if(isLogoutSession) {
session.logoutByTokenSignCountToZero();
}
}
public abstract void doAfterClearToken(String tokenValue);
}
public class kickout extends StpLogic{
@Override
public void doAfterClearToken(String tokenValue){
//*******这里写具体的踢人的逻辑
}
}
public class LogOut extends StpLogic{
@Override
public void doAfterClearToken(String tokenValue){
//*******这里写具体的注销的逻辑
}
}
public class replaced extends StpLogic{
@Override
public void doAfterClearToken(String tokenValue){
//*******这里写具体的顶人的逻辑
}
}
Consumer
接口,然后在清除token后执行accept
方法,这样就完成了不同的后续操作,然后在调用这个方法时编写具体的执行逻辑代码,这样调用的好处是你不用单独为了一小段代码再撰写一个类(当然函数式接口使用过的是匿名内部类,本质上也是创建了类的,只不过不用撰写类放在项目目录下),这样可以提高开发效率,当然可读性比直接撰写了差一些,代码如下 /**
* 顶人下线,根据账号id 和 设备标识
* 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-4
*
* @param loginId 账号id
* @param device 设备标识 (填null代表顶替所有设备)
*/
public void replaced(Object loginId, String device) {
clearTokenCommonMethod(loginId, device, tokenValue -> {
// 将此 token 标记为已被顶替
updateTokenToIdMapping(tokenValue, NotLoginException.BE_REPLACED);
SaManager.getSaTokenListener().doReplaced(loginType, loginId, tokenValue);
}, false);
}
/**
* 踢人下线,根据账号id 和 设备标识
* 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-5
*
* @param loginId 账号id
* @param device 设备标识 (填null代表踢出所有设备)
*/
public void kickout(Object loginId, String device) {
clearTokenCommonMethod(loginId, device, tokenValue -> {
// 将此 token 标记为已被踢下线
updateTokenToIdMapping(tokenValue, NotLoginException.KICK_OUT);
SaManager.getSaTokenListener().doKickout(loginType, loginId, tokenValue);
}, true);
}
/**
* 会话注销,根据账号id 和 设备标识
*
* @param loginId 账号id
* @param device 设备标识 (填null代表所有注销设备)
*/
public void logout(Object loginId, String device) {
clearTokenCommonMethod(loginId, device, tokenValue -> {
// 删除Token-Id映射 & 清除Token-Session
deleteTokenToIdMapping(tokenValue);
deleteTokenSession(tokenValue);
SaManager.getSaTokenListener().doLogout(loginType, loginId, tokenValue);
}, true);
}
函数式接口Consumer
介绍,这个函数式接口是为了消费数据而定义的,其代码如下
package java.util.function;
import java.util.Objects;
/**
* Represents an operation that accepts a single input argument and returns no
* result. Unlike most other functional interfaces, {@code Consumer} is expected
* to operate via side-effects.
*
* This is a functional interface
* whose functional method is {@link #accept(Object)}.
*
* @param the type of the input to the operation
*
* @since 1.8
*/
@FunctionalInterface
public interface Consumer<T> {
/**
* Performs this operation on the given argument.
*
* @param t the input argument
*/
void accept(T t);
/**
* Returns a composed {@code Consumer} that performs, in sequence, this
* operation followed by the {@code after} operation. If performing either
* operation throws an exception, it is relayed to the caller of the
* composed operation. If performing this operation throws an exception,
* the {@code after} operation will not be performed.
*
* @param after the operation to perform after this operation
* @return a composed {@code Consumer} that performs in sequence this
* operation followed by the {@code after} operation
* @throws NullPointerException if {@code after} is null
*/
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}