zoukankan      html  css  js  c++  java
  • 类加载器、反射、注解和动态代理

    本节内容:

    • 类加载器
    • 反射
    • 注解 @xxx
    • 动态代理

    一、类加载器

    1. 什么是类加载器,作用是什么?

    类加载器(ClassLoader)就是加载字节码文件(.class)。站在累加载器的角度上看CodeSegment,就是一个一个Class对象。

    站在ClassLoader角度来讲,每个class就是一个class对象。而在每个class类对象里,又有属性、方法。

    2. ClassLoader的类加载机制

    并非一次性加载,需要的时候加载(运行期间动态加载)。不是从头到尾扫描,有多少类就加载进多少类,而是用到的时候才会去加载。

    【示例】:TestDynamicLoading.java

    package com.itheima.reflection;
    
    public class TestDynamicLoading {
    
    	public static void main(String[] args) {
    		new A();
    		System.out.println("=========");
    		new B();
    		
    		new C();
    		new C();
    		
    		new D();
    		new D();
    	}
    
    }
    
    class A {
    	
    }
    
    class B {
    	
    }
    
    class C {
    	static {
    		System.out.println("cccccccc");
    	}
    }
    
    class D {
    	{
    		System.out.println("dddddd");
    	}
    }

    如果A.class和B.class同时加载进来,那么“======”会在它们加载完成后打印出来;

    如果是看到A加载A,看到B才加载B,那么“======”会在它们中间。

    下面设置下运行配置,鼠标右击选择“Run As”,“Run Configuration”,(x)= Arguments,在“VM arguments”里输入:

    -verbose:class
    

    所以这叫做动态加载。

    另外,注意看类C中的static语句块,static语句块中的内容会在类加载进内存后被调用一次,注意无论你生成多少个C的对象,只会调用一次。  

    而类D中的代码叫动态语句块,每次new出来一个对象的时候就会去执行动态语句块中的内容。动态语句块用的不多。


    3. 类加载器的种类

    类加载器有三种,不同类加载器加载不同的字节码:

    • BootStrap:引导类加载器:加载都是最基础的文件。这是最核心的类加载器。
      • 这里的BootStrap和前端学习的BootStrap不是一个东西,前端学习的BootStrap是一个前端框架。这里的BootStrap底层是C语言写的,Java是个高级语言,它是操作不了硬件的。它主要加载的是JVM运行时的最基础的一些jar包。其他的类加载器都是Java写的。其他的类加载器要想工作的话,类加载器本身也是要加载到内存,下面的类加载器都是被BootStrap类加载器加载到内存中,然后其他的类加载器在加载其他的需要的class进内存。
    • ExtClassLoader:扩展类加载器:加载都是基础的文件,加载JVM的一些基础性jar包中的类。
    • AppClassLoader:应用类加载器:第三方jar包和自己编写java文件。
      • 第三方jar包和我们自己编写的jar文件是没区别的。我们自己写的java文件编译后也可以打成一个jar包。

    【注意】:上面的关系不是继承,是加载器对象中有一个引用指向了上一层的加载器对象。 

    怎么获得类加载器?(重点)

    ClassLoader 字节码对象.getClassLoader();

    获得类加载器之后,可以获得classes下任何资源:

    package com.itheima.classloader;
    
    public class Demo {
        public static void main(String[] args) {     
            //获得Demo字节码文件的类加载器
            Class clazz = Demo.class;//获得Demo的字节码对象
            ClassLoader classLoader = clazz.getClassLoader();//获得类加载器
            //getResource()中的的参数路径是相对于classes(src)
            //获得classes(src)下的任何的资源,这个jdbc.properties被放在了和Demo.java在一起
            String path = classLoader.getResource("com/itheima/classloader/jdbc.properties").getPath();
            //classLoader.getResourceAsStream("");
            System.out.println(path);
            
        }
    }
    

    二、反射

    上面的字节码对象(Class对象)怎么获取?3种方式,上面的示例中只是这3种中第一种方式。

    方式一,使用类的class属性:
    Class<java.util.Date> clz1 = java.util.Date.class;
    方式二,通过Class类中的静态方法forName(String className),传入类的全限定名(必须添加完整包名)。
    Class<?> clz2 = Class.forName(“java.util.Date”); //把名字为java.util.Date的类加载进内存
    方式三,通过对象的getClass方法来实现,其中,getClass()是Object类中的方法,所有的对象都可以调用该方法
    java.util.Date str = new java.util.Date();
    Class<?> clz3 = str.getClass();

    反射是在运行期间动态地加载一个类进来,动态地new一个对象出来,动态地去了解这个对象的内部结构,动态地去调用这个对象的某一些方法。在学习框架时,很多的类的名字是写在配置文件中的。这时候你就会想它们是怎么new出来的,就是用反射机制new出来的。  

    【示例】:建立一个properties文件,把需要动态加载的类名写进去

    test.properties

    com.itheima.reflection.T

    接着写一个程序从properties中把这个类名读出来,然后生成它的一个对象。

    TestFlection.java

    package com.itheima.reflection;
    
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    
    public class TestFlection {
    
    	public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
    		String str = "com.itheima.reflection.T"; //必须是类全名,这块从properties文件中读取出来	
    		Class c = Class.forName(str);
    		Object o = c.newInstance();
    		Method[] methods = c.getMethods(); //获取类里面有哪些方法,这些方法有哪些信息,放到method对象里
    		for (Method m : methods) {
    			//System.out.println(m.getName());
    			if(m.getName().equals("mm")) {
    				m.invoke(o); //m.invoke(obj, args) obj:方法属于哪个对象的,args:方法的参数。参数可以有多个,也可以不传,属于可变参数的方法
    			}
    			
    			if(m.getName().equals("m1")) {
    				for (Class paramType : m.getParameterTypes()) {
    					System.out.println(paramType.getName());
    				}
    				m.invoke(o, 1, 2); 
    			}
    			
    			if(m.getName().equals("getS")) {
    				Class returnType = m.getReturnType();
    				System.out.println(returnType);
    				System.out.println(returnType.getName());
    			}
    		}
    	}
    
    }
    
    class T {
    	int i;
    	String s;
    	
    	static { //可以写个static代码块看看是否被加载成功
    		System.out.print("T loaded!");
    	}
    	
    	public T() {
    		System.out.println("T constructed!");
    	}
    	
    	public void m1(int i, int j) {
    		this.i = i + j;
    		System.out.println(this.i);
    	}
    	
    	public void mm() {
    		System.out.println("m invoked");
    	}
    	
    	public String getS() {
    		return s;
    	}
    }

      

    三、注解 @xxx

    1. 什么是注解,注解作用

    注解就是符合一定格式的语法 @xxxx。比如使用Junit是单元测试的工具,在一个类中使用 @Test 对程序中的方法进行测试。

    注解是一种代码级别的说明。它是jdk1.5及以后版本引入的一个特性,与类、接口、枚举是同一个层次。

    注解和注释区别:

    • 注释:在阅读程序时清楚 --给程序员看的
    • 注解:给jvm看的,给机器看的

    注解在目前而言最主流的应用:代替配置文件。

    • 比如我们在用Eclipse创建动态web工程时,把"Dynamic web module version"选择2.5,这样创建完成后会有web.xml。如果选择3.0的版本,创建完成后会少个web.xml。因为注解可以代替webxml 

    在src下新建一个Servlet

    关于配置文件与注解开发的优缺点:

    • 注解优点:开发效率高、成本低
    • 注解缺点:耦合性大、并且不利于后期维护

    企业中一般是注解和xml混用。基本不改的地方用注解,有可能要改的地方用xml配置文件。

    2. jdk5提供的注解

    • @Override:JDK5表示告知编译器此方法是覆盖父类的,JDK6还可以表示实现接口的方法。
    • @Deprecated:表示被修饰的方法已经过时。过时的方法不建议使用,但仍可以使用。一般被标记为过时的方法都存在不同的缺陷:(1)安全问题;(2)新的API取代。
    • @SuppressWarnings:压制警告,被修饰的类或方法如果存在编译警告,将被编译器忽略。
      • deprecation:忽略过时
      • rawtypes:忽略类型安全
      • unused:忽略不使用
      • unchecked:忽略安全检查
      • null:忽略空指针
      • all:忽略所有。如果要忽略多个,可以直接all
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * 测试JDK5提供的注解。
     * @author jkzhao
     *
     */
    public class AnnoDemo {
    
        public static void main(String[] args) {
            //压制警告
            @SuppressWarnings({ "unused", "rawtypes" }) //如果里面要压制的太多,直接"all"
            List list = new ArrayList(); //这句话会报警告,因为没加泛型,集合中什么都可以存。取的时候自己不知道里面存的什么,可能存在转换错误的隐患。rawtypes
                                        //list未使用,也会出警告。unused
    
            show();
    
        }
    
        //定义方法过时
        @Deprecated
        public static void show(){
    
        }
    
        public static void show(String xx){
    
        }
     
        //帮助开发人间检查是否覆盖父类的方法正确。比如你把下面的方法改为public String toStringXX(),加上这个注解,就会报错了。帮你做了检查,你写错了。 
        //注解是给编译器用的,当你写错代码导致错误时,Eclipse会报红线,编译出错了。javac是编译,现在使用IDE工具,不需要自己手动编译,点击菜单栏的"Project",可以看到"Build Automatically"
        @Override
        public String toString() {
            return super.toString();
        }
    
    }
    AnnoDemo.java

    发现的问题:

    不同的注解只能在不同的位置使用(可能在方法上、可能在字段上、可能在类上)

    3. 自定义注解(了解)

    • 怎样去编写一个自定义的注解
    • 怎样去使用注解
    • 怎样去解析注解 --使用反射知识解析注解。注解的功能是靠解析去做的

    (1)编写一个注解

    关键字:@interface

    如何定义注解的属性:
      语法:返回值 属性名称();  
      注意:如果属性的名字是value,并且注解的属性值只有一个,那么在使用注解时可以省略value这个key

    注解属性类型只能是以下几种

    • 基本类型
    • String
    • 枚举类型
    • 注解类型
    • Class类型
    • 以上类型的一维数组类型

    New—>Annotation,输入注解名称,点击完成。

    package com.itheima.annotation;
    
    public @interface MyAnno {
        //定义注解的属性,在这里不叫方法
        String name();
        
        int age() default 28;
        
        //注意:如果属性的名字是value,并且注解的属性值只有一个,那么在使用注解时可以省略value这个key
        //String value();
        //String[] value();
    }
    MyAnno.java

    (2)使用注解

    在类/方法/字段 上面是@XXX

    package com.itheima.annotation;
    
    @MyAnno(name = "zhangsan")
    public class MyAnnoTest {
        
        @MyAnno(name = "zhangsan")
        public void show(String str){
            System.out.println("show running...");
        }
        
    }
    MyAnnoTest.java

    至于注解的功能是解析的时候实现。

    (3)解析使用注解的类

    以后使用框架时,这些解析都是框架的作者帮我们写好了,我们只需要会使用即可。

    介入一个概念:元注解:代表修饰注解的注解,作用:限制定义的注解的特性。

    • @Retention
      • SOURCE: 注解在源码级别可见
      • CLASS:注解在源码级别和字节码文件级别可见
      • RUNTIME:注解在整个运行阶段都可见
    • @Target:代表注解修饰的范围:类上使用,方法上使用,字段上使用
      • FIELD: 字段上可用此注解
      • METHOD: 方法上可以用此注解
      • TYPE: 类/接口上可以使用此注解

    【注意】:要想解析使用了注解的类 , 那么该注解的Retention必须设置成Runtime。因为是通过反射获得的,所以必须把注解带到运行时。

    关于注解解析的实质:从注解中解析出属性值。

    字节码对象存在于获得注解相关的方法:

    isAnnotationPresent(Class<? extends Annotation> annotationClass) : 判断该字节码对象身上是否使用该注解了
    getAnnotation(Class<A> annotationClass) :获得该字节码对象身上的注解对象

    【示例】:解析注解

    package com.itheima.annotation;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    @Target({ElementType.METHOD,ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MyAnno {
        //定义注解的属性,在这里不叫方法
        String name();
        
        int age() default 28;
        
        //注意:如果属性的名字是value,并且注解的属性值只有一个,那么在使用注解时可以省略value这个key
        //String value();
        //String[] value();
    }
    MyAnno.java
    package com.itheima.annotation;
    
    @MyAnno(name = "zhangsan")
    public class MyAnnoTest {
        
        @SuppressWarnings("all")
        @MyAnno(name = "zhangsan")
        //@MyAnno({ "aaa","bbb","ccc"})
        public void show(String str){
            System.out.println("show running...");
        }
        
    }
    MyAnnoTest.java
    package com.itheima.annotation;
    
    import java.lang.reflect.Method;
    
    public class MyAnnoParser {
    
        public static void main(String[] args) throws NoSuchMethodException, SecurityException {
            //解析show方法上面的@MyAnno
            //直接的目的是 获得show方法上的@MyAnno中的参数
            
            //获得show方法的字节码对象
            Class clazz = MyAnnoTest.class;
            Method method = clazz.getMethod("show", String.class);
            //获得show方法上的@MyAnno
            MyAnno annotation = method.getAnnotation(MyAnno.class);
            //获得@MyAnno上的属性值
            System.out.println(annotation.name());//zhangsan
            System.out.println(annotation.age());//28
            
            //根据业务需求写逻辑代码
            
        }
        
    }
    MyAnnoParser.java

    四、动态代理

    1. 什么是代理(中介、微商、经纪人)

    • 目标对象/被代理对象 -- 房主:拥有真正的租房的方法
    • 代理对象 --黑中介:有租房子的方法(其实是调用房主的租房的方法)
    • 执行代理对象方法的对象 --租房的人

    流程:我们要租房(找不到房主,只能找中介租房)-->中介(租房的方法)-->房主(租房的方法)
    抽象:调用对象-->代理对象-->目标对象

    2. 动态代理

    动态代理:不用手动编写一个代理对象,不需要一一编写与目标对象相同的方法,这个过程,在运行时的内存中动态生成代理对象。 --字节码对象级别的代理对象

    动态代理的API:在jdk的API中存在一个Proxy中存在一个生成动态代理的的方法newProxyInstance

    返回值:Object就是代理对象

    参数:

    • loader:代表与目标对象相同的类加载器 --目标对象.getClass().getClassLoader()
    • interfaces:代表与目标对象实现的所有的接口字节码对象数组
    • h:具体的代理的操作,InvocationHandler接口

    【注意】:JDK的Proxy方式实现的动态代理,目标对象必须有接口,没有接口不能实现jdk版动态代理。

    【示例1】:动态代理示例。

    package com.itheima.dynamicProxy;
    
    public interface TargetInterface {
    
        public void method1();
        public String method2();
        public int method3(int x);
    }
    TargetInterface.java
    package com.itheima.dynamicProxy;
    
    public class Target implements TargetInterface{
    
        @Override
        public void method1() {
            System.out.println("method1 running...");
        }
    
        @Override
        public String method2() {
            System.out.println("method2 running...");
            return "method2";
        }
    
        @Override
        public int method3(int x) {
            return x;
        }
    
    }
    Target.java
    package com.itheima.dynamicProxy;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    import org.junit.Test;
    
    public class ProxyTest {
        @Test
        public void test1(){
            //获得动态的代理对象----在运行时 在内存中动态的为Target创建一个虚拟的代理对象
            //objProxy是代理对象 根据参数确定到底是谁的代理对象
            TargetInterface objProxy = (TargetInterface) Proxy.newProxyInstance(
                    Target.class.getClassLoader(), //与目标对象相同的类加载器,因为要用这个类加载器去加载那个虚拟的代理对象。(Target.class)有3种方式
                    new Class[]{TargetInterface.class}, //接口的字节码对象数组
                    new InvocationHandler() { //InvocationHandler是一个接口,里面只有一个方法。这里需要new这个接口或者这个接口的实现类。匿名内部类
                        //invoke 代表的是执行代理对象的方法,通过代理对象去调被代理对象的什么方法,都是执行invoke方法
                        @Override
                        //proxy:是代理对象,就是上面的TargetInterface proxy,不需要管,也不要在这个方法内部用。java中说对象就是维护着这个对象的引用
                        //method:代表目标对象(被代理对象)的方法字节码对象
                        //args:代表目标对象的相应的方法的参数
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            System.out.println("目标方法前的逻辑。。。");
                            //执行目标对象(被代理对象)的方法,通过反射运行该方法
                            Object invoke = method.invoke(new Target(), args);
                            System.out.println("目标方法后的逻辑。。。");
                            return invoke;
                        }
                    }
                );
            
            objProxy.method1(); 
            String method2 = objProxy.method2();
            System.out.println(method2); 
            
        }
    }
    ProxyTest.java
    package com.itheima.dynamicProxy;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    public class ProxyTest2 {
    
        public static void main(String[] args) {
            
            final Target target = new Target();
            
            //动态创建代理对象
            TargetInterface proxy = (TargetInterface) Proxy.newProxyInstance(
                    target.getClass().getClassLoader(), 
                    target.getClass().getInterfaces(), //通过字节码对象获取这个字节码对象实现的所有接口 
                    new InvocationHandler() { //匿名内部类
                        @Override
                        //被执行几次?------- 看代理对象调用方法几次
                        //代理对象调用接口的相应方法 都是调用invoke方法
                        /*
                         * proxy:是代理对象,就是上面的TargetInterface proxy,不需要管,也不要在这个方法内部用。java中说对象就是维护着这个对象的引用
                         * method:代表的是目标方法的字节码对象
                         * args:代表是调用目标方法时参数
                         */
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            //反射知识点
                            Object invoke = method.invoke(target, args);//目标对象的相应方法
                            //return返回的值给代理对象
                            return invoke;
                        }
                    }
                );
            
            proxy.method1();//调用invoke---Method:目标对象的method1方法  args:null  返回值null
            String method2 = proxy.method2();//调用invoke---Method:目标对象的method2方法  args:null  返回值method2
            int method3 = proxy.method3(100);////调用invoke-----Method:目标对象的method3方法 args:Object[]{100}  返回值100
            
            System.out.println(method2);
            System.out.println(method3);
        }
    }
    ProxyTest2.java

    【示例2】:使用动态代理完成全局编码。(与装饰者模式代码放在一起,注意比较)

    public class EncodingFilter implements Filter{
    
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
                throws IOException, ServletException {
            
            //使用动态代理完成全局编码
            final HttpServletRequest req = (HttpServletRequest)request; //目标对象(被代理对象)
            HttpServletRequest enhanceRequest = (HttpServletRequest)Proxy.newProxyInstance(
                    req.getClass().getClassLoader(), 
                    req.getClass().getInterfaces(), 
                    new InvocationHandler() { //匿名内部类
                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            //对getParameter方法进行增强
                            String name = method.getName(); //获得目标对象的方法名称
                            if("getParameter".equals(name)) {
                                String invoke = (String)method.invoke(req, args); //乱码
                                //转码后的字符串
                                invoke = new String(invoke.getBytes("iso8859-1"),"UTF-8");
                                
                                return invoke;
                            }
                            //其它方法原封不动地执行
                            return method.invoke(req, args);
                        }
                    }
                    );
            
            chain.doFilter(enhanceRequest, response);
            
            
            //request.setCharacterEncoding("UTF-8");
            
            //在传递request之前对request的getParameter方法进行增强
            /*
             * 装饰者模式(包装)
             * 
             * 1、增强类与被增强的类要实现统一接口
             * 2、在增强类中传入被增强的类
             * 3、需要增强的方法重写,不需要增强的方法调用被增强对象的
             * 
             */
            //被增强的对象
            //HttpServletRequest req = (HttpServletRequest) request;
            //增强对象
            //EnhanceRequest enhanceRequest = new EnhanceRequest(req);
            
            //chain.doFilter(enhanceRequest, response);
            
        }
    
        @Override
        public void destroy() {
            
        }
        
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
            
        }
    }
    
    class EnhanceRequest extends HttpServletRequestWrapper{ //HttpServletRequest也是实现了HttpServletRequestWrapper
        
        private HttpServletRequest request;
    
        public EnhanceRequest(HttpServletRequest request) {
            super(request);
            this.request = request;
        }
        
        //对getParameter增强,注意并没有对getParameterMap做增强
        @Override
        public String getParameter(String name) {
            String parameter = request.getParameter(name);//乱码
            try {
                parameter = new String(parameter.getBytes("iso8859-1"),"UTF-8");
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
            return parameter;
        }
        
    }
    EncodingFilter.java
    <!-- 编码统一处理的filter -->
      <filter>
          <filter-name>EncodingFilter</filter-name>
          <filter-class>com.ithiema.web.filter.EncodingFilter</filter-class>
      </filter>                
      <filter-mapping>
          <filter-name>EncodingFilter</filter-name>
          <url-pattern>/*</url-pattern>
      </filter-mapping>
    web.xml部分配置

    实际开发中,装饰者模式一般用于某个对象的方法增强的。动态代理虽然能实现方法增强的效果,但一般不会用在方法增强上,用来拦截,然后做权限。比如判断当前用户是否有权限调用某个方法,这种权限粒度就比较细了。

  • 相关阅读:
    从错误状态恢复虚拟机
    OpenStack手动从数据库中删除实例
    对于flat_interface与public_interface的理解
    2. 拓扑图
    Cinder相关命令收集
    Nova相关命令收集
    14. Launch an instance
    9. Add the Block Storage service
    8. Add the dashboard
    7. Add a networking service
  • 原文地址:https://www.cnblogs.com/zhaojiankai/p/7905846.html
Copyright © 2011-2022 走看看