How does dynamic proxying work? You have some interface and class that implements that interface. You than create proxy instance for that interface where all client calls to interface methods can be intercepted and where you can do some action befeore the call is delegated to implementation class.
Suppose you have some simple interface class called UserApi with method authUser. Suppose you want to validate method parameters for not null condition - you can do it directly in implementing class in authUser metod but better way would be to remove this validation logic to separate class. This way you have nicer and cleaner code.
So I have UserApi interface and @NotNull annotation to mark parameters that will be validated for not null condition:
public interface UserApi { public UserData authUser(@NotNull String username, @NotNull String password); }
Simple implemenation class for showcase - username and password must not be null in our case:
public class UserApiImpl implements UserApi { @Override public UserData authUser(String username, String password) { //find and auth user from somewhere and return UserData UserData data = fetchData(username, password); return data; } }
And this is my simple annotation:
@Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface NotNull { }
Main part is instance of InvocationHandler class that serves as proxy to all method calls on our UserApi interface. InvocationHandlerImp can be generally used on any interface. Notice that InvocationHandlerImp implements special java.lang.reflect.InvocationHandler interface. Constructor parameter is real interface implementation that we are proxying. So when client calls authUser method on UserApi interface we intercept this call in invoke method and check parameters for not null validation.
class InvocationHandlerImp implements InvocationHandler { private final Object implementation; InvocationHandlerImp(Object realObject) { this.implementation = realObject; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //check if parameters marked with NotNull are valid Annotation[][] parameterAnnotations = method.getParameterAnnotations(); for(int i = 0; i < parameterAnnotations.length; i++) { for(Annotation annotation : parameterAnnotations[i]) { if(annotation instanceof NotNull) { if(args[i] == null) { throw new NullPointerException("@NotNull parameters must be provided"); } } } } try{ //call real method return method.invoke(implementation, args); } catch(InvocationTargetException e) { throw e.getTargetException(); } } }
Finally, you setup proxy instance like this:
UserApi api = (UserApi)Proxy.newProxyInstance( Thread.currentThread().getContextClassLoader(), new Class[] { UserApi.class }, new InvocationHandlerImp(new UserApiImpl())); UserData user = api.authUser("someuser", "somepassword");
If you call authUser method with null parameters, NullPointerException will be thrown.