Android中的IOC框架,完全注解方式就可以进行UI绑定和事件绑定

转载请注明出处:http://blog.csdn.net/blog_wang/article/details/38468547

相信很多使用过Afinal和Xutils的朋友会发现框架中自带View控件注解及事件绑定功能,我们无需使用findViewById和setOnClickListener即可完成view初始化和监听事件,使用注解在很大程度上使我们的代码看起来更加简洁,让我们的代码看起来不是那么冗余,那我们今天就来一探究竟,看看其中原理是如何来实现的。

Java注解相当于一种标记,标记可以加在包,类,字段,方法,方法的参数以及局部变量上。在程序中加入注解可以起到说明和配置的功能,Javac编译器,开发工具和其他程序可以用反射来了解你的类及各种元素上有无何种标记,根据你的标记,去做相应的事

自定义注解

系统定义了许多注解类,但是都不满足我们的需求,那么我们想要实现注解的功能,就需要自定义注解类型,我们首先来了解自定义注解所需要知道的东西。

元注解是指注解的注解。包括  @Retention @Target @Document @Inherited四种

@Target
@Target(ElementType.TYPE)   //接口、类、枚举、注解
@Target(ElementType.FIELD)  //字段、枚举的常量
@Target(ElementType.METHOD) //用于描述方法
@Target(ElementType.PARAMETER) //用于描述参数
@Target(ElementType.CONSTRUCTOR)  //用于描述构造器
@Target(ElementType.LOCAL_VARIABLE) //用于描述局部变量
@Target(ElementType.ANNOTATION_TYPE) //注解
@Target(ElementType.PACKAGE)  //用于描述包

Target表示自定义的注解类型在什么地方使用,可以看出一个Target能使用多个ElementType,也就是说我们自定义的一个注解类型可以在字段、接口、包、方法上等多个地方使用,如:@Target({ElementType.FIELD,ElementType.METHOD}) 因此这个注解可以是字段注解,也可以是方法的注解


@Retention

@Retention(RetentionPolicy.RUNTIME)  //在运行时有效
@Retention(RetentionPolicy.SOURCE) //在源文件中有效
@Retention(RetentionPolicy.CLASS) //在class文件中有效

Retention表示注解在什么范围内有效

@Document
Documented用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,因此可以被例如javadoc此类的工具文档化。Documented是一个标记注解,没有成员

@Inherited
Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。

注解参数的可支持数据类型:

1.所有基本数据类型(int,float,boolean,byte,double,char,long,short)
2.String类型
3.Class类型
4.enum类型
5.Annotation类型
6.以上所有类型的数组

Annotation类型里面的参数该设定:
1.只能用public或默认(default)这两个访问权修饰.例如,String value();这里把方法设为defaul默认类型
2.参数成员只能用基本类型byte,short,char,int,long,float,double,boolean八种基本数据类型和 String,Enum,Class,annotations等数据类型,以及这一些类型的数组.例如,String value();这里的参数成员就为String
3.如果只有一个参数成员,最好把参数名称设为"value",后加小括号

上面我们详细介绍了自定义注解类所能注解的类型、范围、注解参数的数据类型,以及注解在什么时期有效。OK,了解了那么多,那我们就来自己自定义一个注解类

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface InjectView{

    /**
     * 默认控件ID
     */
    public static int DEFAULT_ID = -1;
    
    /**
     * 默认方法
     */
    public static String DEFAULT_METHOD = "";
    
    /**
     * 功能:接收控件ID
     * @return 返回设置ID
     */
    public int id() default DEFAULT_ID;
    
    /**
     * 功能:接收控件点击方法名
     * @return 返回设置方法名
     */
    public String click() default DEFAULT_METHOD;
}
根据@Target和@Retention我们可以看出这个注解类用于描述字段、枚举,在运行时有效,跟在方法后面的default字段表示当没有使用该参数注解时,调用该方法会返回的默认值,那一个定义好的注解我们要如何使用呢?
public class MainActivity extends FrameActivity
{

    /**
     * 指定控件对应ID和点击事件需要调用的方法名
     * 
     * 这里的ID和click是自定义注解中属性名,多个参数用逗号分开
     * 
     */
    @InjectView(id = R.id.button,click = "click")
    private Button button;
    
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        //当走到这步时,button已经实例化了,并且可以相应点击事件,但是怎么做到的呢?
        button.setText("我是通过注解来实例化的");
    }
    
    public void click(View v){
        switch (v.getId()) {
        case R.id.button:
            Toast.makeText(this,"我被点击了",Toast.LENGTH_SHORT).show();
            break;
        default:
            break;
        }
    }
}
如果大家眼睛比较亮的话,相信大家已经看出我不是继承至Activity,对,这里初始化的工作全部放在FrameActivity类里面,方便其他Activity使用,我们来看看FrameActivity里面都做了什么操作
public class FrameActivity extends Activity{

    @Override
    public void setContentView(int layoutResID){
        super.setContentView(layoutResID);
        //在子类执行完setContentView方法之后调用
        traversalsField();
    }
    
