Proxy¶
Proxy is one of the structural patterns that, in the context of creation, has a similar structure to the Decorator. This pattern adds a layer between the interface and the specific implementation. This layer, depending on the type of proxy we implement, may fulfill a different function.
We can identify several types of proxies. Some of them are:
remote
, the purpose of which is to represent an object located in another space (e.g. another JVM).virtual
, which can be used as a cache for an object reference.security
, which adds a layer of security to the object.
A proxy
object should implement the same interface as the object it wraps. Very often, the end user of the interface does not know that he uses a proxy, which also means that the proxy can hide some implementation details.
The following example shows a simple use of a security proxy
.
The example consists of the following classes:
Message
- represents the sent messageSessionTokens
- stores user tokensTokenValidator
- checks if the user token is validMessageSender
- is a common interface on which a "real" object and a proxy are basedSlackMessageSender
- is an implementation ofMessageSender
SlackMessageSenderProxy
, which is an middleman and introduces a simple security layer - the user can send a message if he has the right token.
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class Message {
private String channelName;
private LocalDateTime postDate;
private String author;
private String text;
public Message(final String channelName, final String author, final String text) {
this.channelName = channelName;
this.author = author;
this.text = text;
this.postDate = LocalDateTime.now();
}
}
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
public class SessionTokens {
private final Map<String, UUID> userNameToSessionToken = new HashMap<>();
public Optional<UUID> getUserToken(final String userName) {
return Optional.ofNullable(userNameToSessionToken.getOrDefault(userName, null));
}
public void createTokenForUser(final String userName) {
userNameToSessionToken.put(userName, UUID.randomUUID());
}
}
import java.util.Random;
import java.util.UUID;
public class TokenValidator {
boolean isExpired(final UUID token) {
return new Random().nextBoolean(); // for example purposes
}
}
public interface MessageSender {
void sendMessage(String channelName, String username, String message);
}
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.List;
@Slf4j
public class SlackMessageSender implements MessageSender {
private final List<Message> messages = new ArrayList<>();
@Override
public void sendMessage(final String channelName, final String username, final String messageText) {
final Message message = new Message(channelName, username, messageText);
log.info("Sent message {}", message);
messages.add(message);
log.info("Messages: {}", messages);
}
}
import lombok.extern.slf4j.Slf4j;
import java.util.Optional;
import java.util.UUID;
@Slf4j
public class SlackMessageSenderProxy implements MessageSender {
private final MessageSender messageSender;
private final SessionTokens sessionTokens;
private final TokenValidator tokenValidator;
public SlackMessageSenderProxy(final MessageSender messageSender, final SessionTokens sessionTokens, final TokenValidator tokenValidator) {
this.messageSender = messageSender;
this.sessionTokens = sessionTokens;
this.tokenValidator = tokenValidator;
}
@Override
public void sendMessage(final String channelName, final String username, final String message) {
final Optional<UUID> userTokenOptional = sessionTokens.getUserToken(username);
if (userTokenOptional.isPresent()) {
final UUID existingToken = userTokenOptional.get();
if (!tokenValidator.isExpired(existingToken)) {
messageSender.sendMessage(channelName, username, message);
}
} else {
log.info("Message from {} not sent to channel {} because user has no valid access token", username, channelName);
}
}
}
Sample use of the pattern - the message will be sent only if the generated token is valid.
public class SecurityProxyDemo {
public static void main(String[] args) {
TokenValidator tokenValidator = new TokenValidator();
SessionTokens sessionTokens = new SessionTokens();
String userName = "Andrzej";
String channelName = "design_patterns";
String message = "I will learn what proxy is!";
sessionTokens.createTokenForUser(userName);
MessageSender realMessageSender = new SlackMessageSender();
MessageSender proxy = new SlackMessageSenderProxy(realMessageSender, sessionTokens, tokenValidator);
proxy.sendMessage(channelName, userName, message);
}
}