第三章 Java基础 (二)

(八)Java的反射机制

1、什么是反射?——框架设计的灵魂

  • 框架:半成品软件。可以在框架的基础上进行软件开发,简化代码。
  • java代码在计算机中经历的阶段(三阶段)
    在这里插入图片描述
  • 反射:就是把Java类中的各种成分映射成相应的Java类,然后对这些类进行操作。(如:类的成员属性->Field、类的成员方法->Method、类的构造方法->Constructor等等)
    反射的好处包括:
    (1)可以在程序运行过程中,操作这些对象。
    (2)可以解耦,提高程序可扩展性。

Java反射说的是在运行状态中,对于任何一个类,我们都能够知道这个类有哪些方法和属性。对于任何一个对象,我们都能够对它的方法和属性进行调用。
我们把这种动态获取对象信息和调用对象方法的功能称之为反射机制。

2、字节码Class对象

(1)Class类对象——描述.class字节码文件
将(.java类文件)经过编译后的.class字节码文件(位于硬盘上)通过类加载器(ClassLoader)加载进内存,通过java.lang.Class类对象对字节码文件进行描述。每一个类都是一个Class类的实例对象。
Class类对象是用来对.class文件进行描述。主要包括三个成员变量:

  • 类成员变量 Field[] fields
  • 类构造方法 Constructor[] constructors
  • 类成员方法 Method[] methods

(2)获取Class对象的方式
1. Class.forName(“全类名”):将字节码文件加载进内存,返回class对象
在Source源代码阶段,此时java类仍位于硬盘上。多用于配置文件,将类名定义在配置文件中。读取文件,并触发类构造器加载类。
Class.forName() 方法如果写错类的路径会报 ClassNotFoundException 的异常。
2. 类名.class:通过类名的属性class获取
在Class类对象阶段,此时java类位于内存中,但没有实际对象。多用于参数的传递。
通过这种方式时,只会加载Dog类,并不会触发其类构造器的初始化。
3. 对象.getClass():getClass()方法在Object类中定义
在运行阶段,此时已经获取类的实例对象,多用于对象的获取字节码的方式。

		// 方法1:Class.forName("全类名")
        try {
            Class cls1 = Class.forName("com.test.demo.Dog");
        } catch (ClassNotFoundException e) {}
        // 方法2:类名.class
        Class cls2 = Dog.class;
        // 方法3:对象.getClass()
        Dog dog = new Dog();
    	Class cls3 = dog.getClass();
		// 用 == 比较3个对象是否为同一个对象(指向同一物理地址)
		System.out.print(cls1 == cls2);	//	true
		System.out.print(cls1 == cls3);	//	true

结论:同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,不论通过哪一种方式获取的class对象都是同一个。

3、反射机制

反射机制reflect可以在运行期间获取类的字段、方法、父类和接口等信息。

(3.1)类成员变量的反射

  1. 获取类成员变量:
  • Field[] getFields():获取所有public 修饰的成员变量
  • Field getField(String name):获取指定名称的public修饰的成员变量
  • Field[] getDeclaredFields():获取所有的成员变量,不考虑修饰符
  • Filed getDeclaredField(String name):获取指定名称的成员变量,不考虑修饰符
  1. Filed:成员变量
  • get(Object object) :获取值
  • void set(Object obj, Object value):设置值
  • setAccessible(true) :忽略访问权限修饰符的安全检查,用于暴力反射,修改私有成员变量的值
 /**
     * 获取成员变量的信息
     * java.lang.reflect.Field
     * Field类封装了关于成员变量的操作
     * getFields()方法获取的是所有的public的成员变量的信息
     * getDeclaredFields获取的是该类自己声明的成员变量的信息
     * @param obj
     */
	public static void printFieldMessage(Object obj) {
		Class c = obj.getClass();	// 获取obj的class对象
		//Field[] fs = c.getFields();
		Field[] fs = c.getDeclaredFields();
		for (Field field : fs) {
			// 获取成员变量的类型的类类型
			Class fieldType = field.getType();
			String typeName = fieldType.getName();
			// 获取成员变量的名称
			String fieldName = field.getName();
			// 获取成员变量的值(默认初始化 = null)
			Object fieldValue = field.get(obj);
			System.out.println(typeName+" "+fieldName+"="+fieldValue);
			// 设置成员变量的值
			// 注意这里的应用:暴力反射——可以通过反射对私有变量修改值
			// 前提:忽略访问权限修饰符的安全检查
			// field.setAccessible(true);
			// field.set(obj,"value");
		}
	}

(3.2)类成员方法的反射

  1. 获取类成员方法:
  • Method[] getMethods():获取所有public修饰的成员方法
  • Method getMethod(String name,类<?>… parameterTypes):获取指定的public修饰的成员方法,name 为方法名,parameterTypes为参数列表(重载)
  • Method[] getDeclaredMethods():获取所有成员方法
  • Method getDeclaredMethod(String name,类<?>… parameterTypes):获取指定的成员方法,name 为方法名,parameterTypes为参数列表(重载)
  1. Method
  • invoke(obj … args):执行方法
  • setAccessible(true) :忽略访问权限修饰符的安全检查,用于暴力反射,修改私有成员方法的值

(2.1)获取方法

	/**
	 * 打印类的信息,包括类的成员函数、成员变量(只获取成员函数)
	 * Method类,方法对象
	 * 一个成员方法就是一个Method对象
	 * getMethods()方法获取的是所有的public的函数,包括父类继承而来的
	 * getDeclaredMethods()获取的是所有该类自己声明的方法,不问访问权限
	 * @param obj 该对象所属类的信息
	 */
	public static void printClassMethodMessage(Object obj){
		//要获取类的信息  首先要获取类的类类型
		Class c = obj.getClass();
		//获取类的名称
		System.out.println("类的名称是:"+c.getName());

		Method[] ms = c.getMethods();//c.getDeclaredMethods()
		for(int i = 0; i < ms.length;i++){
			Class returnType = ms[i].getReturnType();//得到方法的返回值类型的类类型
			String methodName = ms[i].getName();//得到方法的名称
			Class[] paramTypes = ms[i].getParameterTypes();	//获取参数类型--->得到的是参数列表的类型的类类型
			System.out.print(returnType.getName()+" ");
			System.out.print(ms[i].getName()+"(");
			for (Class class1 : paramTypes) {
				System.out.print(class1.getName()+",");
			}
			System.out.println(")");
		}
	}

(2.2)方法反射的操作
通过method.invoke(obj, …args)可以调用obj实例的method方法。

