1. 程式人生 > >【Java】用註解實現注入

【Java】用註解實現注入

在Spring中,可以通過包掃描,找到帶有註解的類和方法,通過反射機制進行注入; 接下來會仿照這種模式,簡單模擬其原理,完成核心效果:

類標識的註解,只有帶有該標識,才進行之後方法的掃描,否則不進行:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target
(ElementType.TYPE) public @interface Service { }

方法的註解, 必須對註解中的action賦值,往後我們是要將action的值作為map中的鍵:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target
(ElementType.METHOD) public @interface Actioner { String action(); }

方法引數的註解,同樣要對其name賦值,為了以後能夠找到對應的引數,完成賦值:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.
RUNTIME) @Target(ElementType.PARAMETER) public @interface AParameter { String name(); }

我們需要將方法抽象成一個類,封裝起來:

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.List;

public class ActionDefination {
	private Class<?> klass; 										// 該
	方法所對應的類
	private Object object; 											// 該執行該方法的物件
	private Method method; 										// 該方法
	private List<Parameter> paramerterList;			// 該方法的所有引數
	
	protected ActionDefination(Class<?> klass, Object object, Method method, List<Parameter> paramerterList) {
		this.klass = klass;
		this.object = object;
		this.method = method;
		this.paramerterList = paramerterList;
	}

	protected Class<?> getKlass() {
		return klass;
	}

	protected Object getObject() {
		return object;
	}

	protected Method getMethod() {
		return method;
	}

	protected List<Parameter> getParamerterList() {
		return paramerterList;
	}
	
}

所有準備工作都做好了,我們就需要通過包掃描的方式,找到帶有Service 註解的類,然後找到其中帶有Actioner 註解的方法,並且得到帶有註解的所有引數,若其某一引數沒有帶註解,則應該異常處理!