    /**
     * 遍历类变量,获取变量注解
     */
    private void traversalsField(){
        //获取类所有属性,包括public,private,protected
        Field[] fields = getClass().getDeclaredFields();
        if(null != fields && fields.length > 0){
            for(Field field : fields){
                //判断属性注解是否属于自定义注解接口
                if(field.isAnnotationPresent(InjectView.class)){
                    //获取变量注解类型
                    InjectView injectView = field.getAnnotation(InjectView.class);
                    //得到设置的ID
                    int id = injectView.id();
                    //如果获取的ID不等于默认ID,则通过findViewById来查找出对象然后设置变量值
                    if(id != InjectView.DEFAULT_ID){
                        try{
                            //类中的成员变量为private,故必须进行此操作 
                            field.setAccessible(true);
                            field.set(this,findViewById(id));
                        }catch (IllegalArgumentException e){
                            e.printStackTrace();
                        }catch (IllegalAccessException e){
                            e.printStackTrace();
                        }
                    }
                    //得到设置方法名
                    String method = injectView.click();
                    if(!method.equals(InjectView.DEFAULT_METHOD)){
                        setViewClickListener(this,field,method);
                    }
                }
            }
        }
    }
    
    /**
     * 给View设置点击事件
     * @param injectedSource 类对象
     * @param field    属性
     * @param clickMethod 方法名
     */
    private void setViewClickListener(Object injectedSource,Field field,String clickMethod){
        try {
            //将属性转成Object类型
            Object obj = field.get(injectedSource);
            //判断Object类型是否是view的实例,如果是强转成view并设置点击事件
            if(obj instanceof View){
                ((View)obj).setOnClickListener(new EventListener(injectedSource).click(clickMethod));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    class EventListener implements OnClickListener{

        /**
         * 类对象
         */
        public Object obj;
        /**
         * 方法名
         */
        public String clickMethod;
        
        public EventListener(Object obj){
            this.obj = obj;
        }
        
        /**
         * click返回的是实现了OnClickListener接口的实例
         */
        public EventListener click(String clickMethod){
            this.clickMethod = clickMethod;
            return this;
        }
        
        //当view点击时会调用onClick方法
        @Override
        public void onClick(View v) {
            invokeClickMethod(obj, clickMethod, v);
        }
        
        private Object invokeClickMethod(Object obj, String methodName, Object... params){
            if(obj == null) {
                return null;
            }
            Method method = null;
            try{
                //获取类对象中以methodName和接受一个View参数的类型方法
                method = obj.getClass().getDeclaredMethod(methodName,View.class);
                if(method != null){
                    //类中的方法为private,故必须进行此操作 
                    method.setAccessible(true);
                    //执行方法,并传递当前对象
                    return method.invoke(obj, params);    
                }else{
                    throw new Exception("no such method:" + methodName);
                }
            }catch(Exception e){
                e.printStackTrace();
            }
            return null;
        }
    }
}
代码中的注释还算是比较详细的,这里我就不在细说,大概说下实现原理,首先主要是获取当前类对象的所有属性包括public、private、protected,然后循环判断属性是否采用注解并且是否是使用我们自定义的注解类,如果两种情况都满足则获取属性所指定的注解值,如果注解值不是默认的就表示指定了明确的值,这里就可以根据获取到的ID值来findViewById()来获取对象,并且设置给当前属性来完成初始化。
指定控件的点击事件就比较麻烦,首先需要先将属性转成Object对象,然后判断是否是View的实例,如果是则设置点击事件,在OnClickListener的onClick方法中获取当前类中以根据注解值来命名的方法,然后通过Method的invoke来调用执行,注意,这里传递了事件对象本身,所以我们方法中必须要加入参数。
至此,我们所有的代码都已经写完了,我们运行起来看看效果


源码下载,请点击这里

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我先来回答你的问题。 1. 用xml方式装配各服务层组件 首先,我们需要在xml配置文件声明组件扫描器,用于扫描指定包下的类。在这个例子,我们以`com.example.service`作为扫描的包路径。 ```xml <context:component-scan base-package="com.example.service"/> ``` 接着,在xml配置文件声明各个服务层组件,指定它们的类型、名称和依赖关系。 ```xml <bean id="userService" class="com.example.service.impl.UserServiceImpl"> <property name="userDao" ref="userDao"/> </bean> <bean id="userDao" class="com.example.dao.impl.UserDaoImpl"/> ``` 在这个例子,我们声明了`userService`和`userDao`两个组件,其`userService`依赖于`userDao`组件。 2. 用注解方式装配各服务层组件 除了xml方式,我们也可以用注解方式装配各个服务层组件。首先,在配置文件声明组件扫描器,用于扫描指定包下的类。 ```xml <context:component-scan base-package="com.example.service"/> ``` 然后,在服务层组件的类上加上`@Service`注解,并指定它的名称。 ```java @Service("userService") public class UserServiceImpl implements UserService { @Autowired private UserDao userDao; } ``` 在这个例子,我们用`@Service`注解声明了`UserServiceImpl`组件,并指定它的名称为`userService`。同时,我们在`UserServiceImpl`使用`@Autowired`注解自动装配了`UserDao`组件。 在Dao层组件的类上加上`@Repository`注解,并指定它的名称。 ```java @Repository("userDao") public class UserDaoImpl implements UserDao { } ``` 总结一下: - xml方式需要在配置文件声明每个组件,比较繁琐,但可以清晰地看到每个组件之间的依赖关系。 - 注解方式可以减少配置文件的编写,但需要在每个组件类上加上注解,并且不能直接看到组件之间的依赖关系,比较难以维护。 以上就是用spring框架IOC和依赖注入的方式,分别用xml和注解方式装配各服务层组件的方法。希望能对你有所帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值