临沂市建设局网站,购物网站怎么做推广,网站建设当中的技术解决方案,django做的网站举例注解
介绍#xff1a;
注解(Annotation)很重要#xff0c;未来的开发模式都是基于注解的#xff0c;JPA是基于注解的#xff0c;Spring2.5以上都是基于注解的#xff0c;Hibernate3.x以后也是基于注解的#xff0c;现在的Struts2有一部分也是基于注解的了#xff0c;注…注解
介绍
注解(Annotation)很重要未来的开发模式都是基于注解的JPA是基于注解的Spring2.5以上都是基于注解的Hibernate3.x以后也是基于注解的现在的Struts2有一部分也是基于注解的了注解是一种趋势
注解(Annotation)相当于一种标记在程序中加入注解就等于为程序打上某种标记没有加则等于没有任何标记以后javac编译器、开发工具和其他程序可以通过反射来了解你的类及各种元素上有无何种标记看你的程序有什么标记就去干相应的事标记可以加在包、类属性、方法方法的参数以及局部变量上。
元注解
元注解的作用就是负责注解其他注解。Java 定义了一套注解共有 7 个3 个在 java.lang 中剩下 4 个在 java.lang.annotation 中。
1、作用在代码的注解是
Override - 检查该方法是否是重写方法。如果发现其父类或者是引用的接口中并没有该方法时会报编译错误。Deprecated - 标记过时方法。如果使用该方法会报编译警告。SuppressWarnings - 指示编译器去忽略注解中声明的警告。
2、作用在其他注解的注解(或者说元注解)是:
Retention - 标识这个注解怎么保存是只在代码中还是编入class文件中或者是在运行时可以通过反射访问。Documented - 标记这些注解是否包含在用户文档中。Target - 表示这个注解用在什么地方。Inherited - 标记这个注解是继承于哪个注解类(默认注解并没有继承于任何子类)
3、从 Java 7 开始额外添加了 3 个注解:
SafeVarargs - Java 7 开始支持忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告。FunctionalInterface - Java 8 开始支持标识一个匿名函数或函数式接口。Repeatable - Java 8 开始支持标识某注解可以在同一个声明上使用多次。 Target
Target说明了Annotation所修饰的对象范围Annotation可被用于 packages、types类、接口、枚举、Annotation类型、类型成员方法、构造方法、成员变量、枚举值、方法参数和本地变量如循环变量、catch参数。在Annotation类型的声明中使用了Target可更加明晰其修饰的目标。
作用用于描述注解的使用范围即被描述的注解可以用在什么地方
取值(ElementType)有
ElementType.CONSTRUCTOR用于描述构造器ElementType.FIELD成员变量对象属性包括enum实例ElementType.LOCAL_VARIABLE用于描述局部变量ElementType.METHOD用于描述方法ElementType.PACKAGE用于描述包ElementType.PARAMETER用于描述参数ElementType.TYPE用于描述类、接口(包括注解类型) 或enum声明
使用实例
Target(ElementType.TYPE)
public interface Table {/*** 数据表名称注解默认值为类名称* return*/public String tableName() default className;
}Target(ElementType.FIELD)
public interface NoDBColumn {}注解Table 可以用于注解类、接口(包括注解类型) 或enum声明,而注解NoDBColumn仅可用于注解类的成员变量。 Retention
Retention定义了该Annotation被保留的时间长短某些Annotation仅出现在源代码中而被编译器丢弃而另一些却被编译在class文件中编译在class文件中的Annotation可能会被虚拟机忽略而另一些在class被装载时将被读取请注意并不影响class的执行因为Annotation与class在使用上是被分离的。使用这个meta-Annotation可以对 Annotation的“生命周期”限制。
作用表示需要在什么级别保存该注释信息用于描述注解的生命周期即被描述的注解在什么范围内有效
取值RetentionPoicy有
RetentionPoicy.SOURCE在编译阶段丢弃。这些注解在编译结束之后不再有任何意义所以它们不会被写入字节码(Override和SuppressWarnings都是属于这类注解)在源文件中有效即源文件保留RetentionPoicy.CLASS在类加载的时候丢弃。在字节码文件的处理中有用。注解默认使用这种方式在class文件中有效即class保留RetentionPoicy.RUNTIME始终不会丢弃运行期也保留该注解因此可以使用反射机制读取该注解的信息。我们自定义的注解通常使用这种方式在运行时有效即运行时保留
具体实例如下
Target(ElementType.FIELD)
Retention(RetentionPolicy.RUNTIME)
public interface Column {public String name() default fieldName;public String setFuncName() default setField;public String getFuncName() default getField; public boolean defaultDBValue() default false;
}Column注解的的RetentionPolicy的属性值是RUTIME,这样注解处理器可以通过反射获取到该注解的属性值从而去做一些运行时的逻辑处理 Documented:
Documented用于描述其它类型的annotation应该被作为被标注的程序成员的公共API因此可以被例如javadoc此类的工具文档化。Documented是一个标记注解没有成员。
具体实例如下
Target(ElementType.FIELD)
Retention(RetentionPolicy.RUNTIME)
Documented
public interface Column {public String name() default fieldName;public String setFuncName() default setField;public String getFuncName() default getField; public boolean defaultDBValue() default false;
}Inherited
Inherited 元注解是一个标记注解Inherited阐述了某个被标注的类型是被继承的。如果一个使用了Inherited修饰的annotation类型被用于一个class则这个annotation将被用于该class的子类。
注意Inherited annotation类型是被标注过的class的子类所继承。类并不从它所实现的接口继承annotation方法并不从它所重载的方法继承annotation。
当Inherited annotation类型标注的annotation的Retention是RetentionPolicy.RUNTIME则反射API增强了这种继承性。如果我们使用java.lang.reflect去查询一个Inherited annotation类型的annotation时反射代码检查将展开工作检查class和其父类直到发现指定的annotation类型被发现或者到达类继承结构的顶层。
实例代码
Inherited
public interface Greeting {public enum FontColor{ BULE,RED,GREEN};String name();FontColor fontColor() default FontColor.GREEN;
}自定义注解
使用interface自定义注解时自动继承了java.lang.annotation.Annotation接口由编译程序自动完成其他细节。在定义注解时不能继承其他的注解或接口。interface用来声明一个注解其中的每一个方法实际上是声明了一个配置参数。方法的名称就是参数的名称返回值类型就是参数的类型返回值类型只能是基本类型、Class、String、enum。可以通过default来声明参数的默认值。
定义注解格式 public interface 注解名 {定义体}
注解参数的可支持数据类型
所有基本数据类型int,float,boolean,byte,double,char,long,short)String类型Class类型enum类型Annotation类型以上所有类型的数组
Annotation类型里面的参数该怎么设定: 第一只能用public或默认(default)这两个访问权修饰.例如,String value();这里把方法设为defaul默认类型 第二参数成员只能用基本类型byte,short,char,int,long,float,double,boolean八种基本数据类型和 String,Enum,Class,annotations等数据类型,以及这一些类型的数组.例如,String value();这里的参数成员就为String; 第三如果只有一个参数成员,最好把参数名称设为value,后加小括号.例:下面的例子FruitName注解就只有一个参数成员。
简单的自定义注解和使用注解实例
package annotation;import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** 水果名称注解*/
Target(ElementType.FIELD)
Retention(RetentionPolicy.RUNTIME)
Documented
public interface FruitName {String value() default ;
}package annotation;import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** 水果颜色注解*/
Target(ElementType.FIELD)
Retention(RetentionPolicy.RUNTIME)
Documented
public interface FruitColor {/*** 颜色枚举*/public enum Color{ BULE,RED,GREEN};/*** 颜色属性* return*/Color fruitColor() default Color.GREEN;}package annotation;import annotation.FruitColor.Color;public class Apple {FruitName(Apple)private String appleName;FruitColor(fruitColorColor.RED)private String appleColor;public void setAppleColor(String appleColor) {this.appleColor appleColor;}public String getAppleColor() {return appleColor;}public void setAppleName(String appleName) {this.appleName appleName;}public String getAppleName() {return appleName;}public void displayName(){System.out.println(水果的名字是苹果);}
}注解元素的默认值
**注解元素必须有确定的值要么在定义注解的默认值中指定要么在使用注解时指定非基本类型的注解元素的值不可为null。**因此, 使用空字符串或0作为默认值是一种常用的做法。这个约束使得处理器很难表现一个元素的存在或缺失的状态因为每个注解的声明中所有元素都存在并且都具有相应的值为了绕开这个约束我们只能定义一些特殊的值例如空字符串或者负数一次表示某个元素不存在在定义注解时这已经成为一个习惯用法。例如
package annotation;import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** 水果供应者注解*/
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 ;
}为属性指定缺省值(默认值)
语法类型 属性名() default 默认值;
实例代码
package cn.gacl.annotation;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;Retention(RetentionPolicy.RUNTIME)
//Retention注解决定MyAnnotation注解的生命周期
Target( { ElementType.METHOD, ElementType.TYPE })
public interface MyAnnotation {String color() default blue;//为属性指定缺省值
}package cn.gacl.annotation;MyAnnotation
public class MyAnnotationTest {public static void main(String[] args) {/*** 用反射方式获得注解对应的实例对象后在通过该对象调用属性对应的方法*/MyAnnotation annotation (MyAnnotation) MyAnnotationTest.class.getAnnotation(MyAnnotation.class);System.out.println(annotation.color());//输出color属性的默认值blue}
}value属性:
如果一个注解中有一个名称为value的属性且你只想设置value属性(即其他属性都采用默认值或者你只有一个value属性)那么可以省略掉“value”部分。
例如SuppressWarnings(“deprecation”)
package cn.gacl.annotation;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;Retention(RetentionPolicy.RUNTIME)
//Retention注解决定MyAnnotation注解的生命周期
Target( { ElementType.METHOD, ElementType.TYPE })
public interface MyAnnotation {String color() default blue;//为属性指定缺省值String value();//定义一个名称为value的属性
}package cn.gacl.annotation;MyAnnotation(MrVk)//等价于MyAnnotation(valueMrVk)
public class MyAnnotationTest {public static void main(String[] args) {/*** 用反射方式获得注解对应的实例对象后在通过该对象调用属性对应的方法*/MyAnnotation annotation (MyAnnotation) MyAnnotationTest.class.getAnnotation(MyAnnotation.class);System.out.println(annotation.color());//输出color属性的默认值blueSystem.out.println(annotation.value());}
}为注解增加高级属性:
数组类型的属性:
增加数组类型的属性int[] arrayAttr() default {1,2,4};应用数组类型的属性MyAnnotation(arrayAttr{2,4,5})如果数组属性只有一个值这时候属性值部分可以省略大括号如MyAnnotation(arrayAttr2)这就表示数组属性只有一个值值为2
枚举类型的属性:
增加枚举类型的属性EumTrafficLamp lamp() default EumTrafficLamp.RED;应用枚举类型的属性MyAnnotation(lampEumTrafficLamp.GREEN) 注解综合测试:
EumTrafficLamp.java
package cn.gacl.annotation;
/*** 交通信号灯颜色枚举*/
public enum EumTrafficLamp {RED,//红YELLOW,//黄GREEN//绿
}MetaAnnotation.java
/*** MetaAnnotation注解类为元注解*/
public interface MetaAnnotation {String value();//元注解MetaAnnotation设置有一个唯一的属性value
}MyAnnotation.java
package cn.gacl.annotation;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;Retention(RetentionPolicy.RUNTIME)
//Retention注解决定MyAnnotation注解的生命周期
Target({ElementType.METHOD, ElementType.TYPE})
public interface MyAnnotation {String color() default blue;//为属性指定缺省值/*** 为注解添加value属性这个value属性很特殊如果一个注解中只有一个value属性要设置* 那么在设置注解的属性值时可以省略属性名和等号不写 直接写属性值如SuppressWarnings(deprecation)* 这里的MyAnnotation注解设置了两个String类型的属性color和value* 因为color属性指定有缺省值value属性又是属于特殊的属性因此使用MyAnnotation注解时* 可以这样使用MyAnnotation注解MyAnnotation(colorred,valuexdp)* 也可以这样使用MyAnnotation(MrVK)这样写就表示MyAnnotation注解只有一个value属性要设置color属性采用缺省值* 当一个注解只有一个value属性要设置时是可以省略value的*/String value();//定义一个名称为value的属性//添加一个int类型数组的属性int[] arrayAttr() default {1,2,4};//添加一个枚举类型的属性并指定枚举属性的缺省值缺省值只能从枚举类EumTrafficLamp中定义的枚举对象中取出任意一个作为缺省值EumTrafficLamp lamp() default EumTrafficLamp.RED;//为注解添加一个注解类型的属性,并指定注解属性的缺省值MetaAnnotation annotationAttr() default MetaAnnotation(xdp);}MyAnnotationTest.java
package cn.gacl.annotation;
/*** 这里是将新创建好的注解类MyAnnotation标记到AnnotaionTest类上* 并应用了注解类MyAnnotation中定义各种不同类型的的属性*/
MyAnnotation(colorred,valueMrVk,arrayAttr{3,5,6},lampEumTrafficLamp.GREEN,annotationAttrMetaAnnotation(gacl))
public class MyAnnotationTest {MyAnnotation(将MyAnnotation注解标注到main方法上)public static void main(String[] args) {/*** 这里是检查Annotation类是否有注解这里需要使用反射才能完成对Annotation类的检查*/if(MyAnnotationTest.class.isAnnotationPresent(MyAnnotation.class)) {/*** 用反射方式获得注解对应的实例对象后在通过该对象调用属性对应的方法* MyAnnotation是一个类这个类的实例对象annotation是通过反射得到的这个实例对象是如何创建的呢* 一旦在某个类上使用了MyAnnotation那么这个MyAnnotation类的实例对象annotation就会被创建出来了*/MyAnnotation annotation (MyAnnotation) MyAnnotationTest.class.getAnnotation(MyAnnotation.class);System.out.println(annotation.color());//输出color属性的默认值redSystem.out.println(annotation.value());//输出value属性的默认值MrVkSystem.out.println(annotation.arrayAttr().length);//这里输出的数组属性的长度的结果为3数组属性有三个元素因此数组的长度为3System.out.println(annotation.lamp());//这里输出的枚举属性值为GREENSystem.out.println(annotation.annotationAttr().value());//这里输出的注解属性值:gaclMetaAnnotation ma annotation.annotationAttr();//annotation是MyAnnotation类的一个实例对象System.out.println(ma.value());//输出的结果为gacl}}
}反射机制中 isAnnotation() 和 isAnnotationPresent() 的区别 设 clazz 是一个Class对象 clazz.isAnnotation()判断clazz类是否是一个注解 clazz.isAnnotationPresent(Class? extends Annotation annotationclass)判断clazz类上是否标注了annotationclass注解。 Java注解导图 反射
1.什么是反射
**Java反射就是在运行状态中对于任意一个类都能够知道这个类的所有属性和方法对于任意一个对象都能够调用它的任意方法和属性并且能改变它的属性。**而这也是Java被视为动态或准动态为啥要说是准动态因为一般而言的动态语言定义是程序运行时允许改变程序结构或变量类型这种语言称为动态语言。从这个观点看PerlPythonRuby是动态语言CJavaC#不是动态语言。语言的一个关键性质。
2.反射能做什么
我们知道反射机制允许程序在运行时取得任何一个已知名称的class的内部信息包括包括其modifiers(修饰符)fields(属性)methods(方法)等并可于运行时改变fields内容或调用methods。那么我们便可以更灵活的编写代码代码可以在运行时装配无需在组件之间进行源代码链接降低代码的耦合度还有动态代理的实现等等但是需要注意的是反射使用不当会造成很高的资源消耗
3.反射的具体实现
下面是一个基本的类 Person
package com.ys.reflex;
public class Person {//私有属性private String name Tom;//公有属性public int age 18;//构造方法public Person() {}//私有方法private void say(){System.out.println(private say()...);}//公有方法public void work(){System.out.println(public work()...);}
} ①得到 Class 的三种方式
1.Class.forName(“全类名”):将字节码文件加载进内存,返回class对象
* 多用于配置文件将类名定义在配置文件中。读取文件加载类
2.类名.class:通过类名的属性class获取
* 多用于参数的传递
3.对象.getClass():getClass()方法在Object类中定义着
* 多用于对象的获取字节码的方式
//1、通过对象调用 getClass() 方法来获取,通常应用在比如你传过来一个 Object
// 类型的对象而我不知道你具体是什么类用这种方法Person p1 new Person();Class c1 p1.getClass();//2、直接通过 类名.class 的方式得到,该方法最为安全可靠程序性能更高
// 这说明任何一个类都有一个隐含的静态成员变量 classClass c2 Person.class;//3、通过 Class 对象的 forName() 静态方法来获取用的最多
// 但可能抛出 ClassNotFoundException 异常Class c3 Class.forName(com.ys.reflex.Person); 需要注意的是**一个类在 JVM 中只会有一个 Class 实例,**即我们对上面获取的 c1,c2,c3进行 equals 比较发现都是true
public class ReflectDemo1 {public static void main(String[] args) throws ClassNotFoundException {//1.Class.forName(全类名)Class cls1 Class.forName(ReflectDemo1.domain.Person);System.out.println(cls1);//2.类名.classClass cls2 Person.class;System.out.println(cls2);//3.对象.getClass()Person p new Person();Class cls3 p.getClass();System.out.println(cls3);//比较三个对象System.out.println(cls1cls2); //trueSystem.out.println(cls1cls3); //true//结论同一个字节码文件(*.class)在一次程序运行过程中只会被加载一次无论通过哪一种方式获取的class对象都是同一个}
}上述代码运行结果
class ReflectDemo1.domain.Person
class ReflectDemo1.domain.Person
class ReflectDemo1.domain.Person
true
true ②通过 Class 类获取成员变量、成员方法、接口、超类、构造方法等
查阅 API 可以看到 Class 有很多方法
getName()获得类的完整名字getFields()获得类的public类型的属性getDeclaredFields()获得类的所有属性。包括private 声明的和继承类getMethods()获得类的public类型的方法getDeclaredMethods()获得类的所有方法。包括private 声明的和继承类getMethod(String name, Class[] parameterTypes)获得类的特定方法name参数指定方法的名字parameterTypes 参数指定方法的参数类型getConstructors()获得类的public类型的构造方法getConstructor(Class[] parameterTypes)获得类的特定构造方法parameterTypes 参数指定构造方法的参数类型newInstance()通过类的不带参数的构造方法创建这个类的一个对象
class对象功能: 获取功能: 1获取成员变量们
Field[] getFields()获取所有public修饰的成员变量Field getField(String name)获取制定名称的public修饰的成员变量Field[] getDeclaredFields()获取所有的成员变量不考虑修饰符Field getDeclaredField(string name)获取制定名称的成员变量不考虑修饰符
2获取构造方法们
Constructor?[] getConstructors()获取所有public修饰的构造方法Constructor getConstructor(类?… parameterTypes)获取类的特定的public修饰构造方法parameterTypes 参数指定构造方法的参数类型Constructor getDeclaredConstructor(类?… parameterTypes)获取类的特定构造方法parameterTypes 参数指定构造方法的参数类型Constructor?[] getDeclaredconstructors()获取所有饰的构造方法
3获取成员方法们:
Methodi] getMethods()获得类的所有public类型的方法Method getMethod(string name类?… parameterTypes)获得类的特定的public类型的方法Method[] getDeclaredmethods()获得类的所有方法Method getDeclaredMethod(string name类?… parameterTypes)获得类特定的方法
4获取类名
String getName()获取类的完整名字
Field成员变量
1.设置值
void set(Object obj,Object value)
2.获取值
get(Object obj)
3.忽略访问权限修饰符的安全检查
setAccessible(true)暴力反射
Constructor构造方法
1.创建对象
T newInstance(Object…initargs)
Method方法对象
1.执行方法
Object invoke(Object obj,Object…args)
2.获取方法名称
String getName:获取方法名
通过一个例子来综合演示上面的方法
//获得类完整的名字
String className c2.getName();
System.out.println(className);//输出com.ys.reflex.Person//获得类的public类型的属性。
Field[] fields c2.getFields();
for(Field field : fields){System.out.println(field.getName());//age
}//获得类的所有属性。包括私有的
Field [] allFields c2.getDeclaredFields();
for(Field field : allFields){System.out.println(field.getName());//name age
}//获得类的public类型的方法。这里包括 Object 类的一些方法
Method [] methods c2.getMethods();
for(Method method : methods){System.out.println(method.getName());//work waid equls toString hashCode等
}//获得类的所有方法。
Method [] allMethods c2.getDeclaredMethods();
for(Method method : allMethods){System.out.println(method.getName());//work say
}//获得指定的属性
Field f1 c2.getField(age);
System.out.println(f1);
//获得指定的私有属性
Field f2 c2.getDeclaredField(name);
//启用和禁用访问安全检查的开关值为 true则表示反射的对象在使用时应该取消 java 语言的访问检查反之不取消
f2.setAccessible(true);
System.out.println(f2);//创建这个类的一个对象
Object p2 c2.newInstance();
//将 p2 对象的 f2 属性赋值为 Bobf2 属性即为 私有属性 name
f2.set(p2,Bob);
//使用反射机制可以打破封装性导致了java对象的属性不安全。
System.out.println(f2.get(p2)); //Bob//获取构造方法
Constructor [] constructors c2.getConstructors();
for(Constructor constructor : constructors){System.out.println(constructor.toString());//public com.ys.reflex.Person()
}4.根据反射获取父类属性
父类 Parent.java
public class Parent {public String publicField parent_publicField;protected String protectField parent_protectField;String defaultField parent_defaultField;private String privateField parent_privateField;}子类 Son.java
public class Son extends Parent {
}测试类
public class ReflectionTest {Testpublic void testGetParentField() throws Exception{Class c1 Class.forName(com.ys.model.Son);//获取父类私有属性值System.out.println(getFieldValue(c1.newInstance(),privateField));}public static Field getDeclaredField(Object obj,String fieldName) {Field field null;Class c obj.getClass();for(; c ! Object.class ; c c.getSuperclass()){try {field c.getDeclaredField(fieldName);field.setAccessible(true);return field;}catch (Exception e){//这里甚么都不要做并且这里的异常必须这样写不能抛出去。//如果这里的异常打印或者往外抛则就不会执行c c.getSuperclass(),最后就不会进入到父类中了}}return null;}public static Object getFieldValue(Object object,String fieldName) throws Exception{Field field getDeclaredField(object,fieldName);return field.get(object);}
} 通过执行上述代码我们获得了父类的私有属性值这里要注意的是直接通过反射获取子类的对象是不能得到父类的属性值的必须根据反射获得的子类 Class 对象在调用 getSuperclass() 方法获取父类对象然后在通过父类对象去获取父类的属性值。
4.反射总结
灵活使用反射能让我们代码更加灵活这里比如JDBC原生代码注册驱动hibernate 的实体类Spring 的 AOP等等都有反射的实现。但是凡事都有两面性反射也会消耗系统的性能增加复杂性等合理使用才是真
5.相关案例
【案例1】通过一个对象获得完整的包名和类名
package Reflect;/*** 通过一个对象获得完整的包名和类名* */
class Demo{//other codes...
}class hello{public static void main(String[] args) {Demo demonew Demo();System.out.println(demo.getClass().getName());}
}【运行结果】
Reflect.Demo【案例2】实例化Class类对象
package Reflect;
class Demo{//other codes...
}class hello{public static void main(String[] args) {Class? demo1null;Class? demo2null;Class? demo3null;try{//一般尽量采用这种形式demo1Class.forName(Reflect.Demo);}catch(Exception e){e.printStackTrace();}demo2new Demo().getClass();demo3Demo.class;System.out.println(类名称 demo1.getName());System.out.println(类名称 demo2.getName());System.out.println(类名称 demo3.getName());}
}【运行结果】
类名称 Reflect.Demo类名称 Reflect.Demo类名称 Reflect.Demo【案例3】通过Class实例化其他类的对象
通过无参构造实例化对象
package Reflect;class Person{public String getName() {return name;}public void setName(String name) {this.name name;}public int getAge() {return age;}public void setAge(int age) {this.age age;}Overridepublic String toString(){return [this.name this.age];}private String name;private int age;
}class hello{public static void main(String[] args) {Class? demonull;try{demoClass.forName(Reflect.Person);}catch (Exception e) {e.printStackTrace();}Person pernull;try {per(Person)demo.newInstance();} catch (InstantiationException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (IllegalAccessException e) {// TODO Auto-generated catch blocke.printStackTrace();}per.setName(Rollen);per.setAge(20);System.out.println(per);}
}【运行结果】
[Rollen 20]但是注意一下当我们把Person中的默认的无参构造函数取消的时候比如自己定义只定义一个有参数的构造函数之后会出现错误
比如我定义了一个构造函数
public Person(String name, int age) {this.ageage;this.namename;}然后继续运行上面的程序会出现
java.lang.InstantiationException: Reflect.Personat java.lang.Class.newInstance0(Class.java:340)at java.lang.Class.newInstance(Class.java:308)at Reflect.hello.main(hello.java:39)Exception in thread main java.lang.NullPointerExceptionat Reflect.hello.main(hello.java:47)所以大家以后再编写使用Class实例化其他类的对象的时候一定要自己定义无参的构造函数
【案例4】通过Class调用其他类中的构造函数 也可以通过这种方式通过Class创建其他类的对象
package Reflect;import java.lang.reflect.Constructor;class Person{public Person() {}public Person(String name){this.namename;}public Person(int age){this.ageage;}public Person(String name, int age) {this.ageage;this.namename;}public String getName() {return name;}public int getAge() {return age;}Overridepublic String toString(){return [this.name this.age];}private String name;private int age;
}class hello{public static void main(String[] args) {Class? demonull;try{demoClass.forName(Reflect.Person);}catch (Exception e) {e.printStackTrace();}Person per1null;Person per2null;Person per3null;Person per4null;//取得全部的构造函数Constructor? cons[]demo.getConstructors();try{per1(Person)cons[0].newInstance();per2(Person)cons[1].newInstance(Rollen);per3(Person)cons[2].newInstance(20);per4(Person)cons[3].newInstance(Rollen,20);}catch(Exception e){e.printStackTrace();}System.out.println(per1);System.out.println(per2);System.out.println(per3);System.out.println(per4);}
}【运行结果】
[null 0][Rollen 0][null 20][Rollen 20]【案例5】 返回一个类实现的接口
package Reflect;interface China{public static final String nameRollen;public static int age20;public void sayChina();public void sayHello(String name, int age);
}class Person implements China{public Person() {}public Person(String sex){this.sexsex;}public String getSex() {return sex;}public void setSex(String sex) {this.sex sex;}Overridepublic void sayChina(){System.out.println(hello ,china);}Overridepublic void sayHello(String name, int age){System.out.println(name age);}private String sex;
}class hello{public static void main(String[] args) {Class? demonull;try{demoClass.forName(Reflect.Person);}catch (Exception e) {e.printStackTrace();}//保存所有的接口Class? intes[]demo.getInterfaces();for (int i 0; i intes.length; i) {System.out.println(实现的接口 intes[i].getName());}}
}【运行结果】
实现的接口 Reflect.China注意以下几个例子都会用到这个例子的Person类所以为节省篇幅此处不再粘贴Person的代码部分只粘贴主类hello的代码
【案例6】取得其他类中的父类
class hello{public static void main(String[] args) {Class? demonull;try{demoClass.forName(Reflect.Person);}catch (Exception e) {e.printStackTrace();}//取得父类Class? tempdemo.getSuperclass();System.out.println(继承的父类为 temp.getName());}
}【运行结果】
继承的父类为 java.lang.Object【案例7】获得其他类中的全部构造函数
这个例子需要在程序开头添加import java.lang.reflect.*;
然后将主类编写为
class hello{public static void main(String[] args) {Class? demonull;try{demoClass.forName(Reflect.Person);}catch (Exception e) {e.printStackTrace();}Constructor?cons[]demo.getConstructors();for (int i 0; i cons.length; i) {System.out.println(构造方法 cons[i]);}}
}【运行结果】
构造方法 public Reflect.Person()构造方法 public Reflect.Person(java.lang.String)但是细心的读者会发现上面的构造函数没有public 或者private这一类的修饰符
【案例7】获取修饰符
class hello{public static void main(String[] args) {Class? demonull;try{demoClass.forName(Reflect.Person);}catch (Exception e) {e.printStackTrace();}Constructor?cons[]demo.getConstructors();for (int i 0; i cons.length; i) {Class? p[]cons[i].getParameterTypes();System.out.print(构造方法 );int mocons[i].getModifiers();System.out.print(Modifier.toString(mo) );System.out.print(cons[i].getName());System.out.print(();for(int j0;jp.length;j){System.out.print(p[j].getName() argi);if(jp.length-1){System.out.print(,);}}System.out.println(){});}}
}【运行结果】
构造方法 public Reflect.Person(){}构造方法 public Reflect.Person(java.lang.String arg1){}【案例8】取得其他类的全部属性
class hello {public static void main(String[] args) {Class? demo null;try {demo Class.forName(Reflect.Person);} catch (Exception e) {e.printStackTrace();}System.out.println(本类属性);// 取得本类的全部属性Field[] field demo.getDeclaredFields();for (int i 0; i field.length; i) {// 权限修饰符int mo field[i].getModifiers();String priv Modifier.toString(mo);// 属性类型Class? type field[i].getType();System.out.println(priv type.getName() field[i].getName() ;);}System.out.println(实现的接口或者父类的属性);// 取得实现的接口或者父类的属性Field[] filed1 demo.getFields();for (int j 0; j filed1.length; j) {// 权限修饰符int mo filed1[j].getModifiers();String priv Modifier.toString(mo);// 属性类型Class? type filed1[j].getType();System.out.println(priv type.getName() filed1[j].getName() ;);}}
}【运行结果】
本类属性private java.lang.String sex;实现的接口或者父类的属性public static final java.lang.String name;public static final int age;【案例9】通过反射调用其他类中的方法
class hello {public static void main(String[] args) {Class? demo null;try {demo Class.forName(Reflect.Person);} catch (Exception e) {e.printStackTrace();}try{//调用Person类中的sayChina方法Method methoddemo.getMethod(sayChina);method.invoke(demo.newInstance());//调用Person的sayHello方法methoddemo.getMethod(sayHello, String.class,int.class);method.invoke(demo.newInstance(),Rollen,20);}catch (Exception e) {e.printStackTrace();}}
}【运行结果】
hello ,chinaRollen 20【案例10】调用其他类的set和get方法
class hello {public static void main(String[] args) {Class? demo null;Object objnull;try {demo Class.forName(Reflect.Person);} catch (Exception e) {e.printStackTrace();}try{objdemo.newInstance();}catch (Exception e) {e.printStackTrace();}setter(obj,Sex,男,String.class);getter(obj,Sex);}/*** param obj* 操作的对象* param att* 操作的属性* */public static void getter(Object obj, String att) {try {Method method obj.getClass().getMethod(get att);System.out.println(method.invoke(obj));} catch (Exception e) {e.printStackTrace();}}/*** param obj* 操作的对象* param att* 操作的属性* param value* 设置的值* param type* 参数的属性* */public static void setter(Object obj, String att, Object value,Class? type) {try {Method method obj.getClass().getMethod(set att, type);method.invoke(obj, value);} catch (Exception e) {e.printStackTrace();}}
}// end class【运行结果】
男【案例11】通过反射操作属性
class hello {public static void main(String[] args) throws Exception {Class? demo null;Object obj null;demo Class.forName(Reflect.Person);obj demo.newInstance();Field field demo.getDeclaredField(sex);field.setAccessible(true);field.set(obj, 男);System.out.println(field.get(obj));}
}// end class【案例12】通过反射取得并修改数组的信息
import java.lang.reflect.*;
class hello{public static void main(String[] args) {int[] temp{1,2,3,4,5};Class?demotemp.getClass().getComponentType();System.out.println(数组类型 demo.getName());System.out.println(数组长度 Array.getLength(temp));System.out.println(数组的第一个元素: Array.get(temp, 0));Array.set(temp, 0, 100);System.out.println(修改之后数组第一个元素为 Array.get(temp, 0));}
}【运行结果】
数组类型 int数组长度 5数组的第一个元素: 1修改之后数组第一个元素为 100【案例13】通过反射修改数组大小
class hello{public static void main(String[] args) {int[] temp{1,2,3,4,5,6,7,8,9};int[] newTemp(int[])arrayInc(temp,15);print(newTemp);System.out.println();String[] atr{a,b,c};String[] str1(String[])arrayInc(atr,8);print(str1);}/*** 修改数组大小* */public static Object arrayInc(Object obj,int len){Class?arrobj.getClass().getComponentType();Object newArrArray.newInstance(arr, len);int coArray.getLength(obj);System.arraycopy(obj, 0, newArr, 0, co);return newArr;}/*** 打印* */public static void print(Object obj){Class?cobj.getClass();if(!c.isArray()){return;}System.out.println(数组长度为 Array.getLength(obj));for (int i 0; i Array.getLength(obj); i) {System.out.print(Array.get(obj, i) );}}
}【运行结果】
数组长度为 151 2 3 4 5 6 7 8 9 0 0 0 0 0 0 数组长度为 8a b c null null null null nullIOC与DI
概念
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OmVILX2F-1691076401278)(C:\Users\HUAWEI\AppData\Roaming\Typora\typora-user-images\image-20230103212325457.png)]
依赖Dependency就是有联系表示一个类依赖于另一个类
**依赖倒置原则DIP**设计模式六大原则之一是一种软件架构设计的原则抽象概念。
**控制反转IoC**一种反转流、依赖和接口的方式DIP的具体实现方式。
**依赖注入DI**实现IoC的一种实现方式用来反转依赖IoC的具体实现方式。
IoC容器依赖注入的框架用来映射依赖管理对象创建和生存周期DI框架。
依赖
依赖就是有联系有地方使用它就是有依赖它下面看一个简单的示例 代码示例
class BMW
{public string Show(){return 宝马;}
}
class ChinesePeople
{private BMW bmw new BMW();public void Run(){Console.WriteLine($今天开{bmw.Show()}上班);}
}
class Program
{static void Main(string[] args){ChinesePeople people new ChinesePeople();BMW bmw new BMW();people.Run();Console.Read();}
}上面中国人开着宝马去上班客户端有使用中国人、宝马汽车两个对象中国人中有使用对象宝马汽车我们可以从中找到三个依赖关系
客户端依赖对象ChinesePeople客户端依赖对象BMWChinesePeople依赖对象BMW
依赖倒置原则
过些日子来了新需求中国人不仅要开宝马去上班还要开奔驰去上班如果按照上面直接依赖关系的方式去做我们就需要修改ChinesePeople类让它实现一个参数为宝马的重载方法Run(),显然这样不是好的设计我们总不能每次新增一种汽车即修改下层模块都要去修改ChinesePeople类吧相对于汽车为上层模块太麻烦了。。。
先简单分析一下耦合关系就是依赖关系如果依赖关系很重牵一发而动全身将很难维护扩展耦合关系越少系统会越稳定因此要较少依赖
定义
A.高层模块不应依赖于底层模块两者应该依赖于抽象
B.抽象不应该依赖于细节细节应该依赖于抽象 在这个图中我们发现高层模块定义接口将不直接依赖于下层模块下层模块负责实现高层模块定义的接口下面看代码示例
interface ICar
{string Show();
}
class BMW:ICar
{public string Show(){return 宝马;}
}
class BenZ:ICar
{public string Show(){return 奔驰;}
}
interface IPeople
{void Run(ICar bmw);
}
class ChinesePeople:IPeople
{public void Run(ICar bmw){Console.WriteLine($今天开{bmw.Show()}上班);}
}
class Program
{static void Main(string[] args){ICar carBMW new BMW();ICar carBenZ new BenZ();IPeople people new ChinesePeople();people.Run(carBMW);people.Run(carBenZ);Console.Read();}
}输出结果
今天开宝马上班
今天开奔驰上班分析上面代码中ChinesePeople类不再依赖于具体的汽车而是依赖于汽车的抽象这样使得不管换什么样的汽车品牌中国人都是可以开着去上班的而且不需要修改ChinesePeople类。想一下这样是不是挺好的我们可以得出上层不再依赖细节相比面向实现面向接口较好因为抽象相比细节要更稳定。
依赖注入
上面说到的控制反转我们了解到是将控制权转移这是我们的目的配置文件反射是是一种实现而依赖注入则提供的是一种思想或者说是实现IOC的手段。
依赖注入是将对象的创建和绑定转移到被依赖对象的外部来实现。在依赖关系中ChinesePeople类所依赖的对象BMW类的创建和绑定是在ChinesePeople类内部执行的显然这种方法是不可取的那我们怎么BMW类的引用传递给ChinesePeople类呢
方法一 构造函数注入
interface ICar
{string Show();
}
class BMW:ICar
{public string Show(){return 宝马;}
}
class ChinesePeopleContructor
{private ICar _car;public ChinesePeopleContructor(ICar bmw){_car bmw;}public void Run(){Console.WriteLine($今天开{_car.Show()}上班);}
}
static void Main(string[] args)
{ICar car new BMW();ChinesePeopleContructor people new ChinesePeopleContructor(car);people.Run();Console.Read();
} 分析BMW类对象的创建和绑定转移到ChinesePeople类的外部来实现解除了两个对象之间的耦合当需要开奔驰去上班的时候只需要定义一个奔驰类外部重新绑定依赖不需要修改ChinesePeople类的内部即可是先中国人开奔驰去上班的需求
方法二 属性注入
interface ICar
{string Show();
}
class BMW:ICar
{public string Show(){return 宝马;}
}
class ChinesePeopleProperty
{private ICar _ICar;public ICar IC{get { return _ICar; }set { _ICar value; } }public void Run(){Console.WriteLine($今天开{_ICar.Show()}上班);}
}
static void Main(string[] args)
{ICar car new BMW();ChinesePeopleProperty people new ChinesePeopleProperty();people.IC car;people.Run();Console.Read();
} 分析属性注入是通过给属性赋值从而传递依赖
方法三 接口注入
interface ICar
{string Show();
}
class BMW:ICar
{public string Show(){return 宝马;}
}
interface IDependent
{void SetDependent(ICar icar);
}
class ChinesePeopleInterface : IDependent
{private ICar _ICar;public void SetDependent(ICar icar){_ICar icar;}public void Run(){Console.WriteLine($今天开{_ICar.Show()}上班);}
}
static void Main(string[] args)
{ ICar car new BMW();ChinesePeopleInterface people new ChinesePeopleInterface();people.SetDependent(car);people.Run();Console.Read();
} 分析接口依赖是定义一个设置依赖的方法然后被依赖类继承并实现这个接口
IOC是什么
**IOC—Inversion of Control即“控制反转”不是什么技术而是一种设计思想。**在Java开发中**IOC意味着将你设计好的对象交给容器控制而不是传统的在你的对象内部直接控制。**如何理解好Ioc呢理解好Ioc的关键是要明确“谁控制谁控制什么为何是反转有反转就应该有正转了哪些方面反转了”那我们来深入分析一下
●**谁控制谁控制什么**传统Java SE程序设计我们直接在对象内部通过new进行创建对象是程序主动去创建依赖对象而IOC是有专门一个容器来创建这些对象即由IOC容器来控制对 象的创建谁控制谁当然是IOC容器控制了对象控制什么那就是主要控制了外部资源获取不只是对象包括比如文件等。
●**为何是反转哪些方面反转了**有反转就有正转传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象也就是正转而反转则是由容器来帮忙创建及注入依赖对象为何是反转因为由容器帮我们查找及注入依赖对象对象只是被动的接受依赖对象所以是反转哪些方面反转了依赖对象的获取被反转了。
用图例说明一下传统程序设计如图1都是主动去创建相关对象然后再组合起来 (图1 传统应用程序示意图)
当有了IOC/DI的容器后在客户端类中不再主动去创建这些对象了如图2所示: (图2 有IoC/DI容器后程序结构示意图)
IOC能做什么
IOC不是一种技术只是一种思想一个重要的面向对象编程的法则它能指导我们如何设计出松耦合、更优良的程序。传统应用程序都是由我们在类内部主动创建依赖对象从而导致类与类之间高耦合难于测试有了IOC容器后把创建和查找依赖对象的控制权交给了容器由容器进行注入组合对象所以对象与对象之间是松散耦合这样也方便测试利于功能复用更重要的是使得程序的整个体系结构变得非常灵活。
其实IOC对编程带来的最大改变不是从代码上而是从思想上发生了“主从换位”的变化。应用程序原本是老大要获取什么资源都是主动出击但是在IOC/DI思想中应用程序就变成被动的了被动的等待IOC容器来创建并注入它所需要的资源了。
IOC很好的体现了面向对象设计法则之一—— 好莱坞法则“别找我们我们找你”即由IOC容器帮对象找相应的依赖对象并注入而不是由对象主动去找。
IOC和DI
DI—Dependency Injection即“依赖注入”组件之间依赖关系由容器在运行期决定形象的说即由容器动态的将某个依赖关系注入到组件之中。**依赖注入的目的并非为软件系统带来更多功能而是为了提升组件重用的频率并为系统搭建一个灵活、可扩展的平台。**通过依赖注入机制我们只需要通过简单的配置而无需任何代码就可指定目标需要的资源完成自身的业务逻辑而不需要关心具体的资源来自何处由谁实现。
理解DI的关键是“谁依赖谁为什么需要依赖谁注入谁注入了什么”那我们来深入分析一下
●谁依赖于谁当然是应用程序依赖于IOC容器
●**为什么需要依赖**应用程序需要IOC容器来提供对象需要的外部资源
●谁注入谁很明显是IOC容器注入应用程序某个对象应用程序依赖的对象
●注入了什么就是注入某个对象所需要的外部资源包括对象、资源、常量数据
IOC和DI由什么关系呢其实它们是同一个概念的不同角度描述由于控制反转概念比较含糊可能只是理解为容器控制对象这一个层面很难让人想到谁来维护对象关系所以2004年大师级人物Martin Fowler又给出了一个新的名字“依赖注入”相对IOC 而言“依赖注入”明确描述了“被注入对象依赖IOC容器配置依赖对象”。
IOC与DI浅显易懂的讲解
IoC(控制反转)
首先想说说IoCInversion of Control控制反转。这是spring的核心贯穿始终。**所谓IoC对于spring框架来说就是由spring来负责控制对象的生命周期和对象间的关系。**这是什么意思呢举个简单的例子我们是如何找女朋友的常见的情况是我们到处去看哪里有长得漂亮身材又好的mm然后打听她们的兴趣爱好、qq号、电话号、ip号、iq号………想办法认识她们投其所好送其所要这个过程是复杂深奥的我们必须自己设计和面对每个环节。传统的程序开发也是如此在一个对象中如果要使用另外的对象就必须得到它自己new一个或者从JNDI中查询一个使用完之后还要将对象销毁比如Connection等对象始终会和其他的接口或类藕合起来。
那么IoC是如何做的呢有点像通过婚介找女朋友在我和女朋友之间引入了一个第三者婚姻介绍所。婚介管理了很多男男女女的资料我可以向婚介提出一个列表告诉它我想找个什么样的女朋友比如长得像李嘉欣身材像林熙雷唱歌像周杰伦速度像卡洛斯技术像齐达内之类的然后婚介就会按照我们的要求提供一个mm我们只需要去和她谈恋爱、结婚就行了。简单明了如果婚介给我们的人选不符合要求我们就会抛出异常。整个过程不再由我自己控制而是有婚介这样一个类似容器的机构来控制。Spring所倡导的开发方式就是如此所有的类都会在spring容器中登记告诉spring你是个什么东西你需要什么东西然后spring会在系统运行到适当的时候把你要的东西主动给你同时也把你交给其他需要你的东西。所有的类的创建、销毁都由 spring来控制也就是说控制对象生存周期的不再是引用它的对象而是spring。对于某个具体的对象而言以前是它控制其他对象现在是所有对象都被spring控制所以这叫控制反转。
DI(依赖注入)
IoC的一个重点是在系统运行中动态的向某个对象提供它所需要的其他对象。这一点是通过DIDependency Injection依赖注入来实现的。比如对象A需要操作数据库以前我们总是要在A中自己编写代码来获得一个Connection对象有了 spring我们就只需要告诉springA中需要一个Connection至于这个Connection怎么构造何时构造A不需要知道。在系统运行时spring会在适当的时候制造一个Connection然后像打针一样注射到A当中这样就完成了对各个对象之间关系的控制。A需要依赖 Connection才能正常运行而这个Connection是由spring注入到A中的依赖注入的名字就这么来的。那么DI是如何实现的呢 Java 1.3之后一个重要特征是反射reflection它允许程序在运行的时候动态的生成对象、执行对象的方法、改变对象的属性spring就是通过反射来实现注入的。
理解了IoC和DI的概念后一切都将变得简单明了剩下的工作只是在spring的框架中堆积木而已。
我对IoC**(控制反转)和DI(依赖注入)**的理解
在平时的java应用开发中我们要实现某一个功能或者说是完成某个业务逻辑时至少需要两个或以上的对象来协作完成在没有使用Spring的时候每个对象在需要使用他的合作对象时自己均要使用像new object() 这样的语法来将合作对象创建出来这个合作对象是由自己主动创建出来的创建合作对象的主动权在自己手上自己需要哪个合作对象就主动去创建创建合作对象的主动权和创建时机是由自己把控的而这样就会使得对象间的耦合度高了A对象需要使用合作对象B来共同完成一件事A要使用B那么A就对B产生了依赖也就是A和B之间存在一种耦合关系并且是紧密耦合在一起而使用了Spring之后就不一样了创建合作对象B的工作是由Spring来做的Spring创建好B对象然后存储到一个容器里面当A对象需要使用B对象时Spring就从存放对象的那个容器里面取出A要使用的那个B对象然后交给A对象使用至于Spring是如何创建那个对象以及什么时候创建好对象的A对象不需要关心这些细节问题(你是什么时候生的怎么生出来的我可不关心能帮我干活就行)A得到Spring给我们的对象之后两个人一起协作完成要完成的工作即可。
所以控制反转IoC(Inversion of Control)是说创建对象的控制权进行转移以前创建对象的主动权和创建时机是由自己把控的而现在这种权力转移到第三方比如转移交给了IoC容器它就是一个专门用来创建对象的工厂你要什么对象它就给你什么对象有了 IoC容器依赖关系就变了原先的依赖关系就没了它们都依赖IoC容器了通过IoC容器来建立它们之间的关系。
这是我对Spring的IoC(控制反转)的理解。DI(依赖注入)其实就是IOC的另外一种说法DI是由Martin Fowler 在2004年初的一篇论文中首次提出的。他总结控制的什么被反转了就是获得依赖对象的方式反转了。
IOC的理论背景
我们知道在面向对象设计的软件系统中它的底层都是由N个对象构成的各个对象之间通过相互合作最终实现系统地业务逻辑 图1 软件系统中耦合的对象
如果我们打开机械式手表的后盖就会看到与上面类似的情形各个齿轮分别带动时针、分针和秒针顺时针旋转从而在表盘上产生正确的时间。图1中描述的就是这样的一个齿轮组它拥有多个独立的齿轮这些齿轮相互啮合在一起协同工作共同完成某项任务。我们可以看到在这样的齿轮组中如果有一个齿轮出了问题就可能会影响到整个齿轮组的正常运转。
齿轮组中齿轮之间的啮合关系,与软件系统中对象之间的耦合关系非常相似。对象之间的耦合关系是无法避免的也是必要的这是协同工作的基础。现在伴随着工业级应用的规模越来越庞大对象之间的依赖关系也越来越复杂经常会出现对象之间的多重依赖性关系因此架构师和设计师对于系统的分析和设计将面临更大的挑战。对象之间耦合度过高的系统必然会出现牵一发而动全身的情形。 (图2 对象之间的依赖关系)
耦合关系不仅会出现在对象与对象之间也会出现在软件系统的各模块之间以及软件系统和硬件系统之间。如何降低系统之间、模块之间和对象之间的耦合度是软件工程永远追求的目标之一。为了解决对象之间的耦合度过高的问题软件专家Michael Mattson 1996年提出了IOC理论用来实现对象之间的“解耦”目前这个理论已经被成功地应用到实践当中。
什么是IOC
IOC是Inversion of Control的缩写多数书籍翻译成“控制反转”。
1996年Michael Mattson在一篇有关探讨面向对象框架的文章中首先提出了IOC 这个概念。对于面向对象设计及编程的基本思想前面我们已经讲了很多了不再赘述简单来说就是把复杂系统分解成相互合作的对象这些对象类通过封装以后内部实现对外部是透明的从而降低了解决问题的复杂度而且可以灵活地被重用和扩展。
IOC理论提出的观点大体是这样的借助于“第三方”实现具有依赖关系的对象之间的解耦。
如下图 图3 IOC解耦过程
大家看到了吧由于引进了中间位置的“第三方”也就是IOC容器使得A、B、C、D这4个对象没有了耦合关系齿轮之间的传动全部依靠“第三方”了全部对象的控制权全部上缴给“第三方”IOC容器所以IOC容器成了整个系统的关键核心它起到了一种类似“粘合剂”的作用把系统中的所有对象粘合在一起发挥作用如果没有这个“粘合剂”对象与对象之间会彼此失去联系这就是有人把IOC容器比喻成“粘合剂”的由来。
我们再来做个试验把上图中间的IOC容器拿掉然后再来看看这套系统 图4 拿掉IOC容器后的系统
我们现在看到的画面就是我们要实现整个系统所需要完成的全部内容。这时候A、B、C、D这4个对象之间已经没有了耦合关系彼此毫无联系这样的话当你在实现A的时候根本无须再去考虑B、C和D了对象之间的依赖关系已经降低到了最低程度。所以如果真能实现IOC容器对于系统开发而言这将是一件多么美好的事情参与开发的每一成员只要实现自己的类就可以了跟别人没有任何关系
我们再来看看控制反转(IOC)到底为什么要起这么个名字我们来对比一下软件系统在没有引入IOC容器之前如图1所示对象A依赖于对象B那么对象A在初始化或者运行到某一点的时候自己必须主动去创建对象B或者使用已经创建的对象B。无论是创建还是使用对象B控制权都在自己手上。软件系统在引入IOC容器之后这种情形就完全改变了如图3所示由于IOC容器的加入对象A与对象B之间失去了直接联系所以当对象A运行到需要对象B的时候IOC容器会主动创建一个对象B注入到对象A需要的地方。通过前后的对比我们不难看出来对象A获得依赖对象B的过程,由主动行为变为了被动行为控制权颠倒过来了这就是“控制反转”这个名称的由来IOC也叫依赖注入(DI)
2004年Martin Fowler探讨了同一个问题**既然IOC是控制反转那么到底是“哪些方面的控制被反转了呢”经过详细地分析和论证后他得出了答案“获得依赖对象的过程被反转了”。**控制被反转之后获得依赖对象的过程由自身管理变为了由IOC容器主动注入。于是他给“控制反转”取了一个更合适的名字叫做“依赖注入Dependency Injection”。他的这个答案实际上给出了实现IOC的方法注入。所谓依赖注入就是由IOC容器在运行期间动态地将某种依赖关系注入到对象之中。
对象A依赖于对象B,当对象 A需要用到对象B的时候IOC容器就会立即创建一个对象B送给对象A。IOC容器就是一个对象制造工厂你需要什么它会给你送去你直接使用就行了而再也不用去关心你所用的东西是如何制成的也不用关心最后是怎么被销毁的这一切全部由IOC容器包办。 在传统的实现中由程序内部代码来控制组件之间的关系。我们经常使用new关键字来实现两个组件之间关系的组合这种实现方式会造成组件之间耦合。IOC很好地解决了该问题它将实现组件间关系从程序内部提到外部容器也就是说由容器在运行期将组件间的某种依赖关系动态注入组件中。
所以依赖注入(DI)和控制反转(IOC)是从不同的角度的描述的同一件事情就是指通过引入IOC容器利用依赖关系注入的方式实现对象之间的解耦。
IOC的优缺点
使用IOC框架产品能够给我们的开发过程带来很大的好处但是也要充分认识引入IOC框架的缺点做到心中有数杜绝滥用框架。
软件系统中由于引入了第三方IOC容器生成对象的步骤变得有些复杂本来是两者之间的事情又凭空多出一道手续所以我们在刚开始使用IOC框架的时候会感觉系统变得不太直观。所以引入了一个全新的框架就会增加团队成员学习和认识的培训成本并且在以后的运行维护中还得让新加入者具备同样的知识体系。由于IOC容器生成对象是通过反射方式在运行效率上有一定的损耗。如果你要追求运行效率的话就必须对此进行权衡。具体到IOC框架产品(比如Spring)来讲需要进行大量的配制工作比较繁琐对于一些小的项目而言客观上也可能加大一些工作成本。IOC框架产品本身的成熟度需要进行评估如果引入一个不成熟的IOC框架产品那么会影响到整个项目所以这也是一个隐性的风险。我们大体可以得出这样的结论一些工作量不大的项目或者产品不太适合使用IOC框架产品。另外如果团队成员的知识能力欠缺对于IOC框架产品缺乏深入的理解也不要贸然引入。最后特别强调运行效率的项目或者产品也不太适合引入IOC框架产品像WEB2.0网站就是这种情况。
IOC容器的技术剖析
IOC中最基本的技术就是“反射(Reflection)”编程目前.Net C#、Java和PHP5等语言均支持其中PHP5的技术书籍中有时候也被翻译成“映射”。有关反射的概念和用法大家应该都很清楚通俗来讲就是根据给出的类名字符串方式来动态地生成对象。这种编程方式可以让对象在生成时才决定到底是哪一种对象。反射的应用是很广泛的很多的成熟的框架比如象Java中的Hibernate、Spring框架.Net中 NHibernate、Spring.Net框架都是把“反射”做为最基本的技术手段。
IOC例子
第一步创建一个Maven项目
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HHuroPYt-1691076401285)(C:\Users\HUAWEI\AppData\Roaming\Typora\typora-user-images\image-20220518220028042.png)]
第二步创建一个类Hello
package beans;public class Hello {private String name;public void setName(String name) {this.name name;}public void show(){System.out.println(hello,name);}
}第三步创建配置文件beans.xml
?xml version1.0 encodingUTF-8?
beans xmlnshttp://www.springframework.org/schema/beansxmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd!-- bean就是java对象由spring容器来创建和管理 --bean namehello classbeans.Helloproperty namename value梁域强/property/bean
/beans第三步编写测试类Test
import beans.Hello;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;public class Test {public static void main(String[] args) {//解析beans.xml文件生成管理相应的bean对象ApplicationContext context new ClassPathXmlApplicationContext(Beans.xml);Hello hello (Hello) context.getBean(hello);hello.show();}
}输出结果
hello,梁域强Hello对象是谁创建的
我们在Hello类中添加一个构造函数可以确定Hello对象确定被创建
package beans;public class Hello {private String name;public Hello(){System.out.println(Hello,被创建了);}public void setName(String name) {this.name name;}public void show(){System.out.println(hello,name);}
}
运行Test测试类结果显示
hello 被创建
hello,张三由此可以得知Hello对象是由spring容器来创建的bean工厂可以包含多个bean,创建不同类的对象 Hello对象的属性是怎样设置的?
Hello对象的属性是由spring容器来设置的,这个过程就叫做控制反转
**控制的内容**指的是谁来控制对象的创建;传统的应用程序对象的创建是由程序本身来控制使用Spring以后是由spring来创建对象的。
**反转**有反转就有正转正转指程序来创建对象反转指程序本身不去创建对象而变为被动的接收容器给我们创建的对象
**总结**以前对象是由程序本身来创建使用spring后程序变为了被动接收spring创建好的对象;
控制反转有一个别名–依赖注入(DI-dependency injection)
**DI:**比如在我们的Hello类中我们的类Hello就依赖于name属性以来的这个name属性是由spring容器来设置的name值的设置过程就叫做依赖注入(通过setName方法进行的依赖注入)
**IOC:**是一种编程思想由主动编程变为别动接收;
IOC的实现是通过Ioc容器(Bean工厂)来实现的。IOC容器–BeanFactory 使用IOC来创建对象的方式3种方式
1)通过无参的构造方法来创建
User.java:
package cn.sxt.vo;public class User {public User(){System.out.println(user的无参构造方法);}private String name;public void setName(String name) {this.name name;}public void show(){System.out.println(namename);}
}beans.xml:
?xml version1.0 encodingUTF-8?
beans xmlnshttp://www.springframework.org/schema/beansxmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdbean iduser classcn.sxt.vo.Userproperty namename value张三/property/bean
/beansTest:
package cn.sxt.test;import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;import cn.sxt.vo.User;public class Test {public static void main(String[] args) {ApplicationContext acnew ClassPathXmlApplicationContext(beans.xml);User user(User)ac.getBean(user);user.show();}}2)通过有参构造方法来创建
User.java:
package cn.sxt.vo;public class User {private String name;public User(String name) {super();this.name name;}public void show(){System.out.println(namename);}
}beans.xml配置(有三种情况):
第一种:根据参数的下标(index)来设置
?xml version1.0 encodingUTF-8?
beans xmlnshttp://www.springframework.org/schema/beansxmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdbean iduser classcn.sxt.vo.User!-- index指的是构造方法参数下标从0开始 --constructor-arg index0 value李四/constructor-arg/bean
/beans第二种根据参数名称(name)来设置
bean iduser classcn.sxt.vo.User!-- name指的是属性值 --constructor-arg namename value王五/constructor-arg/bean第三种根据参数类型(type)来设置
bean iduser classcn.sxt.vo.Userconstructor-arg typejava.lang.String value徐六/constructor-arg
/bean3)通过工厂方法来创建对象(有两种); 第一种静态工厂来创建;
UserFactory.java:
package cn.sxt.factory;import cn.sxt.vo.User;public class UserFactory {public static User newInstance(String name){return new User(name);}
}beans.xml
bean iduser classcn.sxt.factory.UserFactory factory-methodnewInstanceconstructor-arg index0 value任七/constructor-arg
/bean第二种动态工厂来创建
UserDynamicFacory.java:
package cn.sxt.factory;import cn.sxt.vo.User;public class UserDynamicFactory {public User newInstance(String name){return new User(name);}
}beans.xml:
bean iduserFacotry classcn.sxt.factory.UserDynamicFactory/bean iduser factory-beanuserFacotry factory-methodnewInstanceconstructor-arg index0 value王五/
/beanSpring容器
创建容器
方式一类路径加载配置文件
ApplicationContext ctx new ClassPathXmlApplicationContext(applicationContext.xml);方式二文件路劲加载配置文件
ApplicationContext ctx new FileSystemXmlApplicationContext(D:\\applicationContext.xml);加载多个配置文件
ApplicationContext ctx new ClassPathXmlApplicationContext(bean1.xml,bean2.xml);获取Bean
方式一使用bean名称获取(主要使用这种方式)
BookDao bookDao (BookDao) ctx.getBean(bookDao);方式二使用bean名称获取并指定类型
BookDao bookDao ctx.getBean(bookDao,BookDao.class);方式三使用bean类型获取(要求容器中这个类型的bean只能有一个多的话会报错)
BookDao bookDao ctx.getBean(BookDao.class);BeanFactory初始化
类路径加载文件
Resource resource new ClassPathResource(applicationContext.xml);
BeanFactory bf new XmlBeanFactory(resource);
BookDao bookDao bf.getBean(bookBean,BookDao.class);
bookDao.save();BeanFactory创建完毕后所有的bean均为延迟加载
PS:Bean的延迟加载
使用ApplicationContext加载容器容器内的所有的bean都为立即加载
BookDaoImpl.java
public class BookDaoImpl implements BookDao {public BookDaoImpl() {System.out.println(构造方法运行...);}public void save() {System.out.println(book dao save ...);}
}
App.java
public class App {public static void main(String[] args) {ApplicationContext ctx new ClassPathXmlApplicationContext(applicationContext.xml);}
}运行App.java运行结果如下图所示
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TEDi4kyZ-1691076401286)(C:\Users\HUAWEI\AppData\Roaming\Typora\typora-user-images\image-20230105195640370.png)]
可以看出BookDaoImpl类中的构造方法被执行了尽管App.java中并没有获取bookDao的bean,这就是立即加载
如果要使ApplicationContext加载容器时容器内的所有的bean都为延迟加载的话就要修改applicationContext.xml文件,加上lazy-init“true”即可将立即加载改为延迟加载
applicationContext.xml
?xml version1.0 encodingUTF-8?
beans xmlnshttp://www.springframework.org/schema/beansxmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdbean idbookDao classBeanDemo.dao.impl.BookDaoImpl lazy-inittrue//beans使用BeanFactory加载容器容器内的所有的bean都为延迟加载
AppBeanFactory.java
public class AppBeanFactory {public static void main(String[] args) {Resource resource new ClassPathResource(applicationContext.xml);BeanFactory bf new XmlBeanFactory(resource);}
}运行AppBeanFactory.java运行结果如下图所示
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Gx6Tj9DM-1691076401287)(C:\Users\HUAWEI\AppData\Roaming\Typora\typora-user-images\image-20230105195914340.png)]
可以发现这一次BookDaoImpl类中的构造方法并没有执行这就是bean的延迟加载
容器类层次结构图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2lkpFRaM-1691076401287)(C:\Users\HUAWEI\AppData\Roaming\Typora\typora-user-images\image-20230105183333601.png)]
总结
1.容器相关
BeanFactory是IoC容器的顶层接口初始化BeanFactory对象时加载的bean延迟加载ApplicationContext接口是Spring容器的核心接口初始化时bean立即加载ApplicationContext接口提供基础的bean操作相关方法通过其他接口扩展其功能ApplicationContext接口常用初始化类
*ClassPathXmlApplicationContext
*FileSystemXmlApplicationContext
2.bean相关
beanidbookDao bean的Idnamedao bookDaoImpl daoImpl bean别名classcom.itheima.dao.impl.BookDaoImpl bean类型静态工厂类FactoryBean类scopesingleton 控制bean的实例数量init-methodinit 生命周期初始化方法destroy-methoddestory 生命周期销毁方法autowirebyType 自动装配类型factory-methodgetInstance bean工厂方法应用于静态工厂或实例工厂factory-beancom.itheima.factory.BookDaoFactory 实例beanlazy-inittrue 控制bean延迟加载
/3.依赖注入相关:
bean idbookService classcom,itheima.service.impl.BookServiceImplconstructor-arg namebookDao refbookDao/ 构造器注入引用类型constructor-arg nameuserDao refuserDao/constructor-arg namemsg valueWARN/ 构造器注入简单类型constructor-arg typejava.lang.String index3 valueWARN/ 类型匹配与索引匹配property namebookDao refbookDao/ setter注入引用类型property nameuserDao refuserDao/property namemsg valueWARN/ setter注入简单类型property namenames setter注入集合类型list list集合valueitcast/value 集合注入简单类型ref beandataSource/ 集合注入引用类型/list /property
/bean注解开发
使用Component定义bean
Component(bookDao)
public class BookDaoImpl implements BookDao {
}
Component
public class BookServiceImpl implements BookService {
}核心配置文件中通过组件扫描加载bean
context:component-scan base-packagecom.itheima/Spring提供Component注解的三个衍生注解
Controller: 用于表现层bean定义
Service:用于业务层bean定义
Repository:用于数据层bean定义
Repository(bookDao)
public class BookDaoImpl implements BookDao {
}
Service
public class BookServiceImpl implements BookService {
}纯注解开发
Spring3.0开启了纯注解开发模式使用Java类替代配置文件开启了Spring快速开发赛道Java类代替Spring核心配置文件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TxDzwrIF-1691076401288)(C:\Users\HUAWEI\AppData\Roaming\Typora\typora-user-images\image-20230105233550901.png)]
Configuration注解用于设定当前类为配置类ComponentScan注解用于设定扫描路径此注解只能添加一次多个数据请用数组格式
ComponentScan({com.itheima.service,com.itheima.dao])读取Spring核心配置文件初始化容器对象切换为读取Java配置类初始化容器对象
//加载配置文件初始化容器
ApplicationContext ctx new ClassPathXmlApplicationContext(applicationContext,xml);
//加载配置类初始化容器
ApplicationContext ctx new AnnotationConfigApplicationContext(SpringConfig.class);依赖注入
使用Autowired注解开启自动装配模式(按类型)
Service
public class BookServiceImpl implements BookService {Autowiredprivate BookDao bookDao;public void setBookDao(BookDao bookDao) {this.bookDao bookDao;}public void gave() {System,out.println(book service save ...);bookDao .save();}
} 注意:自动装配基于反射设计创建对象并暴力反射对应属性为私有属性初始化数据因此无需提供setter方法 注意:自动装配建议使用无参构造方法创建对象(默认)如果不提供对应构造方法请提供唯一的构造方法 使用Qualifier注解开启指定名称装配bean
Service
public class BookServiceImpl implements BookService {AutowiredQualifier(bookDao)private BookDao bookDao;
}注意: Qualifier注解无法单独使用必须配合Autowired注解使用 使用Value实现简单类型注入
Repository(bookDao)
public class BookDaoImpl implements BookDao {Value(100”)private String connectionNum;
}加载properties文件
使用PropertySource注解加载properties文件
Configuration
ComponentScan(com.itheima)
PropertySource(classpath:jdbc.properties)
public class SpringConfig {
}注意:路径仅支持单一文件配置多文件请使用数组格式配置不允许使用通配符*
XML配置比对注解配置
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yeCDezYG-1691076401289)(C:\Users\HUAWEI\AppData\Roaming\Typora\typora-user-images\image-20230105235049174.png)] 集合注入引用类型
# 注解开发- **使用Component定义bean**java
Component(bookDao)
public class BookDaoImpl implements BookDao {
}
Component
public class BookServiceImpl implements BookService {
}核心配置文件中通过组件扫描加载bean
context:component-scan base-packagecom.itheima/Spring提供Component注解的三个衍生注解
Controller: 用于表现层bean定义
Service:用于业务层bean定义
Repository:用于数据层bean定义
Repository(bookDao)
public class BookDaoImpl implements BookDao {
}
Service
public class BookServiceImpl implements BookService {
}纯注解开发
Spring3.0开启了纯注解开发模式使用Java类替代配置文件开启了Spring快速开发赛道Java类代替Spring核心配置文件
[外链图片转存中…(img-TxDzwrIF-1691076401288)]
Configuration注解用于设定当前类为配置类ComponentScan注解用于设定扫描路径此注解只能添加一次多个数据请用数组格式
ComponentScan({com.itheima.service,com.itheima.dao])读取Spring核心配置文件初始化容器对象切换为读取Java配置类初始化容器对象
//加载配置文件初始化容器
ApplicationContext ctx new ClassPathXmlApplicationContext(applicationContext,xml);
//加载配置类初始化容器
ApplicationContext ctx new AnnotationConfigApplicationContext(SpringConfig.class);依赖注入
使用Autowired注解开启自动装配模式(按类型)
Service
public class BookServiceImpl implements BookService {Autowiredprivate BookDao bookDao;public void setBookDao(BookDao bookDao) {this.bookDao bookDao;}public void gave() {System,out.println(book service save ...);bookDao .save();}
} 注意:自动装配基于反射设计创建对象并暴力反射对应属性为私有属性初始化数据因此无需提供setter方法 注意:自动装配建议使用无参构造方法创建对象(默认)如果不提供对应构造方法请提供唯一的构造方法 使用Qualifier注解开启指定名称装配bean
Service
public class BookServiceImpl implements BookService {AutowiredQualifier(bookDao)private BookDao bookDao;
}注意: Qualifier注解无法单独使用必须配合Autowired注解使用 使用Value实现简单类型注入
Repository(bookDao)
public class BookDaoImpl implements BookDao {Value(100”)private String connectionNum;
}加载properties文件
使用PropertySource注解加载properties文件
Configuration
ComponentScan(com.itheima)
PropertySource(classpath:jdbc.properties)
public class SpringConfig {
}注意:路径仅支持单一文件配置多文件请使用数组格式配置不允许使用通配符*
XML配置比对注解配置
[外链图片转存中…(img-yeCDezYG-1691076401289)]