class A{
	public void print(){
		System.out.println("helloworld");
	}
	public void print(int a,int b){
		System.out.println(a+b);
	}
	public void print(String a,String b){
		System.out.println(a.toUpperCase()+","+b.toLowerCase());
	}
}

public class MethodDemo1 {
	public static void main(String[] args) {
  	   //1.首先获取类
	   //2.获取方法,方法由名称和参数列表决定
	   //3.方法的反射操作,用m对象来进行方法调用 和直接用对象a1.print调用的效果完全相同.方法如果没有返回值返回null,有返回值返回具体的返回值
		A a1 = new A();
		Class c = a1.getClass();

	    try {
	    	Method m = c.getMethod("print", int.class,int.class);//获取方法print(int,int)
	    	Object o = m.invoke(a1, 10,20);//进行反射操作
             Method m1 = c.getMethod("print",String.class,String.class);//获取方法print(String,String)
             //a1.print("hello", "WORLD");
             o = m1.invoke(a1, "hello","WORLD");
             Method m2 = c.getMethod("print");//获取方法print()
             m2.invoke(a1);//进行反射操作
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

(3.3)类构造方法的反射

  1. 获取类构造方法:
  • Constructor[] getConstructors():获取public修饰的构造方法
  • Constructor getConstructor(类<?>… parameterTypes):获取指定的public修饰的构造方法(构造方法的方法名 = 类名),parameterTypes为参数列表
  • Constructor[] getDeclaredConstructors():获取所有构造方法
  • Constructor getDeclaredConstructor(类<?>… parameterTypes):获取指定的构造方法,name为方法名(构造方法的方法名 = 类名),parameterTypes为参数列表
  1. Constructor:构造方法
  • T.newInstance(Object… init args):创建对象
  • Class.newInstance():如果使用空参数构造方法创建对象,操作可以简化为:Class对象的newInstance方法
  • setAccessible(true) :忽略访问权限修饰符的安全检查,用于暴力反射,修改私有构造方法的值

(3.1)获取构造函数

	/**
	 * 打印对象的构造函数的信息
	 * 构造函数也是对象
	 * java.lang. Constructor中封装了构造函数的信息
	 * getConstructors获取所有的public的构造函数
	 * getDeclaredConstructors得到所有的构造函数
	 * @param obj
	 */
	public static void printConMessage(Object obj){
		Class c = obj.getClass();

		//Constructor[] cs = c.getConstructors();
		Constructor[] cs = c.getDeclaredConstructors();
		for (Constructor constructor : cs) {
			System.out.print(constructor.getName()+"(");
			//获取构造函数的参数列表--->得到的是参数列表的类类型
			Class[] paramTypes = constructor.getParameterTypes();
			for (Class class1 : paramTypes) {
				System.out.print(class1.getName()+",");
			}
			System.out.println(")");
		}
	}

(3.2)通过构造函数生成类实例

public class ClassTest {
    public static void main(String[] args) throws NoSuchMethodException {
        Class class_dog = Dog.class;
		// 1. 获取实例构造器(含参)
        Constructor constructor = class_dog.getConstructor(String.class, int.class);
		Object dog1 = constructor.newInstance("Tom", 10); // 通过newInstance生成类实例,如果没有显示的声明默认构造器,class_dog.getConstructor()会抛出NoSuchMethodException异常。
		// 2. 获取构造方法(不含参)
		Constructor constructor1 = class_dog.getConstructor();
		Object dog2 = class_dog.newInstance(); // 不含参的构造方法可以通过Class.newInstance简化生成对象
    }
}
class Dog {
    private String name;
    private int age;
    
    public Dog(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

(3.4)类名的获取

String getName()

4、反射案例

(4.1)Class类的动态加载

编译时刻加载的类是静态加载类,运行时刻加载的类是动态加载类。

  • 需求
    写一个"框架",在不能改变该类任何代码的前提下,可以帮我们创建任意类的对象,并且执行其中任意方法。
  • 实现
  1. 配置文件
  2. 反射
  • 步骤
  1. 将需要创建的对象的全类名和需要执行的方法定义在配置文件中
  2. 在程序中加载读取配置文件
  3. 使用反射奇数来加载类文件进内存
  4. 创建对象,执行方法

Person.java

public class Person{
	String name;
	int age;
	public void sleep(){System.out.println("sleep...");}
}

Student.java

public class Student{
	String stuName;
	String stuNum;
	public void study(){System.out.println("study...");}
}

pro.properties文件

// 动态加载Person类
// 输出 sleep...
className = cn.itcast.domain.Person
methodName = sleep
// 动态加载Student类
// 输出study...
className = cn.itcast.domain.Student
methodName = study

ReflectTest.java

public class ReflectTest{
	public static void main(String[] args){
		// 可以创建任意类对象,可以执行任意方法,且不用改变类的任何代码
		// 1. 加载配置文件
		// 1.1 创建Properties对象
		Properties pro = new Properties();
		// 1.2 加载配置文件,转换为一个集合
		// 1.2.1 获取class目录下配置文件
		ClassLoader classLoader = ReflectTest.class.getClassLoader();
		InputStream is = classLoader.getResourceAsStream("pro.properties");
		pro.load(is)l
		// 2. 获取配置文件中定义的数据
		String className = pro.getProperty("className");
		String methodName = pro.getProperty("methodName");
		// 3. 加载该类进内存
		Class cls = Class.forName(className);
		// 4. 创建对象
		Object obj = cls.newInstance();
		// 5. 获取方法对象
		Method method = cls.getMethod(methodName);
		// 6. 执行方法
		method.invoke(obj);
	}
}

(4.2)JDBC数据库连接

在JDBC 的操作中,如果要想进行数据库的连接,则必须按照以上的几步完成

  1. 通过Class.forName()加载数据库的驱动程序 (通过反射加载,前提是引入相关了Jar包)
  2. 通过 DriverManager 类进行数据库的连接,连接的时候要输入数据库的连接地址、用户名、密码
  3. 通过Connection 接口接收连接
public class ConnectionJDBC {  
  
    /** 
     * @param args 
     */  
    //驱动程序就是之前在classpath中配置的JDBC的驱动程序的JAR 包中  
    public static final String DBDRIVER = "com.mysql.jdbc.Driver";  
    //连接地址是由各个数据库生产商单独提供的,所以需要单独记住  
    public static final String DBURL = "jdbc:mysql://localhost:3306/test";  
    //连接数据库的用户名  
    public static final String DBUSER = "root";  
    //连接数据库的密码  
    public static final String DBPASS = "";  
      
      
    public static void main(String[] args) throws Exception {  
        Connection con = null; //表示数据库的连接对象  
        Class.forName(DBDRIVER); //1、使用CLASS 类加载驱动程序 ,反射机制的体现 
        con = DriverManager.getConnection(DBURL,DBUSER,DBPASS); //2、连接数据库  
        System.out.println(con);  
        con.close(); // 3、关闭数据库  
    }  

(4.3)Spring框架使用——反射

在 Java的反射机制在做基础框架的时候非常有用,行内有一句这样的老话:反射机制是Java框架的基石。一般应用层面很少用,不过这种东西,现在很多开源框架基本都已经封装好了,自己基本用不着写。典型的除了hibernate之外,还有spring也用到很多反射机制。最经典的就是xml的配置模式。
Spring通过XML配置模式装载Bean的过程:

  1. 将程序内所有XML或Properties配置文件加载入内存中
  2. Java类里面解析xml或properties里面的内容,得到对应实体类的字节码字符串以及相关属性信息
  3. 使用反射机制,根据这个字符串获得某个类的Class实例
  4. 动态配置实例的属性

通过反射,Spring框架可以在不改变代码的前提下,直接修改配置文件。模拟Spring加载XML配置文件:

public class BeanFactory {
       private Map<String, Object> beanMap = new HashMap<String, Object>();
       /**
       * bean工厂的初始化.
       * @param xml xml配置文件
       */
       public void init(String xml) {
              try {
                     //读取指定的配置文件
                     SAXReader reader = new SAXReader();
                     ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
                     //从class目录下获取指定的xml文件
                     InputStream ins = classLoader.getResourceAsStream(xml);
                     Document doc = reader.read(ins);
                     Element root = doc.getRootElement();  
                     Element foo;
                    
                     //遍历bean
                     for (Iterator i = root.elementIterator("bean"); i.hasNext();) {  
                            foo = (Element) i.next();
                            //获取bean的属性id和class
                            Attribute id = foo.attribute("id");  
                            Attribute cls = foo.attribute("class");
                           
                            //利用Java反射机制,通过class的名称获取Class对象
                            Class bean = Class.forName(cls.getText());
                           
                            //获取对应class的信息
                            java.beans.BeanInfo info = java.beans.Introspector.getBeanInfo(bean);
                            //获取其属性描述
                            java.beans.PropertyDescriptor pd[] = info.getPropertyDescriptors();
                            //设置值的方法
                            Method mSet = null;
                            //创建一个对象
                            Object obj = bean.newInstance();
                           
                            //遍历该bean的property属性
                            for (Iterator ite = foo.elementIterator("property"); ite.hasNext();) {  
                                   Element foo2 = (Element) ite.next();
                                   //获取该property的name属性
                                   Attribute name = foo2.attribute("name");
                                   String value = null;
                                  
                                   //获取该property的子元素value的值
                                   for(Iterator ite1 = foo2.elementIterator("value"); ite1.hasNext();) {
                                          Element node = (Element) ite1.next();
                                          value = node.getText();
                                          break;
                                   }
                                  
                                   for (int k = 0; k < pd.length; k++) {
                                          if (pd[k].getName().equalsIgnoreCase(name.getText())) {
                                                 mSet = pd[k].getWriteMethod();
                                                 //利用Java的反射极致调用对象的某个set方法,并将值设置进去
                                                 mSet.invoke(obj, value);
                                          }
                                   }
                            }
                           
                            //将对象放入beanMap中,其中key为id值,value为对象
                            beanMap.put(id.getText(), obj);
                     }
              } catch (Exception e) {
                     System.out.println(e.toString());
              }
       }
      
       //other codes
}

(九)Java注解

1、什么是Java注解

注解与注释:
注解:说明程序的。给计算机看得。
注释:用文字描述程序,给程序员看的。方便程序员理解。
注解(Annotation)是插入代码中的元数据,一种代码级别的说明。它是在JDK5.0及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。
(1)JDK 1.5 之后的新特性
(2)用来说明程序
(3)使用注解:@注解名称
Annotation的作用大致可分为三类:

  • 编写文档:通过代码里标识的注解生成文档(生成java doc文档(api));
/**
* 注解javadoc演示
* @author itcast
* @version 1.0
* @since 1.5
* /
public class AnnoDemo1 {
	/**
	* 计算两数的和
	* @param a 整数
	* @param b 整数
	* @return 两数的和
	* /
	public int add(int a,int b){
		return a+b;
	}
}

生成命令

javadoc AnnoDemo1.java
  • 代码分析:通过代码里标识的注解对代码进行分析(使用反射);
  • 编译检查:通过代码里标识的注解让编译器能实现基本的编译检查;

Java提供了一种源程序中的元素关联任何信息和任何元数据的途径和方法。
它可以在编译期使用预编译工具进行处理, 也可以在运行期使用 Java 反射机制进行处理,用于创建文档,跟踪代码中的依赖性,甚至执行基本编译时检查。
本质上,Annotion是一种特殊的接口,程序可以通过反射来获取指定程序元素的Annotion对象,然后通过Annotion对象来获取注解里面的元数据。(元数据metadata:关于数据的数据)

2、Java中的常见注解

(2.1)JDK中内置注解

  1. @Override
    用于检测被该注解标注的方法是否时继承自父类(接口)的。
    @Override 是一个标记注解类型,它被用作标注方法。它说明了被标注的方法重载了父类的方法,起到了断言的作用。如果我们使用了这种Annotation在一个没有覆盖父类方法的方法时,java编译器将以一个编译错误来警示:Method does not override method from its superclass
public class Fruit{
    public void displayName(){
        System.out.println("水果的名字是:*****");
    }
}

class Orange extends Fruit{
    @Override
    public void displayName(){
        System.out.println("水果的名字是:桔子");
    }
}
  1. @Deprecated
    用于标注已经过时的方法;
    Deprecated也是一个标记注解。当一个类型或者类型成员使用@Deprecated修饰的话,编译器将不鼓励使用这个被标注的程序元素。而且这种修饰具有一定的 “延续性”:如果我们在代码中通过继承或者覆盖的方式使用了这个过时的类型或者成员,虽然继承或者覆盖后的类型或者成员并不是被声明为 @Deprecated,但编译器仍然要报警。
  2. @SuppressWarnnings
    用于通知java编译器禁止特定的编译警告;
    @SuppressWarnings 其注解目标为类、字段、函数、函数入参、构造函数和函数的局部变量。在java5.0,sun提供的javac编译器为我们提供了-Xlint选项来使编译器对合法的程序代码提出警告,此种警告从某种程度上代表了程序错误。有时我们无法避免这种警告,在调用的方法前增加@SuppressWarnings修饰,告诉编译器停止对此方法的警告。
    一般传递参数all @SuppressWarnnings(“all”)
public class AppleService {
    public void displayName(){
        System.out.println("水果的名字是:苹果");
    }

    /**
     * @deprecated 该方法已经过期,不推荐使用
     */
    @Deprecated
    public void showTaste(){
        System.out.println("水果的苹果的口感是:脆甜");
    }

    public void showTaste(int typeId){
        if(typeId==1){
            System.out.println("水果的苹果的口感是:酸涩");
        }
        else if(typeId==2){
            System.out.println("水果的苹果的口感是:绵甜");
        }
        else{
            System.out.println("水果的苹果的口感是:脆甜");
        }
    }
}

public class AppleConsumer {
    //@SuppressWarnings({"deprecation"})	压制过时警告(对于过时的方法不显示警告)
    public static void main(String[] args) {
        AppleService appleService=new AppleService();
        appleService.showTaste();
        appleService.showTaste(2);
    }
}

ppleService类的showTaste() 方法被@Deprecated标注为过时方法,在AppleConsumer类中使用的时候,编译器会给出该方法已过期,不推荐使用的提示。
但如果标注@SuppressWarnings({“deprecation”}),则抑制了过时警告,编译器不会报警告。
(2.2)Java第三方注解
Spring:@Autowired、@Service、@Repository
Mybatis:@InsertProvider、@UpdateProvider、@Options

3、注解的分类

(3.1)按照运行机制分类

  • 源码注解:注解只在源码中存在,编译成.class文件就不存在了
  • 编译时注解:注解只在源码中与.class文件都存在了,包括@Override、@Deprecated、@SuppressWarnnings
  • 运行时注解:在运行阶段仍起作用,甚至影响运行逻辑的注解,如@Autowired

(3.2)根据注解功能与用途

  • 系统内置注解:系统自带的注解类型,如@Override
  • 元注解:注解的注解,负责注解其他注解,如@Target
  • 自定义注解:用户根据自己的需求自定义的注解类型

(3.3)根据成员个数分类

  • 标记注解:没有定义成员的Annotation类型,自身代表某类信息,如:@Override
  • 单成员注解:只定义了一个成员,比如@SuppressWarnings 定义了一个成员String[] value,使用value={…}大括号来声明数组值,一般也可以省略“value=”
  • 多成员注解:定义了多个成员,使用时以name=value对分别提供数据

4、自定义注解

(4.1)格式

元注解
public @interface 注解名称{
	... 属性列表
}

(4.2)本质
注解本质上就是一个接口,该接口默认继承java.lang.annotation.Annotation接口。

public interface MyAnno extends java.lang.annotation.Annotation{}

(4.3)属性
接口中的抽象方法被称为注解的属性。每一个抽象方法实际上是声明了一个配置参数。方法的名称就是参数的名称,返回值类型就是参数类型。

  1. 属性的返回值类型有下列取值:byte,short,char,int,long,float,double,boolean八种基本数据类型、String、enum枚举类型、Annotation注解类型及以上类型的数组
  2. 定义了属性,在使用时需要给属性赋值
  • 如果定义属性时,使用default关键字给属性默认初始化值,则使用注解时,可以不进行属性的赋值
  • 如果只有一个属性需要赋值,且属性的名称是value,则value可以省略,直接定义值即可(例如@SuppressWarnings(“all”))
  • 数组赋值时,值使用{}包裹。如果数组中只有一个值,则{}省略
public @interface MyAnnotation{
	int age();									
	String name() default "张三";	
	String[] strs();
}

public @interface SuppressWarnings{
	String[] value();
}

@MyAnno(age = 10,name = "李四",strs={"aaa","bbb","ccc"} )
public class Worker{}

(4.4)元注解
元注解的作用就是负责注解其他注解。Java5.0定义了4个标准的meta-annotation类型,它们被用来提供对其它 annotation类型作说明。

  • @Target
    作用:
    描述注解能够作用的位置
    取值(ElementType):
    1.CONSTRUCTOR:用于描述构造器
    2.FIELD:用于描述域
    3.LOCAL_VARIABLE:用于描述局部变量
    4.METHOD:用于描述方法
    5.PACKAGE:用于描述包
    6.PARAMETER:用于描述参数
    7.TYPE:用于描述类、接口(包括注解类型) 或enum声明
  • @Retention
    作用:
    描述该注解的生命周期,表示在什么编译级别上保存该注解的信息。Annotation被保留的时间有长短:某些Annotation仅出现在源代码中,而被编译器丢弃;而另一些却被编译在class文件中;编译在class文件中的Annotation可能会被虚拟机忽略,而另一些在class被装载时将被读取(请注意并不影响class的执行,因为Annotation与class在使用上是被分离的)。
    取值(RetentionPoicy):
    1.SOURCE:当前被描述的注解只在源文件中有效,不会保留到class字节码文件中,也不会被JVM读取到。
    2.CLASS:当前被描述的注解,会保留到class字节码文件中,不会被JVM读取到。
    3.RUNTIME:当前被描述的注解,会保留到class字节码文件中,并被JVM读取到。
  • @Documented
    @Documented 用来描述注解是否被抽取到api文档中。在生成javadoc文档的时候将该Annotation也写入到文档中。
  • @Inherited
    @Inherited 元注解是一个标记注解,@Inherited用来描述注解是否被子类继承。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。
@Target({ElementType.TYPE,ElementType.METHOD,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface MyAnno{}

(4.5)在程序使用(解析)注解:获取注解中定义的属性值

  1. 解析注解信息的方法
    Java使用Annotation接口来代表程序元素前面的注解,该接口是所有Annotation类型的父接口。相应地,Java在java.lang.reflect 包下新增了AnnotatedElement接口,该接口代表程序中可以接受注解的程序元素。
    实际上,java.lang.reflect 包所有提供的反射API扩充了读取运行时Annotation信息的能力。当一个Annotation类型被定义为运行时的Annotation后,该注解才能是运行时可见,当class文件被装载时被保存在class文件中的Annotation才会被虚拟机读取。
    AnnotatedElement接口是所有程序元素(Field、Method、Package、Class和Constructor)的父接口,所以程序通过反射获取了某个类的AnnotatedElement对象之后,程序就可以调用该对象的如下七个方法来访问Annotation信息:
  • T getAnnotation(Class annotationClass) :返回该程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null;
  • Annotation[] getDeclaredAnnotation(Class):返回该程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null;与此接口中的其他方法不同,该方法将忽略继承的注解;
  • Annotation[] getAnnotations():返回该程序元素上存在的所有注解;
  • Annotation[] getDeclaredAnnotations():返回直接存在于此元素上的所有注释。与此接口中的其他方法不同,该方法将忽略继承的注解;
  • Annotation[] getAnnotationsByType(Class):返回直接存在于此元素上指定注解类型的所有注解;
  • Annotation[] getDeclaredAnnotationsByType(Class):返回直接存在于此元素上指定注解类型的所有注解。与此接口中的其他方法不同,该方法将忽略继承的注解;
  • boolean isAnnotationPresent(Class<?extends Annotation> annotationClass):判断该程序元素上是否包含指定类型的注解,存在则返回true,否则返回false;
  1. 解析注解信息实例
/***********注解声明***************/
/**
 * 水果名称注解
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitName {
    String value() default " ";
}


/**
 * 水果颜色注解
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitColor {
    /**
     * 颜色枚举
     */
    public enum Color{BLUE, RED, GREEN};

    /**
     * 颜色属性
     * @return
     */
    Color fruitColor() default Color.GREEN;
}


/**
 * 水果供应商注解
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitProvider {
    /**
     * 供应商编号
     * @return
     */
    public int id() default -1;

    /**
     * 供应商名称
     * @return
     */
    public String name() default " ";

    /**
     * 供应商地址
     * @return
     */
    public String address() default " ";
}
/***********注解使用***************/
public class Apple {
    @FruitName("Apple")
    private String appleName;
    @FruitColor(fruitColor = FruitColor.Color.RED)
    private String appleColor;
    @FruitProvider(id = 1, name = "陕西红富士集团", address = "陕西红富士大厦")
    private String appleProvider;

    public String getAppleProvider() {
        return appleProvider;
    }

    public void setAppleProvider(String appleProvider) {
        this.appleProvider = appleProvider;
    }

    public String getAppleName() {
        return appleName;
    }

    public void setAppleName(String appleName) {
        this.appleName = appleName;
    }

    public String getAppleColor() {
        return appleColor;
    }

    public void setAppleColor(String appleColor) {
        this.appleColor = appleColor;
    }

    public void displayName(){
        System.out.println(getAppleName());
    }
}
/***********注解信息获取***************/
public class AnnotationParser {
    public static void main(String[] args) {
        Field[] fields = Apple.class.getDeclaredFields();
        for (Field field : fields) {
            //System.out.println(field.getName().toString());
            if (field.isAnnotationPresent(FruitName.class)){
                FruitName fruitName = field.getAnnotation(FruitName.class);
                System.out.println("水果的名称:" + fruitName.value());
            }else if (field.isAnnotationPresent(FruitColor.class)){
                FruitColor fruitColor = field.getAnnotation(FruitColor.class);
                System.out.println("水果的颜色:"+fruitColor.fruitColor());
            }else if (field.isAnnotationPresent(FruitProvider.class)){
                FruitProvider fruitProvider = field.getAnnotation(FruitProvider.class);
                System.out.println("水果供应商编号:" + fruitProvider.id() + " 名称:" + fruitProvider.name() + " 地址:" + fruitProvider.address());
            }
        }
    }
}
/***********输出结果***************/
水果的名称:Apple
水果的颜色:RED
水果供应商编号:1 名称:陕西红富士集团 地址:陕西红富士大厦

(4.6)小结

  1. 注解很常用
  2. 注解一般给编译器、解析程序使用
  3. 注解不是程序的一部分,可理解为一个标签

5、项目实战1——框架

(5.1)反射框架

  1. 利用注解代替配置文件
/**
* 描述需要执行的类名和方法名
* /
@Target ({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Pro{
	String className();
	String methodName();
}
}
  1. 注解解析案例:实现反射动态加载类
    (1)通过反射获取注解定义位置的对象(Class,Method,Field)
    (2)获取指定的注解:getAnnotation(Class)其实就是在内存中生成了一个该注解接口的子类实现对象
    (3)调用注解中的抽象方法获取配置的属性值
public class ReflectTest{
		public static void main(String[] args) throws Exception{
		// 前提:不能改变该类的任何代码,可以创建任意类的对象,可以执行任意方法
		// 1. 解析注解
		// 1.1 获取该类的字节码文件对象
		Class<ReflectTest> reflectTestClass = ReflectTest.class;
		// 2.获取上边的注解对象,起始就是在内存中生成了一个注解接口的子类实现对象
		// public class ProImpl implements Pro{
		//	public String className(){
		//		return "cn.itcast.annotation.Demo1";
		// 	}
		// 	public String methodName(){
		//		return "show";
		//	}
		// }
		Pro an = reflectTestClass.getAnnotation(Pro.class);
		// 3.调用注解对象中定义的抽象方法,获取返回值
		String className = an.className();
		String methodName = an.methodName();
		// 4. 加载该类进内存
		Class cls = Class.forName(className);
		// 5. 创建对象
		Object obj = cls.newInstance();
		// 6. 获取方法对象
		Method method = cls.getMethod(methodName);
		// 7. 执行方法
		method.invoke(obj);
}

(5.2) 测试框架

  1. 简单的测试框架
// 简单的测试框架
// 当主方法柱形后,会自动执行被检测的所有方法(加Check注解的方法),判断方法是否有异常,记录到文件中
public class TestCheck{
	public static void main(String[] args){
		// 1.创建计算器对象
		Calculator c = new Calculator();
		// 2. 获取字节码文件对象
		Class cls = c.getClass();
		// 3. 获取所有方法
		Method[] methods = cls.getMethods();
		int number = 0;//出现异常的次数
		BufferedWriter bw = new BufferedWriter(new FileWriter("bug.txt"));

		for(Method method: methods){
		// 4. 判断方法上是否有Check注释
		if(method.isAnnotationPresent(Check.class)){
		// 5.如果有Check注释,则执行
		try{
			method.invoke(c);
		}catch(Exception e){
		// 6. 捕获异常,并记录到文件中
			number ++;
			bw.write(method.getName() + "方法出现异常");
			bw.write("异常的名称:"+e.getCause().getClass().getSimpleName());
			bw.write("异常的原因:"+e.getCause().getMessage());
		}
	}
}
bw.write("本次测试一共出现"+number+"次异常");
bw.flush();
bw.close();
}
}
  1. 定义的计算器类
public class Calculator{
	// 加法
	@Check
	public void add(){System.out.println("1+0="+(1+0));}
	// 减法
	@Check
	public void sub(){System.out.println("1-0="+(1-0));}
	// 乘法
	@Check
	public void mul(){System.out.println("1*0="+(1*0));}
	// 除法
	@Check
	public void div(){System.out.println("1/0="+(1/0));}
}
  1. Check注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Check{}

6、项目实战2——Hibernate

(6.1)项目需求
项目取自一个公司持久层架构,用来代替Hibernate解决方案,核心代码通过注解实现。
1、有一张用户表,字段包括用户ID,用户名、手机号;
2、有一个用户类,方便的对每个字段或字段组合条件进行检索,并打印出SQL。
(6.2)注解实战
注解声明
Table注解

@Target({ElementType.TYPE})//作用域为类或接口
@Retention(RetentionPolicy.RUNTIME)//生命周期为运行时
public @interface Table{
	String value();//一个值,表名
}

Column注解

@Target({ElementType.Field})//作用域为字段
@Retention(RetentionPolicy.RUNTIME)//生命周期为运行时
public @interface Column{
	String value();//一个值,字段名
}

注解使用

@Table("db_user")
public class User{
	@Column("_id")
	private int id;
	@Column("user_name");
	private String userName;
	@Column("mobile_phone");
	private String mobilePhone;
}

注解信息获取
通过注解进行对数据库信息的查找

    //根据userName在数据库中进行查询
    public static String FindUserByUserName(String userName) {
    		StringBuilder sb = new StringBuilder();
    		//1.获取class
    		Class clazz = User.class;
    		//2.获取table的名字
    		boolean exists = clazz.isAnnotationPresent(Table.class);
    		if(!exists)return null;
    		Table t = (Table)clazz.getAnnotation(Table.class);
    		Stirng tableName = t.value;
    		sb.append("select * from ").append(tableName).append("where 1=1");
    		//3.获取userName字段
    		Field field = clazz.getDeclaredField("userName");
    		//4.处理每个字段对应的sql
			//4.1 拿到数据库字段名
			boolean fExists = field.isAnnotationPresent(Column.class);
			String columnName = Column.value();
			//4.2 拿到字段的值
			String fieldValue = userName;
			//4.3 拼装sql
			sb.append(" and "+columnName+" = '"+fieldValue + "'");
			
			return sb.toString();
    }

输出结果

select * from db_user where user_name = 'chy'

(十)依赖倒置(DIP)、依赖注入(DI)和控制反转(IOC)

1、依赖(Dependency)

定义
依赖是类与类之间的连接,依赖关系表示一个类依赖于另一个类的定义,通俗来讲就是需要
实例
一个人(Person)可以买车(Car)和房子(House),Person类依赖于Car类和House类

public static void main(String ... args){
        //TODO:

    Person person = new Person();
    person.buy(new House());
    person.buy(new Car());

}

static class Person{

    //表示依赖House
    public void buy(House house){}
    //表示依赖Car
    public void buy(Car car){}
}

static class House{
}

static class Car{
}

2、依赖倒置(Dependence Inversion Principle)

定义
高层模块不应该依赖低层模块,二者都该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象;通俗来讲,依赖倒置原则的本质就是通过抽象(接口或抽象类)使个各类或模块的实现彼此独立,互不影响,实现模块间的松耦合。
类A直接依赖类B,假如要将类A改为依赖类C,则必须通过修改类A的代码来达成。这种场景下,类A一般是高层模块,负责复杂的业务逻辑;类B和类C是低层模块,负责基本的原子操作;假如修改类A,会给程序带来不必要的风险。此时将类A修改为依赖接口interface,类B和类C各自实现接口interface,类A通过接口interface间接与类B或者类C发生联系,则会大大降低修改类A的几率。
实例
(1)不使用依赖倒置,每次出行都需要修改Person类代码

public class Person {

    private Bike mBike;
    private Car mCar;
    private Train mTrain;

    public Person(){
        mBike = new Bike();
        //mCar = new Car();
//        mTrain = new Train();
    }

    public void goOut(){
        System.out.println("出门啦");
        mBike.drive();
        //mCar.drive();
//        mTrain.drive();
    }

    public static void main(String ... args){
            //TODO:
        Person person = new Person();
        person.goOut();
    }
}

(2)使用依赖倒置,上层模块不应该依赖底层模块,它们都应该依赖于抽象。抽象不应该依赖于细节,细节应该依赖于抽象。

public class Person {

//    private Bike mBike;
    private Car mCar;
    private Train mTrain;
    private Driveable mDriveable;

    public Person(){
//        mBike = new Bike();
        //mCar = new Car();
       mDriveable = new Train();
    }

    public void goOut(){
        System.out.println("出门啦");
        mDriveable.drive();
        //mCar.drive();
//        mTrain.drive();
    }

    public static void main(String ... args){
            //TODO:
        Person person = new Person();
        person.goOut();
    }
}

3、控制反转(Inversion of Control)

定义
IoC 是一种新的设计模式,它对上层模块与底层模块进行了更进一步的解耦。控制反转的意思是反转了上层模块对于底层模块的依赖控制。
实例

public class Person2 {

    private Driveable mDriveable;

    public Person2(Driveable driveable){
        this.mDriveable = driveable;
    }

    public void goOut(){
        System.out.println("出门啦");
        mDriveable.drive();
        //mCar.drive();
//        mTrain.drive();
    }

    public static void main(String ... args){
            //TODO:将 mDriveable 的实例化移到 Person 外面
        Person2 person = new Person2(new Car());
        person.goOut();
    }
}

就这样无论出行方式怎么变化,Person 这个类都不需要更改代码了。
在上面代码中,Person 把内部依赖的创建权力移交给了 Person2。也就是说 Person 只关心依赖提供的功能,但并不关心依赖的创建。
其中Person2称为IoC容器(依赖注入的地方)

4、依赖注入(Dependency injection)

为了不因为依赖实现的变动而去修改 Person,也就是说以可能在 Driveable 实现类的改变下不改动 Person 这个类的代码,尽可能减少两者之间的耦合需要采用IoC 模式来进行改写代码。
这个需要我们移交出对于依赖实例化的控制权,Person 无法实例化依赖了,它就需要在外部(IoC 容器)赋值给它,这个赋值的动作有个专门的术语叫做注入(injection),需要注意的是在 IoC 概念中,这个注入依赖的地方被称为 IoC 容器,但在依赖注入概念中,一般被称为注射器 (injector)。
表达通俗一点就是:我不想自己实例化依赖,你(injector)创建它们,然后在合适的时候注入给我

实现依赖注入有 3 种方式:

  • 构造函数中注入
  • setter 方式注入
  • 接口注入
/**
 * 接口方式注入
 * 接口的存在,表明了一种依赖配置的能力。
 */
public interface DepedencySetter {
    void set(Driveable driveable);
}
public class Person2  implements DepedencySetter {

    //接口方式注入
    @Override
    public void set(Driveable driveable) {
        this.mDriveable = mDriveable;
    }

    private Driveable mDriveable;

    //构造函数注入
    public Person2(Driveable driveable){
        this.mDriveable = driveable;
    }

    //setter 方式注入
    public void setDriveable(Driveable mDriveable) {
        this.mDriveable = mDriveable;
    }

    public void goOut(){
        System.out.println("出门啦");
        mDriveable.drive();
        //mCar.drive();
//        mTrain.drive();
    }

    public static void main(String ... args){
            //TODO:
        Person2 person = new Person2(new Car());
        person.goOut();
    }
}

(十一)Java代理模式

1、代理模式基本概念及分类

(1.1)基本概念
在这里插入图片描述
为其他对象提供一种代理以控制对这个对象的访问。代理类和委托类有共同的父类或父接口,这样在任何使用委托类对象的地方都可以用代理对象替代。代理对象起到中介的作用,可去掉功能服务或增加额外的服务。负责为委托类预处理消息,过滤消息并将请求分派给委托类处理,以及进行消息被委托类执行后的后续操作。
例如火车票代售处是火车站的代理,相对于火车站,可以提供额外的服务,如电话预约,提供额外服务的同时,会收取一定金额的手续费。也可以将原有的功能去掉,如代售处不能提供退票服务。
(1.2)代理模式模型
代理模式一般设计到角色有4 种:
1、抽象角色:对应代理接口(<< interface >>Subject),用来定义代理类和委托类的公共对外方法/接口;
2、真实角色:对应委托类(接口实现类RealSubject),真正实现业务逻辑的类,是代理角色所代表的真实对象,是最终要引用的对象;
3、代理角色:对应代理类(Proxy),用来代理和封装真实角色。代理角色内部含有对真实对象的引用,从而可以操作真实对象。同时,代理对象可以在执行真是对象操作时,添加或去除其他操作,相当于对真实对象进行封装;
4、客户角色:对应客户端,使用代理类和主题接口完成一些工作。
在这里插入图片描述
在代理模式中真实角色对于客户端角色来说的透明的,也就是客户端不知道也无需知道真实角色的存在。 为了保持行为的一致性,代理角色和真实角色通常会实现相同的接口,所以在访问者看来两者没有丝毫的区别。
通过代理角色这中间一层,能有效控制对真实角色(委托类对象)的直接访问,也可以很好地隐藏和保护委托类对象,同时也为实施不同控制策略预留了空间,从而在设计上获得了更大的灵活性。
(1.3)代理模式特点
(1.3.1)代理模式优点

  • 隐藏委托类的实现,调用者只需要和代理类进行交互即可。
  • 解耦,在不改变委托类代码情况下做一些额外处理,比如添加初始判断及其他公共操作
    (1.3.2)代理模式应用场景
    代理的使用场景很多,struts2中的 action 调用, hibernate的懒加载, spring的 AOP无一不用到代理。总结起来可分为以下几类:
    1、在原方法执行之前和之后做一些操作,可以用代理来实现(比如记录Log,做事务控制等)。
    2、封装真实的主题类,将真实的业务逻辑隐藏,只暴露给调用者公共的主题接口。
    3、在延迟加载上的应用。
    (1.4)常见代理模式
    (1)远程代理:为不同地理的对象,提供局域网代表对象。类似客户端-服务器代理模式。
    (2)虚拟代理:根据需要将资源消耗很大的对象进行延迟,真正需要的时候进行创建。如网络图片缓存。
    (3)保护代理:控制用户的访问权限,如网页需要注册才能浏览发帖。
    (4)智能引用代理:提供对目标对象额外的服务。如日志处理、权限管理、事务处理……

2、代理模式实现方式

根据代理类的生成时间不同可以将代理分为静态代理和动态代理。

(2.1)静态代理

所谓静态代理也就是在程序运行前就已经存在代理类的.class文件,代理类和委托类的关系在运行前就确定了。
接口

public interface Moveable {
	void move();
}

被代理对象Car

public class Car implements Moveable {

	@Override
	public void move() {
		//实现开车
		try {
			Thread.sleep(new Random().nextInt(1000));
			System.out.println("汽车行驶中....");
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

}

通过代理记录Car行驶时间
(2.1.1)继承方式

public class Car2 extends Car {

	@Override
	public void move() {
		long starttime = System.currentTimeMillis();
		System.out.println("汽车开始行驶....");
		super.move();
		long endtime = System.currentTimeMillis();
		System.out.println("汽车结束行驶....  汽车行驶时间:" 
				+ (endtime - starttime) + "毫秒!");
	}
	
}

(2.1.2)聚合方式
聚合:在代理中引用被代理对象

public class Car3 implements Moveable {

	public Car3(Car car) {
		super();
		this.car = car;
	}

	private Car car;
	
	@Override
	public void move() {
		long starttime = System.currentTimeMillis();
		System.out.println("汽车开始行驶....");
		car.move();
		long endtime = System.currentTimeMillis();
		System.out.println("汽车结束行驶....  汽车行驶时间:" 
				+ (endtime - starttime) + "毫秒!");
	}

}

(2.1.3)继承方式与聚合方式对比
聚合方式比继承方式更适合代理模式:适合功能的叠加(可灵活传递,组合)
记录日志代理

public class CarLogProxy implements Moveable {

	public CarLogProxy(Moveable m) {
		super();
		this.m = m;
	}

	private Moveable m;
	
	@Override
	public void move() {
		System.out.println("日志开始....");
		m.move();
		System.out.println("日志结束....");
	}

}

记录时间代理

public class CarTimeProxy implements Moveable {

	public CarTimeProxy(Moveable m) {
		super();
		this.m = m;
	}

	private Moveable m;
	
	@Override
	public void move() {
		long starttime = System.currentTimeMillis();
		System.out.println("汽车开始行驶....");
		m.move();
		long endtime = System.currentTimeMillis();
		System.out.println("汽车结束行驶....  汽车行驶时间:" 
				+ (endtime - starttime) + "毫秒!");
	}

}

测试

	public static void main(String[] args) {
		Car car = new Car();
		CarLogProxy clp = new CarLogProxy(car);
		CarTimeProxy ctp = new CarTimeProxy(clp);
		ctp.move();//先记录日志后记录时间
	}

如果要按照上述的方法使用代理模式,那么真实角色(委托类)必须是事先已经存在的,并将其作为代理对象的内部属性。但是实际使用时,一个真实角色必须对应一个代理角色,如果大量使用会导致类的急剧膨胀;此外,如果事先并不知道真实角色(委托类),该如何使用代理呢?这个问题可以通过Java的动态代理类来解决。

(2.2)动态代理

通过动态代码可实现对不同类、不同方法的代理。动态代理的源码是在程序运行期间由JVM根据反射等机制动态的生成,所以不存在代理类的字节码文件(.class)。代理类和委托类的关系在程序运行时确定。
(2.2.1)JDK动态代理
1.实现模式
在这里插入图片描述
Java动态代理类位于java.lang.reflect包下,一般主要涉及到以下两个类:
(1)Interface InvocationHandler
InvocationHandler是负责连接代理类和委托类的中间类必须实现的接口,它自定义了一个 invoke 方法,用于集中处理在动态代理类对象上的方法调用,每次生成动态代理对象都邀制定一个对应的调用处理器对象,通常在该方法中实现对委托类的代理访问。

public object invoke(Object obj,Method method,Object[] args)

在实际使用时,obj指代理类的实例,method指被代理的方法,args是该方法的参数数组。这个抽象方法在代理类中动态实现。
该方法也是InvocationHandler接口所定义的唯一的一个方法,该方法负责集中处理动态代理类上的所有方法的调用。调用处理器根据这三个参数进行预处理或分派到委托类实例上执行。
(2)Proxy class动态代理类
Proxy是 Java 动态代理机制的主类,它提供了一组静态方法来为一组接口动态地生成代理类及其对象。

static Object newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandler h)

返回代理类的一个实例,返回后的代理类可以当做被代理类使用(可使用被代理类在接口中声明过的方法)
该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
2.实例
步骤1:创建一个实现接口InvocationHandler的调用处理器,它必须实现invoke方法

public class TimeHandler implements InvocationHandler {
//动态代理类对应的调用处理程序类(时间处理器)
	public TimeHandler(Object target) {
		super();
		this.target = target;
	}
//代理类持有一个委托类的对象引用
	private Object target;
	
	/*
	 * 参数:
	 * proxy  被代理对象
	 * method  被代理对象的方法
	 * args 方法的参数
	 * 
	 * 返回值:
	 * Object  方法的返回值
	 * */
	@Override
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		long starttime = System.currentTimeMillis();
		System.out.println("汽车开始行驶....");
		method.invoke(target);//调用被代理对象的方法(Car的move方法)
		long endtime = System.currentTimeMillis();
		System.out.println("汽车结束行驶....  汽车行驶时间:" 
				+ (endtime - starttime) + "毫秒!");
		return null;
	}
}

TimeHandler实现了InvocationHandler的invoke方法,当代理对象的方法被调用时,invoke方法会被回调。其中proxy表示实现了公共代理方法的动态代理对象。
步骤2:创建被代理的类以及接口
接口

public interface Moveable {
	void move();
}

代理类

public class Car implements Moveable {

	@Override
	public void move() {
		//实现开车
		try {
			Thread.sleep(new Random().nextInt(1000));
			System.out.println("汽车行驶中....");
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

步骤3:调用Proxy的静态方法newProxyInstance,提供ClassLoader和代理接口类型数组动态创建一个代理类,并通过代理调用方法

//客户端,使用代理类和主题接口完成功能
public class Test {
	/**
	 * JDK动态代理测试类
	 */
	public static void main(String[] args) {
		Car car = new Car();
		InvocationHandler h = new TimeHandler(car);
		Class<?> cls = car.getClass();
		/**
		 * loader  类加载器
		 * interfaces  实现接口
		 * h InvocationHandler
		 */
		Moveable m = (Moveable)Proxy.newProxyInstance(cls.getClassLoader(),
												cls.getInterfaces(), h);//获得动态代理对象,动态代理对象与代理对象实现同一接口
		m.move();//调用动态代理的move方法
	}
}

上面代码通过InvocationHandler handler=new TimeHandler(target);将委托对象作为构造方法的参数传递给了TimeHandler来作为代理方法调用的对象。当我们调用代理对象的move()方法时,该调用将会被转发到TimeHandler对象的invoke上,从而达到动态代理的效果。
3.总结
所谓动态代理是这样一种class:它是运行时生成的class,该class需要实现一组interface,使用动态代理类时,必须实现InvocationHandler接口
(2.2.2)cglib动态代理
实例
步骤1:引入cglib-nodep.jar
步骤2:代理类,不用实现接口

public class Train {

	public void move(){
		System.out.println("火车行驶中...");
	}
}

步骤3:cglib代理类,实现MethodInterceptor接口

public class CglibProxy implements MethodInterceptor {

	private Enhancer enhancer = new Enhancer();//创建代理类的属性
	
	public Object getProxy(Class clazz){
		//设置创建子类(需要产生代理)的类
		enhancer.setSuperclass(clazz);
		enhancer.setCallback(this);
		
		return enhancer.create();//返回代理类的实例
	}
	
	/**
	 * 拦截所有目标类方法的调用
	 * obj  目标类的实例
	 * m   目标方法的反射对象
	 * args  方法的参数
	 * proxy代理类的实例
	 */
	@Override
	public Object intercept(Object obj, Method m, Object[] args,
			MethodProxy proxy) throws Throwable {
		System.out.println("日志开始...");
		//代理类调用父类的方法(cglib采用继承的方式,故代理类是目标类的子类)
		proxy.invokeSuper(obj, args);
		System.out.println("日志结束...");
		return null;
	}

}

步骤4:获取代理类,并调用代理类的方法

	public static void main(String[] args) {

		CglibProxy proxy = new CglibProxy();
		Train t = (Train)proxy.getProxy(Train.class);
		t.move();
	}

(2.2.3)JDK动态代理与cglib动态代理的区别
JDK动态代理:只能代理实现了接口的类,没有实现接口的类不能实现JDK的动态代理
CGLIB动态代理:针对类来实现代理,对指定目标类产生一个子类,通过方法拦截技术拦截所有父类方法的调用。

(2.3)静态代理与动态代理对比

1、静态代理
优点:
业务类只需要关注业务逻辑本身,保证了业务类的重用性。这是代理的共有优点。
缺点:
(1)代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理,静态代理在程序规模稍大时就无法胜任了。
(2)如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
(3)采用静态代理模式,那么真实角色(委托类)必须事先已经存在的,并将其作为代理对象代理对象内部属性。但是实际使用时,一个真实角色必须对应一个代理角色,如果大量使用会导致类的急剧膨胀。
2、动态代理
优点
1、动态代理类的字节码在程序运行时由Java反射机制动态生成,无需程序员手工编写它的源代码。
2、动态代理类不仅简化了编程工作,而且提高了软件系统的可扩展性,因为Java 反射机制可以生成任意类型的动态代理类。
缺点
JDK的动态代理机制只能代理实现了接口的类,而不能实现接口的类就不能实现JDK的动态代理,cglib是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。

发布了74 篇原创文章 · 获赞 15 · 访问量 6257

猜你喜欢

转载自blog.csdn.net/qq_29966203/article/details/90733538