Annotation与动态代理(Proxy)
本文是作者个人总结,如有纰漏请指正
在阅读下面内容前,希望读者有动态代理和Annotation方面的相关知识。
像mybatis这样的开源框架,在接口上进行标记,之后框架在运行时会生成实际的逻辑代码,执行后将结果返回给用户。例如:
publicinterfaceUserMapper{
@Select("SELECT * FROM users WHERE id = #{userId}")
User getUser(@Param("userId")String userId);
}
本文主要是探讨这个使用Annotation和Proxy如何达到mybatis这种效果。
本文以weather获取为例,weather的接口定义如下:
@URL("http://m.weather.com.cn/data/{city}.html")
publicinterface WeatherHttp {
public String getWeather(@ParamUrl("city") String city);
}
接口中定义了两个Annotation分别是URL和ParamUrl,URL表示接口要访问的网站,而ParamUrl表示访问网站Url中需要替换的参数。
两个Annotation定义如下:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public@interfaceURL {
publicabstract String value();
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public@interfaceParamUrl {
publicabstract String value();
}
现在,我期望用动态代理的形式为这个类生成一个远程的访问,对于客户端,可以使用如下的方式获取信息:
publicclass WeatherTest {
publicstaticvoid main(String[] args) {
WeatherHttp weatherHttp = MapperProxy.newMapperProxy(WeatherHttp.class);
System.out.println(weatherHttp.getWeather("101010100"));
}
}
通过MapperProxy获取一个代理实例,然后执行这个代理实例,最终获取期望的结果。
动态代理的代码如下:
publicclass MapperProxy implements InvocationHandler {
privatestaticfinal Set<String> OBJECT_METHODS = new HashSet<String>() {
{
add("toString");
add("getClass");
add("hashCode");
add("equals");
add("wait");
add("notify");
add("notifyAll");
}
};
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
try {
if (!OBJECT_METHODS.contains(method.getName())) {
final Class<?> declaringInterface = findDeclaringInterface(proxy,
method);
if (declaringInterface == null) {
thrownew Exception(
"Could not find interface with the given method "
+ method);
}
MapperMethod mapMethod = new MapperMethod(declaringInterface,method);
return mapMethod.execute(args);
}
} catch (Exception e) {
e.printStackTrace();
}
returnnull;
}
private Class<?> findDeclaringInterface(Object proxy, Method method) {
Class<?> declaringInterface = null;
for (Class<?> iface : proxy.getClass().getInterfaces()) {
try {
Method m = iface.getMethod(method.getName(),
method.getParameterTypes());
if (declaringInterface != null) {
thrownew Exception(
"Ambiguous method mapping. Two mapper interfaces contain the identical method signature for "
+ method);
} elseif (m != null) {
declaringInterface = iface;
}
} catch (Exception e) {
}
}
if (declaringInterface == null) {
}
return declaringInterface;
}
@SuppressWarnings("unchecked")
publicstatic <T> T newMapperProxy(Class<T> mapperInterface) {
ClassLoader classLoader = mapperInterface.getClassLoader();
Class<?>[] interfaces = new Class[]{mapperInterface};
MapperProxy proxy = new MapperProxy();
return (T) Proxy.newProxyInstance(classLoader, interfaces, proxy);
}
}
动态代理实现了InvocationHandler接口,这样当用户调用接口时,会调用invoke方法,在invoke方法中会创建MapperMethod实例,并调用其中的excute方法,MapperMethod方法定义如下:
publicclass MapperMethod {
private Class<?> declaringInterface;
private Method method;
private String url;
private Map<String,Integer> paramUrl = new HashMap<String, Integer>();
private RestTemplate template = new RestTemplate();
public MapperMethod(Class<?> declaringInterface, Method method) {
this.declaringInterface =declaringInterface;
this.method = method;
prepareUrl();
prepareParam();
}
privatevoid prepareParam() {
for (int i = 0; i < method.getParameterTypes().length; i++) {
Object[] paramAnnos = method.getParameterAnnotations()[i];
for (int j = 0; j < paramAnnos.length; j++) {
if (paramAnnos[j] instanceofParamUrl) {
paramUrl.put(((ParamUrl) paramAnnos[j]).value(), i);
}
}
}
}
privatevoid prepareUrl() {
Annotation[] annotations = declaringInterface.getAnnotations();
for(Annotation anno: annotations){
if(anno instanceofURL){
url = ((URL)anno).value();
}
}
}
public Object execute(Object[] args) {
Map<String,Object> newMap = new HashMap<String, Object>();;
if(paramUrl != null & !paramUrl.isEmpty()){
for(Map.Entry<String, Integer> e : paramUrl.entrySet()){
newMap.put(e.getKey(), args[e.getValue()]);
}
}
returntemplate.getForObject(url, String.class,newMap);
}
}
可见,最终MapperMethod方法,根据Annotation的标记信息,最终执行需要的逻辑。
简要回顾:用户使用Annotation标记其使用的接口,通过MapperProxy获取接口的实例,当调用接口的方法后会触发MapperProxy的invoke方法的调用,在invoke方法中创建了MapperMethod对象,并调用了其excute方法,在MapperMethod中最终解析Annotation,并组织远程访问。
因此MapperProxy实现了动态代理,而MapperMethod最终解释Annotation并执行相应的处理程序。