首先先把源码添出来:源码下载链接
你的支持是我前进的动力,欢迎star.
接下来进入正题:在学习使用注解之前一定要理解注解的定义,明白注解的各部分构成。文章也主要从这几个方面讲解,后面附带几个小案例。
什么是注解:
- 百度百科 —— java.lang.annotation.Retention可以在您定义Annotation型态时,指示编译器如何对待您的自定义 Annotation,预设上编译器会将Annotation资讯留在class档案中,但不被虚拟机器读取,而仅用于编译器或工具程式运行时提供资讯。
- 个人理解:注解是一种元数据, 可以添加到java代码中. 类、方法、变量、参数、包都可以被注解,注解对注解的代码没有直接影响.
注解有哪些优缺点?
- 优点:
越大的项目,使用注解就越清晰,代码可读性越高,维护起来就越简单。简单来说,通过注解,可以使我们的开发更方便简- 缺点: 理解起来有一定的难度……
注解的讲解?
元注解
Java内置的注解有Override, Deprecated, SuppressWarnings等.
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
Overrider上的两个注解我们称之为元注解。简单来说就是用来定义注解的注解我们叫做元注解。其作用就是定义注解的作用范围,使用在什么元素上面。
元注解一共有四种:
@Retention,@Target,@Inherited,@Documented
@Target
@Target说明了Annotation所修饰的对象范围:Annotation可被用于 packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)。在Annotation类型的声明中使用了target可更加明晰其修饰的目标。
作用: 用来描述注解可以在什么地方使用(使用范围)
取值(ElementType):
1. CONSTRUCTOR:用于描述构造器
2. FIELD:用于描述域
3. LOCAL_VARIABLE:用于描述局部变量
4. METHOD:用于描述方法
5. PACKAGE:用于描述包
6. PARAMETER:用于描述参数
7. TYPE:用于描述类、接口(包括注解类型) 或enum声明
@Retention
@Retention定义了该Annotation被保留的时间长短:某些Annotation仅出现在源代码中,而被编译器丢弃;而另一些却被编译在class文件中;编译在class文件中的Annotation可能会被虚拟机忽略,而另一些在class被装载时将被读取(请注意并不影响class的执行,因为Annotation与class在使用上是被分离的)。使用这个meta-Annotation可以对 Annotation的“生命周期”限制。
作用:用来描述注解的生命周期(注解的使用范围)
取值(RetentionPoicy):
1. SOURCE:在源文件中有效(即源文件保留)
2. CLASS:在class文件中有效(即class保留)
3. RUNTIME:在运行时有效(即运行时保留)
@Documented
@Documented用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,因此可以被例如javadoc此类的工具文档化。Documented是一个标记注解,没有成员。
@Inherited:
@Inherited 元注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。
注意:@Inherited annotation类型是被标注过的class的子类所继承。类并不从它所实现的接口继承annotation,方法并不从它所重载的方法继承annotation。 当@Inherited annotation类型标注的annotation的Retention是RetentionPolicy.RUNTIME,则反射API增强了这种继承性。如果我们使用java.lang.reflect去查询一个@Inherited annotation类型的annotation时,反射代码检查将展开工作:检查class和其父类,直到发现指定的annotation类型被发现,或者到达类继承结构的顶层。
这其中, @Retention是定义保留策略, 直接决定了我们用何种方式解析. SOUCE级别的注解是用来标记的, 比如Override, SuppressWarnings. 我们真正使用的类型是CLASS(编译时)和RUNTIME(运行时)
自定义注解
使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口,由编译程序自动完成其他细节。在定义注解时,不能继承其他的注解或接口。@interface用来声明一个注解,其中的每一个方法实际上是声明了一个配置参数。方法的名称就是参数的名称,返回值类型就是参数的类型(返回值类型只能是基本类型、Class、String、enum)。可以通过default来声明参数的默认值。
自定义注解的格式:
public @interface 注解名 {定义体}
注解参数的可支持数据类型:
- 所有基本数据类型(int,float,boolean,byte,double,char,long,short)
- String类型
- Class类型
- enum类型
- Annotation类型
- 以上所有类型的数组
案列1(布局文件的注解)
简单的Activity布局设置
注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ContentView {
int value();
}
反射类:
public class ViewUtils {
public static void injectContentView(Activity activity){
Class a = activity.getClass();
if (a.isAnnotationPresent(ContentView.class)) {
// 得到activity这个类的ContentView注解
ContentView contentView = (ContentView) a.getAnnotation(ContentView.class);
// 得到注解的值
int layoutId = contentView.value();
// 使用反射调用setContentView
try {
Method method = a.getMethod("setContentView", int.class);
method.setAccessible(true);
method.invoke(activity, layoutId);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
}
使用类:
@ContentView(R.layout.activity_annotation2)
public class Annotation2Activity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ViewUtils.injectContentView(this);
}
}
案列2(字段注解)
Butterknife相信大家都用过,怎么实现的呢?下面我们来看看如何通过注解反射来实现一个简单的ButterKnife:
注解:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ViewInject {
int value();
}
反射
public static void injectViews(Activity activity){
Class a = activity.getClass();
Field[] fields = a.getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(ViewInject.class)) {
ViewInject viewInject = field.getAnnotation(ViewInject.class);
int viewId = viewInject.value();
try {
Method method = a.getMethod("findViewById", int.class);
method.setAccessible(true);
Object invoke = method.invoke(activity, viewId);
field.setAccessible(true);
field.set(activity,invoke);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
使用方法:
@ContentView(R.layout.activity_annotation2)
public class Annotation2Activity extends AppCompatActivity {
@ViewInject(R.id.annotation_btn1)
Button mButton1;
@ViewInject(R.id.annotation_btn2)
Button mButton2;
@ViewInject(R.id.annotation_text)
TextView mTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ViewUtils.injectContentView(this);
ViewUtils.injectViews(this);
}
}
案列3(字段注解)
在请求网络数据时,会提供接口地址、请求数据等等一些参数,接下来展示的时如何利用反射和注解来封装我们的请求部分:
枚举
public enum Host {
Aserbao_1("https://One"),
Aserbao_2("https://Two"),
Aserbao_3("https://Three");
private String host;
Host(String host){
this.host = host;
}
public String getHost(){
return host;
}
}
参数注解:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface RequestParamsKey {
String key();
}
类注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface RequestParamsUrl {
//接口地址
String url();
//端口
Host host() default Host.Aserbao_1;
//缓存
boolean isCache() default false;
}
输入参数类:
@RequestParamsUrl(url = "getLocation.php", isCache = true, host = Host.Aserbao_2)
public class LocationReq {
@RequestParamsKey(key = "lat_key")
public String lat;
@RequestParamsKey(key = "lan_key")
public String lan;
}
反射解析类:
public class RequestParam {
public RequestParam() {
}
public static String getParam(Class<?> _clazz,Object _object){
Class<?> clazz = _clazz;
Field[] fields = clazz.getDeclaredFields();
try {
return requestParam(fields, clazz, _object);
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
/**
* 获取请求路径
*
* @param fields
* @param clazz
* @param _object
* @return
* @throws IllegalAccessException
* @throws IllegalArgumentException
*/
private static String requestParam(Field[] fields, Class<?> clazz, Object _object) throws IllegalArgumentException, IllegalAccessException {
StringBuilder request = new StringBuilder();
RequestParamsUrl requestParamsUrl = (RequestParamsUrl) clazz.getAnnotation(RequestParamsUrl.class);
if (requestParamsUrl != null) {
String url = requestParamsUrl.url();
boolean isCache = requestParamsUrl.isCache();
String host = requestParamsUrl.host().getHost();
request.append(host);
request.append(url);
request.append("?");
System.out.println("请求端口:" + host);
System.out.println("请求地址:" + url);
System.out.println("是否缓存:" + isCache);
}
for (Field field : fields) {
RequestParamsKey requestParamsKey = field.getAnnotation(RequestParamsKey.class);
if (requestParamsKey != null) {
String key = requestParamsKey.key();
String Value = (String) field.get(_object);
request.append(key);
request.append("=");
request.append(Value);
request.append("&");
}
}
request.deleteCharAt(request.length() - 1);
System.out.println("请求路径:" + request.toString());
return request.toString();
}
}
结果展示类:
public class AnnationActivity extends AppCompatActivity {
public LocationReq mLocation;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_annation);
}
public void btn_one(View view) {
config();
Toast.makeText(AnnationActivity.this, RequestParam.getParam(mLocation.getClass(),mLocation), Toast.LENGTH_SHORT).show();
}
/**
* 设置请求参数
*/
private void config() {
mLocation = new LocationReq();
mLocation.lan = "123.09";
mLocation.lat = "232.34";
}
}
案列4(事件的注解)
注解:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface EventBase {
Class listenerType();
String listenerSetter();
String methodName();
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@EventBase(listenerType = View.OnClickListener.class,listenerSetter = "setOnClickListener",methodName = "onClick")
public @interface OnClick {
int[] value();
}
反射
public static void injectEvents(Activity activity){
Class<? extends Activity> aClass = activity.getClass();
Method[] methods = aClass.getDeclaredMethods();
for (Method method : methods) {
if (method.isAnnotationPresent(OnClick.class)) {
//得到该方法上的OnClick注解
OnClick onClick = method.getAnnotation(OnClick.class);
//获取到OnClick注解的值
int[] viewIds = onClick.value();
//得到OnClick注解上的EventBase注解
EventBase eventBase = onClick.annotationType().getAnnotation(EventBase.class);
String listenerSetter = eventBase.listenerSetter();
Class listenerType = eventBase.listenerType();
String methodName = eventBase.methodName();
//使用动态代理
DynamicHandler handler = new DynamicHandler(activity);
Object listener = Proxy.newProxyInstance(listenerType.getClassLoader(), new Class<?>[] { listenerType }, handler);
handler.addMethod(methodName, method);
// 为每个view设置点击事件
for (int viewId : viewIds) {
try {
Method findViewByIdMethod = aClass.getMethod("findViewById", int.class);
findViewByIdMethod.setAccessible(true);
View view = (View) findViewByIdMethod.invoke(activity, viewId);
Method setEventListenerMethod = view.getClass().getMethod(listenerSetter, listenerType);
setEventListenerMethod.setAccessible(true);
setEventListenerMethod.invoke(view, listener);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
动态代理:
public class DynamicHandler implements InvocationHandler {
private final HashMap<String,Method> methodMap = new HashMap<>(1);
// 因为传进来的为activity,使用弱引用主要是为了防止内存泄漏
private WeakReference<Object> handlerRef;
public DynamicHandler(Object object){
this.handlerRef = new WeakReference<Object>(object);
}
public void addMethod(String name,Method method){
methodMap.put(name,method);
}
// 当回到OnClickListener的OnClick方法的时候,它会调用这里的invoke方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 得到activity实例
Object handler = handlerRef.get();
if (handler != null) {
// method对应的就是回调方法OnClick,得到方法名
String methodName = method.getName();
// 得到activtiy里面的clickBtnInvoked方法
method = methodMap.get(methodName);
if (method != null) {
// 回调clickBtnInvoked方法
return method.invoke(handler,args);
}
}
return null;
}
}
使用方法:
@ContentView(R.layout.activity_annotation2)
public class Annotation2Activity extends AppCompatActivity {
@ViewInject(R.id.annotation_btn1)
Button mButton1;
@ViewInject(R.id.annotation_btn2)
Button mButton2;
@ViewInject(R.id.annotation_text)
TextView mTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ViewUtils.injectContentView(this);
ViewUtils.injectViews(this);
ViewUtils.injectEvents(this);
}
@Override
protected void onResume() {
super.onResume();
init();
}
private void init() {
mTextView.setText("The second part is already done");
}
@OnClick({R.id.annotation_btn1, R.id.annotation_btn2})
public void clickBtnInvoked(View view){
switch (view.getId()){
case R.id.annotation_btn1:
Toast.makeText(this, "toast num one", Toast.LENGTH_SHORT).show();
break;
case R.id.annotation_btn2:
Toast.makeText(this, "toast num two", Toast.LENGTH_SHORT).show();
break;
}
}
}
这里写一下ViewUtils的代码:
public class ViewUtils {
public static void injectContentView(Activity activity){
Class a = activity.getClass();
if (a.isAnnotationPresent(ContentView.class)) {
// 得到activity这个类的ContentView注解
ContentView contentView = (ContentView) a.getAnnotation(ContentView.class);
// 得到注解的值
int layoutId = contentView.value();
// 使用反射调用setContentView
try {
Method method = a.getMethod("setContentView", int.class);
method.setAccessible(true);
method.invoke(activity, layoutId);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
public static void injectViews(Activity activity){
Class a = activity.getClass();
Field[] fields = a.getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(ViewInject.class)) {
ViewInject viewInject = field.getAnnotation(ViewInject.class);
int viewId = viewInject.value();
try {
Method method = a.getMethod("findViewById", int.class);
method.setAccessible(true);
Object invoke = method.invoke(activity, viewId);
field.setAccessible(true);
field.set(activity,invoke);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
public static void injectEvents(Activity activity){
Class<? extends Activity> aClass = activity.getClass();
Method[] methods = aClass.getDeclaredMethods();
for (Method method : methods) {
if (method.isAnnotationPresent(OnClick.class)) {
//得到该方法上的OnClick注解
OnClick onClick = method.getAnnotation(OnClick.class);
//获取到OnClick注解的值
int[] viewIds = onClick.value();
//得到OnClick注解上的EventBase注解
EventBase eventBase = onClick.annotationType().getAnnotation(EventBase.class);
String listenerSetter = eventBase.listenerSetter();
Class listenerType = eventBase.listenerType();
String methodName = eventBase.methodName();
//使用动态代理
DynamicHandler handler = new DynamicHandler(activity);
Object listener = Proxy.newProxyInstance(listenerType.getClassLoader(), new Class<?>[] { listenerType }, handler);
handler.addMethod(methodName, method);
// 为每个view设置点击事件
for (int viewId : viewIds) {
try {
Method findViewByIdMethod = aClass.getMethod("findViewById", int.class);
findViewByIdMethod.setAccessible(true);
View view = (View) findViewByIdMethod.invoke(activity, viewId);
Method setEventListenerMethod = view.getClass().getMethod(listenerSetter, listenerType);
setEventListenerMethod.setAccessible(true);
setEventListenerMethod.invoke(view, listener);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
}
源码下载链接你的支持是我前进的动力,欢迎star.有问题欢迎留言。