java中的注解以及动态代理

  • Post author:
  • Post category:java


  1. Annotation注解,是一种代码级别的说明。与类、接口、枚举在同一个层次。
  2. 注释是给开发人员看的。

    注解

    是给计算机提供相应信息的。
  3. 注解可以在变量,方法,类之上加载;注解可以有属性也可以没有属性;
  4. 注解的作用范围:

    1、源码(如类中注释[帮助文档]中的注解):String类上的@Author,@Since,@See,作用:使用命令javadoc命令将当前的源码生成帮助文档,可以识别String类上的相关注解;

    2、编译期间(如@override,@suppressWarnings,@deprecated):告诉编译器部分信息;

    3、运行期间(如@Test(timeout=1000)):当我们在当前代码上以Junit方式运行时,Junit会运行方法上包含@Test注解的方法。
  5. 作用总结:

    1、编译检查:通过代码里的标识注解,让编译能够实现基本的编译检查。

    2、代码分析:通过代码里的标识注解,对代码进行分析,从而达到取代XML的目的。

    3、编写文档:通过代码里的标识注解,辅助生成帮助文档对应的内容。
  6. 自定义注解:

    public @interface MyAnno01 {    //自定义注解
    //注解的属性支持的类型有:基本数据类型,String,Class,Annotation,枚举类型,以及以上类型的一维数组类型
        public long timeout() default -1;      //给注解定义了一个属性
        public Class cc() default java.util.Date.class; //属性支持字节码类型
    }

    public @interface 注解名称{


    public 属性类型 属性名称();

    public 属性类型 属性名称() default 默认值;

    }


  7. 注解作用:配置作用


    配置:开发的时候部分信息不希望写死在程序中,例如数据库的用户名和密码,可以将用户名和密码存放在.txt,.properities,.xml文件中,利用程序来读取文件中的内容。
  8. 框架:一大堆工具类的组合,目的:加速项目开发。框架部分hibernate,spring,struts2很多信息需要配置,提供了2种形式的配置(XML和注解)。
  9. 如果配置信息不会发生频繁的改动,如servlet路径,建议使用注解的形式。
  10. @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface Test {
      boolean ignore() default false;
    }

    使用注解后,可以在运行时,

    通过反射的方式取出方法的注解

    (读取字节码信息),如果这个注解是@Test,并且没有被忽略,那就可以通过反射的方式去执行这个方法了。

  11. public class TestUtil {
    //    在运行时通过反射的方式取得方法的注解,通过判断注解来执行。
        public static void main(String[] args) throws Exception {
    //        run(new TestTest());//方式一:new 对象再反射获取
            run1();//方式2:反射方式获取
        }
        public static void run(Object obj) throws Exception {
            for(Method m : obj.getClass().getMethods()){
                Test t = m.getDeclaredAnnotation(Test.class);
                if(t != null && !t.ignore()){
                    m.invoke(obj);
                }
            }
        }
        public static void run1() throws ClassNotFoundException, IllegalAccessException, InstantiationException, InvocationTargetException {
    //        将TestTest的字节码文件加载到内存中,Class对象(代表字节码文件在内存中的对象)
            Class clazz = Class.forName("AnnotationTest.Tests.TestTest");
            Object obj = clazz.newInstance();
    //        获取字节码上的所有方法
            for(Method m : clazz.getMethods()){
    //            System.out.println(m.getName());//打印方法名称
    //            System.out.println( m.isAnnotationPresent(Test.class));//打印方法上是否有注解
                Test t = m.getAnnotation(Test.class);
                if(t != null && !t.ignore()){
    //              m.invoke(obj);//方式1
                    m.invoke(new TestTest());//方式2
                }
    
            }
        }
    }
  12. 其在开发中的地位:

    1)类似dom4j解析XML文件,程序员不会去解析XML文件。

    2)自己的开发中不会自定义注解,也不会使用反射读取注解信息。
  13. 设计模式初步:

    1)

    单例模式

    :如QQ客户端与腾讯服务器连接,保证一个客户端与服务器只有一次连接(仅生成一个对象)(如:Connection conn = DriverManager.getConnection(); )
  14. public class Stu {
        private Stu() {
        }
        private static Stu stu = new Stu();
        public static Stu getInstance(){
            return stu;
        }
    }

    2)

    装饰者模式

    :Java提供了ICar接口、GoogleCar提供了接口的实现;希望在谷歌Car接入到生态圈平台时,增加启动方法中的一些功能。

    使用场景:二次开发的时候,无法获取到源码,无法使用继承前提下,要对已经存在对象上的功能进行增强。

    解决:自己定义一个装饰类MyCar实现ICar接口,为自定义装饰类传递被修饰的对象。

    弊端:如果被实现的接口中的方法过多,装饰类中的方法过多冗余

    public class MyCar implements Icar{
        Icar icar;
        public MyCar(Icar car){
            this.icar = car;//构造方法需要传入GoogleCar即扩展了GoogleCar的实现
        }
        @Override
        public void run() {
            icar.run();
        }
    
        @Override
        public void start() {
            System.out.println("天气");//增加的功能
            icar.start();
        }
    
        @Override
        public void stop() {
            icar.stop();
        }
    }


  15. 动态代理

    来解决上述弊端问题:原理:通过虚拟机在内存中创建类似MyClass.class文件

    public class Test {
        public static void main(String[] args){
    //        Proxy.newProxyInstance(TestCar.class.getClassLoader(),
    //                GoogelCar.class.getInterfaces(),  new liu());
    
    //        使用匿名内部类
    //        参数1:类加载器加载内存中创建的字节码文件
    //        参数2:告诉虚拟机内存中正在被创建的字节码文件中应该有哪些方法
    //        参数3:告诉虚拟机正在被创建的字节码上的各个方法如何处理
            Icar car = (Icar) Proxy.newProxyInstance(Test.class.getClassLoader(),
                    GoogelCar.class.getInterfaces(), new InvocationHandler() {
    //          invoke方法:method代表正在执行的方法;args代表正在执行的方法中的参数;Object代
    //                      表方法执行完毕之后的返回值
                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    //                        method代表当前正在执行的每个方法
    //                        System.out.println(method.getName());
    //                        method.invoke(new GoogelCar());
                            if(method.getName().equalsIgnoreCase("start")){
                                method.invoke(new GoogelCar());
                                System.out.println("天气");
                            }else {
                                method.invoke(new GoogelCar());
                            }
                            return null;
                        }
                    });
            car.run();
            car.start();
            car.stop();
    
        }
    }
    class liu implements InvocationHandler{
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            return null;
        }
    }

  16. 代理(Proxy)

    可以在运行时创建一个实现了一组给定接口的新类。

    要创建一个代理对象,

    需要使用Proxy类的newProxyInstance方法

    ,这个方法有三个参数:

    1)一个类加载器;

    2)一个Class对象数组,每个元素都是需要实现的接口;

    3)一个调用处理器。

        public static Object newProxyInstance(ClassLoader loader,
                                              Class<?>[] interfaces,
                                              InvocationHandler h) 

    构造实现指定接口的代理类的一个新实例;所有方法会调用给定处理器对象的

    invoke

    方法;

    无论何时调用代理对象的方法,调用处理器的invoke方法都会被调用,并向其传递Method对象和原始的调用参数。调用处理器必须给出处理调用的放式。

  17. 例子:使用代理对象对二分查找进行跟踪,首先用1~1000整数的代理填充数组,然后调用Arrays类中的binarySearch方法在数组中查找一个随机整数。最后,打印出与之匹配的元素。

    1)实现InvocaHandler接口:

    1. class TraceHandler implements InvocationHandler {
          private Object target;
      
          public TraceHandler(Object t) {
              target = t;
          }
      
          public Object invoke(Object proxy, Method m, Object[] args) throws Throwable {
              System.out.print(target);
              System.out.print("." + m.getName() + "(");
              if (args != null) {
                  for (int i = 0; i < args.length; i++) {
                      System.out.print(args[i]);
                      if (i < args.length - 1) System.out.print(", ");
                  }
              }
              System.out.print(")");
              return m.invoke(target, args);
          }
      }

      2)实现过程:

      public class ProxyTest {
          public static void main(String[] args) {
              Object[] elements = new Object[100];
              for (int i = 0; i < elements.length; i++) {
                  Integer value = i + 1;//Interger类型的对象实现了Comparable接口
                  InvocationHandler handler = new TraceHandler(value);//调用处理器
                  Object proxy = Proxy.newProxyInstance(null, new Class[]{Comparable.class}, handler);//创建的一个实现Comparable接口的一个代理对象
                  elements[i] = proxy;
              }
              Integer key = new Random().nextInt(elements.length) + 1;
              int result = Arrays.binarySearch(elements, key);
              if (result >= 0) System.out.print(elements[result]);
          }
      }



版权声明:本文为liuzewei2015原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。