Java反射说的是在运行状态中,对于任何一个类,我们都能够知道这个类有哪些方法和属性。对于任何一个对象,我们都能够对它的方法和属性进行调用。
我们把这种动态获取对象信息和调用对象方法的功能称之为反射机制。
(1)Class类对象——描述.class字节码文件
将(.java类文件)经过编译后的.class字节码文件(位于硬盘上)通过类加载器(ClassLoader)加载进内存,通过java.lang.Class类对象对字节码文件进行描述。每一个类都是一个Class类的实例对象。
Class类对象是用来对.class文件进行描述。主要包括三个成员变量:
(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对象都是同一个。
反射机制reflect可以在运行期间获取类的字段、方法、父类和接口等信息。
/**
* 获取成员变量的信息
* 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");
}
}
(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.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;
}
}
String getName()
编译时刻加载的类是静态加载类,运行时刻加载的类是动态加载类。
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);
}
}
在JDBC 的操作中,如果要想进行数据库的连接,则必须按照以上的几步完成
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、关闭数据库
}
在 Java的反射机制在做基础框架的时候非常有用,行内有一句这样的老话:反射机制是Java框架的基石。一般应用层面很少用,不过这种东西,现在很多开源框架基本都已经封装好了,自己基本用不着写。典型的除了hibernate之外,还有spring也用到很多反射机制。最经典的就是xml的配置模式。
Spring通过XML配置模式装载Bean的过程:
通过反射,Spring框架可以在不改变代码的前提下,直接修改配置文件。模拟Spring加载XML配置文件:
public class BeanFactory {
private Map beanMap = new HashMap();
/**
* 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
}
注解与注释:
注解:说明程序的。给计算机看得。
注释:用文字描述程序,给程序员看的。方便程序员理解。
注解(Annotation)是插入代码中的元数据,一种代码级别的说明。它是在JDK5.0及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。
(1)JDK 1.5 之后的新特性
(2)用来说明程序
(3)使用注解:@注解名称
Annotation的作用大致可分为三类:
/**
* 注解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.1)JDK中内置注解
public class Fruit{
public void displayName(){
System.out.println("水果的名字是:*****");
}
}
class Orange extends Fruit{
@Override
public void displayName(){
System.out.println("水果的名字是:桔子");
}
}
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.1)按照运行机制分类
(3.2)根据注解功能与用途
(3.3)根据成员个数分类
(4.1)格式
元注解
public @interface 注解名称{
... 属性列表
}
(4.2)本质
注解本质上就是一个接口,该接口默认继承java.lang.annotation.Annotation接口。
public interface MyAnno extends java.lang.annotation.Annotation{}
(4.3)属性
接口中的抽象方法被称为注解的属性。每一个抽象方法实际上是声明了一个配置参数。方法的名称就是参数的名称,返回值类型就是参数类型。
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.TYPE,ElementType.METHOD,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface MyAnno{}
(4.5)在程序使用(解析)注解:获取注解中定义的属性值
/***********注解声明***************/
/**
* 水果名称注解
*/
@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)小结
/**
* 描述需要执行的类名和方法名
* /
@Target ({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Pro{
String className();
String methodName();
}
}
public class ReflectTest{
public static void main(String[] args) throws Exception{
// 前提:不能改变该类的任何代码,可以创建任意类的对象,可以执行任意方法
// 1. 解析注解
// 1.1 获取该类的字节码文件对象
Class 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);
}
// 简单的测试框架
// 当主方法柱形后,会自动执行被检测的所有方法(加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();
}
}
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));}
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Check{}
(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'
定义
依赖是类与类之间的连接,依赖关系表示一个类依赖于另一个类的定义,通俗来讲就是需要
实例
一个人(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{
}
定义
高层模块不应该依赖低层模块,二者都该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象;通俗来讲,依赖倒置原则的本质就是通过抽象(接口或抽象类)使个各类或模块的实现彼此独立,互不影响,实现模块间的松耦合。
类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();
}
}
定义
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容器(依赖注入的地方)
为了不因为依赖实现的变动而去修改 Person,也就是说以可能在 Driveable 实现类的改变下不改动 Person 这个类的代码,尽可能减少两者之间的耦合需要采用IoC 模式来进行改写代码。
这个需要我们移交出对于依赖实例化的控制权,Person 无法实例化依赖了,它就需要在外部(IoC 容器)赋值给它,这个赋值的动作有个专门的术语叫做注入(injection),需要注意的是在 IoC 概念中,这个注入依赖的地方被称为 IoC 容器,但在依赖注入概念中,一般被称为注射器 (injector)。
表达通俗一点就是:我不想自己实例化依赖,你(injector)创建它们,然后在合适的时候注入给我
实现依赖注入有 3 种方式:
/**
* 接口方式注入
* 接口的存在,表明了一种依赖配置的能力。
*/
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();
}
}
(1.1)基本概念
为其他对象提供一种代理以控制对这个对象的访问。代理类和委托类有共同的父类或父接口,这样在任何使用委托类对象的地方都可以用代理对象替代。代理对象起到中介的作用,可去掉功能服务或增加额外的服务。负责为委托类预处理消息,过滤消息并将请求分派给委托类处理,以及进行消息被委托类执行后的后续操作。
例如火车票代售处是火车站的代理,相对于火车站,可以提供额外的服务,如电话预约,提供额外服务的同时,会收取一定金额的手续费。也可以将原有的功能去掉,如代售处不能提供退票服务。
(1.2)代理模式模型
代理模式一般设计到角色有4 种:
1、抽象角色:对应代理接口(<< interface >>Subject),用来定义代理类和委托类的公共对外方法/接口;
2、真实角色:对应委托类(接口实现类RealSubject),真正实现业务逻辑的类,是代理角色所代表的真实对象,是最终要引用的对象;
3、代理角色:对应代理类(Proxy),用来代理和封装真实角色。代理角色内部含有对真实对象的引用,从而可以操作真实对象。同时,代理对象可以在执行真是对象操作时,添加或去除其他操作,相当于对真实对象进行封装;
4、客户角色:对应客户端,使用代理类和主题接口完成一些工作。
在代理模式中真实角色对于客户端角色来说的透明的,也就是客户端不知道也无需知道真实角色的存在。 为了保持行为的一致性,代理角色和真实角色通常会实现相同的接口,所以在访问者看来两者没有丝毫的区别。
通过代理角色这中间一层,能有效控制对真实角色(委托类对象)的直接访问,也可以很好地隐藏和保护委托类对象,同时也为实施不同控制策略预留了空间,从而在设计上获得了更大的灵活性。
(1.3)代理模式特点
(1.3.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的动态代理类来解决。
通过动态代码可实现对不同类、不同方法的代理。动态代理的源码是在程序运行期间由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动态代理:针对类来实现代理,对指定目标类产生一个子类,通过方法拦截技术拦截所有父类方法的调用。
1、静态代理
优点:
业务类只需要关注业务逻辑本身,保证了业务类的重用性。这是代理的共有优点。
缺点:
(1)代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理,静态代理在程序规模稍大时就无法胜任了。
(2)如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
(3)采用静态代理模式,那么真实角色(委托类)必须事先已经存在的,并将其作为代理对象代理对象内部属性。但是实际使用时,一个真实角色必须对应一个代理角色,如果大量使用会导致类的急剧膨胀。
2、动态代理
优点
1、动态代理类的字节码在程序运行时由Java反射机制动态生成,无需程序员手工编写它的源代码。
2、动态代理类不仅简化了编程工作,而且提高了软件系统的可扩展性,因为Java 反射机制可以生成任意类型的动态代理类。
缺点
JDK的动态代理机制只能代理实现了接口的类,而不能实现接口的类就不能实现JDK的动态代理,cglib是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。