掃描包下符合要求的所有內容,最後形成一張map

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class ActionFactory {
	private static final Map<String, ActionDefination> actionDefinationMap = new HashMap<String, ActionDefination>();
	
	// 單例模式
	private ActionFactory() {
	}
	public static ActionFactory newInstance() {
		return creatNewInstance.actionFactory;
	} 
	
	private static class creatNewInstance {
		private static final ActionFactory actionFactory = new ActionFactory(); 
	}
	
	// 通過類,掃描其所在包下的所有檔案
	public void scanAction(Class<?> klass) {
		scanAction(klass.getPackage().getName());
	}
	
	//  通過包名稱,掃描其下所有檔案
	public void scanAction(String packageName) {
		// 包掃描,在我的上一篇部落格有該方法的實現
		new PackageScanner() {
			
			@Override
			public void dealClass(Class<?> klass) {
				// 只處理帶有Service註解的類
				if (!klass.isAnnotationPresent(Service.class)) {
					return;
				}
				try {
					// 直接由反射機制產生一個物件,將其注入
					Object object = klass.newInstance();
					// 掃描改類下的所有方法
					scanMethod(klass, object);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}.scanPackage(packageName);
	}
	
	// 通過物件,掃描其所有方法
	public void scanAction(Object object) {
		try {
			scanMethod(object.getClass(), object);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private void scanMethod(Class<?> klass, Object object) throws Exception {
		// 得到所有方法
		Method[] methods = klass.getDeclaredMethods();
		
		// 遍歷所有方法,找到帶有Actioner註解的方法,並得到action的值
		for (Method method : methods) {
			if (!method.isAnnotationPresent(Actioner.class)) {
				continue;
			}
			Actioner actioner = method.getAnnotation(Actioner.class);
			String action = actioner.action();
			
			// 判斷action是否已經定義
			if (actionDefinationMap.get(action) != null) {
				throw new ActionHasDefineException("方法" + action + "已定義!");
			}
			
			// 得到所有引數,並判斷引數是否滿足要求
			Parameter[] parameters = method.getParameters();
			List<Parameter> parameterList = new ArrayList<Parameter>();
			for (int i = 0; i < parameters.length; i++) {
				Parameter parameter = parameters[i];
				if (!parameters[i].isAnnotationPresent(AParameter.class)) {
					throw new MethodParameterNotDefineException("第" + (i+1) + "個引數未定義!");
				}
				
				parameterList.add(parameter);
			}
			// 將得到的結果新增到map中
			addActionDefination(action, klass, object, method, parameterList);
		}
	}
	
		private void addActionDefination(String action, Class<?> klass, Object object, Method method, List<Parameter> parameterList) {
		ActionDefination actionDefination = new ActionDefination(klass, object, method, parameterList);
		actionDefinationMap.put(action, actionDefination);
	}
		
	protected ActionDefination getActionDefination(String action) {
		return actionDefinationMap.get(action);
	}
	
}

上述的ActionFactory可以幫助我們掃描到包下所有符合要求的方法,接下來就是通過傳遞引數執行這些方法。 要注意,這套工具的出發點是搭載在網路上,所以傳遞的引數就只能是字串或者位元組流的形式,所以,我們應該對傳遞的引數進行處理,將其生成能供我們識別的形式。 這裡我們將引數轉換為字串的形式,我會用到gson,方便我們將物件轉換為gson字串:

import java.util.HashMap;
import java.util.Map;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

public class ArgumentMaker {
	// 註解AParameter中name的值 +  引數物件轉換成的gson字串所形成的map
	private Map<String, String> argumentMap;
	private Gson gson;
	
	public ArgumentMaker() {
		gson = new GsonBuilder().create();
		argumentMap = new HashMap<String, String>();
	}
	
	// 其name就是註解AParameter中name的值,value就是引數的具體值
	public ArgumentMaker add(String name, Object value) {
		// 通過gson將引數物件轉換為gson字串
		argumentMap.put(name, gson.toJson(value));
		return this;
	}
	
	// 將得到的name + 引數物件轉換成的gson字串map再次轉換成gson字串,以便於進行傳輸
	public String toOgnl() {
		if (argumentMap.isEmpty()) {
			return null;
		}
		
		return gson.toJson(argumentMap);
	}
	
}

接下來就是處理具體的action

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;

public class Addition {
	private static final Gson gson;
	private static final Type type;
	
	static {
		gson = new GsonBuilder().create();
		// 可以得到帶有泛型的map型別 
		type = new TypeToken<Map<String, String>>(){}.getType();
	}
	
	public String doRequest(String action, String parameter) throws Exception {
		ActionDefination ad = ActionFactory.newInstance().getActionDefination(action);
		
		if (ad == null) {
			throw new ActionNotDefineException("方法" + action + "未定義!");
		}
		
		Object object = ad.getObject();
		Method method = ad.getMethod();
		
		Object[] parameters = getParameterArr(parameter, ad.getParamerterList());
		Object result = method.invoke(object, parameters);
		
		return gson.toJson(result);
	}
	
	private Object[] getParameterArr(String parameterString, List<Parameter> parameterList) {
		Object[] results = new Object[parameterList.size()];
		// 將字串形式的引數,轉換成map
		Map<String, String> parameterStringMap = gson.fromJson(parameterString, type);
		
		int index = 0;
		for (Parameter parameter : parameterList) {
			// 得到引數的註解AParameter中name的值
			String key = parameter.getAnnotation(AParameter.class).name();
			
			// 以name的值為鍵,找到引數map中value,再通過gson將其從字串轉換成具體的物件
			Object value = gson.fromJson(parameterStringMap.get(key),
					// 得到引數的具體型別 
					parameter.getParameterizedType());
			
			results[index++] = value;
		}
		
		return results;
	}

演示如何使用

@Service
public class Demo {

	@Actioner(action="one")
	public void fun() {
		System.out.println("執行無參的fun方法");
	}
	
	@Actioner(action="two")
	public void fun(@AParameter(name="1")int parameter) {
		System.out.println("執行單參的fun方法: parameter = " + parameter);
	}
	
	@Actioner(action="three")
	public void fun(@AParameter(name="1")int one, 
			@AParameter(name="2")String two, 
			@AParameter(name="3")boolean three) {
		System.out.println("執行三參的fun方法: one = " + one + " two = " + two + " three = " + three);
	}
	
	private static class Student {
		private String name;
		private int age;
		private boolean sex;
		
		private Student(String name, int age, boolean sex) {
			this.name = name;
			this.age = age;
			this.sex =sex;
		}
		
		@Override
		public String toString() {
			return "name = " + name + ", age = " + age + ", sex = " + (sex ? "男" : "女");
		}
	}
	
	@Actioner(action="four")
	public void fun(@AParameter(name="1")Student student) {
		System.out.println("執行復雜型別引數的fun方法 :" + student);
	}
	
	public static void main(String[] args) throws Exception {
		ActionFactory.newInstance().scanAction("com.zc.server_client.actioner.demo");
		
		Addition addition = new Addition();
		
		addition.doRequest("one", null);
		
		addition.doRequest("two", new ArgumentMaker()
				.add("1", 3)
				.toOgnl());
		
		addition.doRequest("three",new ArgumentMaker()
				.add("3", true)
				.add("1", 3)
				.add("2", "這是第二個引數")
				.toOgnl());
	
		Student student = new Student("小明", 15, true);
		addition.doRequest("four", new ArgumentMaker()
				.add("1", student)
				.toOgnl());
	}

}

這樣做,我們的這套工具就可以完全獨立出來,只需要使用者向我們的框架中注入規定的方法,而不去關心其具體實現,達到了解耦!主要在於找到其對應關係,也可以通過XML檔案的方式形成對應關係,但是用XML檔案對於其引數的處理就會比較麻煩,所以在structs2中,提供了帶有無參方法的介面,讓使用者去實現這個介面,而其所需要的引數是利用其成員來完成的,要給其成員賦值! 我們可以在伺服器和客戶端分別寫出請求和響應的方法,通過網路間字串的傳遞,在伺服器和客戶端分別執行我們注入的方法!