详解Spring中涉及的技术

news2024/11/15 2:08:09

注解

介绍:

注解(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(fruitColor=Color.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(value="MrVk")
public class MyAnnotationTest {
    public static void main(String[] args) {
        /**
         * 用反射方式获得注解对应的实例对象后,在通过该对象调用属性对应的方法
         */
        MyAnnotation annotation = (MyAnnotation) MyAnnotationTest.class.getAnnotation(MyAnnotation.class);
        System.out.println(annotation.color());//输出color属性的默认值:blue
        System.out.println(annotation.value());

    }
}

为注解增加高级属性:

数组类型的属性:
  • 增加数组类型的属性:int[] arrayAttr() default {1,2,4};
  • 应用数组类型的属性:@MyAnnotation(arrayAttr={2,4,5})
  • 如果数组属性只有一个值,这时候属性值部分可以省略大括号,如:@MyAnnotation(arrayAttr=2),这就表示数组属性只有一个值,值为2
枚举类型的属性:
  • 增加枚举类型的属性:EumTrafficLamp lamp() default EumTrafficLamp.RED;
  • 应用枚举类型的属性:@MyAnnotation(lamp=EumTrafficLamp.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(color="red",value="xdp")"
     * 也可以这样使用:"@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(
        color="red",
        value="MrVk",
        arrayAttr={3,5,6},
        lamp=EumTrafficLamp.GREEN,
        annotationAttr=@MetaAnnotation("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属性的默认值:red
            System.out.println(annotation.value());//输出value属性的默认值:MrVk
            System.out.println(annotation.arrayAttr().length);//这里输出的数组属性的长度的结果为:3,数组属性有三个元素,因此数组的长度为3
            System.out.println(annotation.lamp());//这里输出的枚举属性值为:GREEN
            System.out.println(annotation.annotationAttr().value());//这里输出的注解属性值:gacl

            MetaAnnotation 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注解导图:

Java注解导图

反射

1.什么是反射?

​ **Java反射就是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;并且能改变它的属性。**而这也是Java被视为动态(或准动态,为啥要说是准动态,因为一般而言的动态语言定义是程序运行时,允许改变程序结构或变量类型,这种语言称为动态语言。从这个观点看,Perl,Python,Ruby是动态语言,C++,Java,C#不是动态语言。)语言的一个关键性质。

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 的方式得到,该方法最为安全可靠,程序性能更高
//  这说明任何一个类都有一个隐含的静态成员变量 class
  Class 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.类名.class
        Class cls2 = Person.class;
        System.out.println(cls2);
        //3.对象.getClass()
        Person p = new Person();
        Class cls3 = p.getClass();
        System.out.println(cls3);

        //==比较三个对象
        System.out.println(cls1==cls2); //true
        System.out.println(cls1==cls3); //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 属性赋值为 Bob,f2 属性即为 私有属性 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 {

    @Test
    public 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 demo=new 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<?> demo1=null;
        Class<?> demo2=null;
        Class<?> demo3=null;
        try{
            //一般尽量采用这种形式
            demo1=Class.forName("Reflect.Demo");
        }catch(Exception e){
            e.printStackTrace();
        }
        demo2=new Demo().getClass();
        demo3=Demo.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;
    }
    @Override
    public String toString(){
        return "["+this.name+"  "+this.age+"]";
    }
    private String name;
    private int age;
}
 
class hello{
    public static void main(String[] args) {
        Class<?> demo=null;
        try{
            demo=Class.forName("Reflect.Person");
        }catch (Exception e) {
            e.printStackTrace();
        }
        Person per=null;
        try {
            per=(Person)demo.newInstance();
        } catch (InstantiationException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        per.setName("Rollen");
        per.setAge(20);
        System.out.println(per);
    }
}

【运行结果】

[Rollen  20]

但是注意一下,当我们把Person中的默认的无参构造函数取消的时候,比如自己定义只定义一个有参数的构造函数之后,会出现错误:

比如我定义了一个构造函数:

public Person(String name, int age) {
        this.age=age;
        this.name=name;
    }

然后继续运行上面的程序,会出现:

java.lang.InstantiationException: Reflect.Person

    at 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.NullPointerException

    at 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.name=name;
    }
    public Person(int age){
        this.age=age;
    }
    public Person(String name, int age) {
        this.age=age;
        this.name=name;
    }
    public String getName() {
        return name;
    }
    public int getAge() {
        return age;
    }
    @Override
    public String toString(){
        return "["+this.name+"  "+this.age+"]";
    }
    private String name;
    private int age;
}
 
class hello{
    public static void main(String[] args) {
        Class<?> demo=null;
        try{
            demo=Class.forName("Reflect.Person");
        }catch (Exception e) {
            e.printStackTrace();
        }
        Person per1=null;
        Person per2=null;
        Person per3=null;
        Person per4=null;
        //取得全部的构造函数
        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 name="Rollen";
    public static  int age=20;
    public void sayChina();
    public void sayHello(String name, int age);
}
 
class Person implements China{
    public Person() {
         
    }
    public Person(String sex){
        this.sex=sex;
    }
    public String getSex() {
        return sex;
    }
    public void setSex(String sex) {
        this.sex = sex;
    }
    @Override
    public void sayChina(){
        System.out.println("hello ,china");
    }
    @Override
    public void sayHello(String name, int age){
        System.out.println(name+"  "+age);
    }
    private String sex;
}
 
class hello{
    public static void main(String[] args) {
        Class<?> demo=null;
        try{
            demo=Class.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<?> demo=null;
        try{
            demo=Class.forName("Reflect.Person");
        }catch (Exception e) {
            e.printStackTrace();
        }
        //取得父类
        Class<?> temp=demo.getSuperclass();
        System.out.println("继承的父类为:   "+temp.getName());
    }
}

【运行结果】

继承的父类为:   java.lang.Object

【案例7】:获得其他类中的全部构造函数

这个例子需要在程序开头添加import java.lang.reflect.*;

然后将主类编写为:

class hello{
    public static void main(String[] args) {
        Class<?> demo=null;
        try{
            demo=Class.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<?> demo=null;
        try{
            demo=Class.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 mo=cons[i].getModifiers();
            System.out.print(Modifier.toString(mo)+" ");
            System.out.print(cons[i].getName());
            System.out.print("(");
            for(int j=0;j<p.length;++j){
                System.out.print(p[j].getName()+" arg"+i);
                if(j<p.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 method=demo.getMethod("sayChina");
            method.invoke(demo.newInstance());
            //调用Person的sayHello方法
            method=demo.getMethod("sayHello", String.class,int.class);
            method.invoke(demo.newInstance(),"Rollen",20);
             
        }catch (Exception e) {
            e.printStackTrace();
        }
    }
}

【运行结果】

hello ,china

Rollen  20

【案例10】:调用其他类的set和get方法

class hello {
    public static void main(String[] args) {
        Class<?> demo = null;
        Object obj=null;
        try {
            demo = Class.forName("Reflect.Person");
        } catch (Exception e) {
            e.printStackTrace();
        }
        try{
         obj=demo.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<?>demo=temp.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<?>arr=obj.getClass().getComponentType();
        Object newArr=Array.newInstance(arr, len);
        int co=Array.getLength(obj);
        System.arraycopy(obj, 0, newArr, 0, co);
        return newArr;
    }
    /**
     * 打印
     * */
    public static void print(Object obj){
        Class<?>c=obj.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)+" ");
        }
    }
}

【运行结果】

数组长度为: 15

1 2 3 4 5 6 7 8 9 0 0 0 0 0 0 =====================

数组长度为: 8

a b c null null null null null

IOC与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框架)。

依赖

依赖就是有联系,有地方使用它就是有依赖它,下面看一个简单的示例

img

代码示例:

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
  • 客户端依赖对象BMW
  • ChinesePeople依赖对象BMW

依赖倒置原则

​ 过些日子来了新需求,中国人不仅要开宝马去上班,还要开奔驰去上班,如果按照上面直接依赖关系的方式去做,我们就需要修改ChinesePeople类,让它实现一个参数为宝马的重载方法Run(),显然这样不是好的设计,我们总不能每次新增一种汽车(即修改下层模块)都要去修改ChinesePeople类吧(相对于汽车为上层模块),太麻烦了。。。

​ 先简单分析一下,耦合关系就是依赖关系,如果依赖关系很重,牵一发而动全身,将很难维护扩展,耦合关系越少,系统会越稳定,因此要较少依赖

定义:

A.高层模块不应依赖于底层模块,两者应该依赖于抽象

B.抽象不应该依赖于细节,细节应该依赖于抽象

img

​ 在这个图中,我们发现高层模块定义接口,将不直接依赖于下层模块,下层模块负责实现高层模块定义的接口,下面看代码示例:

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,都是主动去创建相关对象然后再组合起来:

img

​ (图1 传统应用程序示意图)

​ 当有了IOC/DI的容器后,在客户端类中不再主动去创建这些对象了,如图2所示:

image-20220518223508305

​ (图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(控制反转)

首先想说说IoC(Inversion of Control,控制反转)。这是spring的核心,贯穿始终。**所谓IoC,对于spring框架来说,就是由spring来负责控制对象的生命周期和对象间的关系。**这是什么意思呢,举个简单的例子,我们是如何找女朋友的?常见的情况是,我们到处去看哪里有长得漂亮身材又好的mm,然后打听她们的兴趣爱好、qq号、电话号、ip号、iq号………,想办法认识她们,投其所好送其所要,这个过程是复杂深奥的,我们必须自己设计和面对每个环节。传统的程序开发也是如此,在一个对象中,如果要使用另外的对象,就必须得到它(自己new一个,或者从JNDI中查询一个),使用完之后还要将对象销毁(比如Connection等),对象始终会和其他的接口或类藕合起来。

那么IoC是如何做的呢?有点像通过婚介找女朋友,在我和女朋友之间引入了一个第三者:婚姻介绍所。婚介管理了很多男男女女的资料,我可以向婚介提出一个列表,告诉它我想找个什么样的女朋友,比如长得像李嘉欣,身材像林熙雷,唱歌像周杰伦,速度像卡洛斯,技术像齐达内之类的,然后婚介就会按照我们的要求,提供一个mm,我们只需要去和她谈恋爱、结婚就行了。简单明了,如果婚介给我们的人选不符合要求,我们就会抛出异常。整个过程不再由我自己控制,而是有婚介这样一个类似容器的机构来控制。Spring所倡导的开发方式就是如此,所有的类都会在spring容器中登记,告诉spring你是个什么东西,你需要什么东西,然后spring会在系统运行到适当的时候,把你要的东西主动给你,同时也把你交给其他需要你的东西。所有的类的创建、销毁都由 spring来控制,也就是说控制对象生存周期的不再是引用它的对象,而是spring。对于某个具体的对象而言,以前是它控制其他对象,现在是所有对象都被spring控制,所以这叫控制反转。

DI(依赖注入)

IoC的一个重点是在系统运行中,动态的向某个对象提供它所需要的其他对象。这一点是通过DI(Dependency Injection,依赖注入)来实现的。比如对象A需要操作数据库,以前我们总是要在A中自己编写代码来获得一个Connection对象,有了 spring我们就只需要告诉spring,A中需要一个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个对象构成的,各个对象之间通过相互合作,最终实现系统地业务逻辑

img

​ (图1 软件系统中耦合的对象)

​ 如果我们打开机械式手表的后盖,就会看到与上面类似的情形,各个齿轮分别带动时针、分针和秒针顺时针旋转,从而在表盘上产生正确的时间。图1中描述的就是这样的一个齿轮组,它拥有多个独立的齿轮,这些齿轮相互啮合在一起,协同工作,共同完成某项任务。我们可以看到,在这样的齿轮组中,如果有一个齿轮出了问题,就可能会影响到整个齿轮组的正常运转。

齿轮组中齿轮之间的啮合关系,与软件系统中对象之间的耦合关系非常相似。对象之间的耦合关系是无法避免的,也是必要的,这是协同工作的基础。现在,伴随着工业级应用的规模越来越庞大,对象之间的依赖关系也越来越复杂,经常会出现对象之间的多重依赖性关系,因此,架构师和设计师对于系统的分析和设计,将面临更大的挑战。对象之间耦合度过高的系统,必然会出现牵一发而动全身的情形。

img

​ (图2 对象之间的依赖关系)

​ 耦合关系不仅会出现在对象与对象之间,也会出现在软件系统的各模块之间,以及软件系统和硬件系统之间。如何降低系统之间、模块之间和对象之间的耦合度,是软件工程永远追求的目标之一。为了解决对象之间的耦合度过高的问题,软件专家Michael Mattson 1996年提出了IOC理论,用来实现对象之间的“解耦”,目前这个理论已经被成功地应用到实践当中。

什么是IOC

​ IOC是Inversion of Control的缩写,多数书籍翻译成“控制反转”。

1996年,Michael Mattson在一篇有关探讨面向对象框架的文章中,首先提出了IOC 这个概念。对于面向对象设计及编程的基本思想,前面我们已经讲了很多了,不再赘述,简单来说就是把复杂系统分解成相互合作的对象,这些对象类通过封装以后,内部实现对外部是透明的,从而降低了解决问题的复杂度,而且可以灵活地被重用和扩展。

IOC理论提出的观点大体是这样的:借助于“第三方”实现具有依赖关系的对象之间的解耦。

​ 如下图:

img

​ (图3 IOC解耦过程)

​ 大家看到了吧,由于引进了中间位置的“第三方”,也就是IOC容器,使得A、B、C、D这4个对象没有了耦合关系,齿轮之间的传动全部依靠“第三方”了,全部对象的控制权全部上缴给“第三方”IOC容器,所以,IOC容器成了整个系统的关键核心,它起到了一种类似“粘合剂”的作用,把系统中的所有对象粘合在一起发挥作用,如果没有这个“粘合剂”,对象与对象之间会彼此失去联系,这就是有人把IOC容器比喻成“粘合剂”的由来。

我们再来做个试验:把上图中间的IOC容器拿掉,然后再来看看这套系统:

img

​ (图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框架的缺点,做到心中有数,杜绝滥用框架。

  1. 软件系统中由于引入了第三方IOC容器,生成对象的步骤变得有些复杂,本来是两者之间的事情,又凭空多出一道手续,所以,我们在刚开始使用IOC框架的时候,会感觉系统变得不太直观。所以,引入了一个全新的框架,就会增加团队成员学习和认识的培训成本,并且在以后的运行维护中,还得让新加入者具备同样的知识体系。
  2. 由于IOC容器生成对象是通过反射方式,在运行效率上有一定的损耗。如果你要追求运行效率的话,就必须对此进行权衡。
  3. 具体到IOC框架产品(比如:Spring)来讲,需要进行大量的配制工作,比较繁琐,对于一些小的项目而言,客观上也可能加大一些工作成本。
  4. IOC框架产品本身的成熟度需要进行评估,如果引入一个不成熟的IOC框架产品,那么会影响到整个项目,所以这也是一个隐性的风险。
  5. 我们大体可以得出这样的结论:一些工作量不大的项目或者产品,不太适合使用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 version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    	<!-- bean就是java对象,由spring容器来创建和管理 -->
        <bean name="hello" class="beans.Hello">
            <property name="name" 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("name="+name);
    }
}

beans.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">
   <bean id="user" class="cn.sxt.vo.User">
           <property name="name" value="张三"></property>
   </bean>
</beans>

Test:

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 ac=new 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("name="+name);
    }
}

beans.xml配置(有三种情况):

第一种:根据参数的下标(index)来设置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">
   <bean id="user" class="cn.sxt.vo.User">
           <!-- index指的是构造方法参数下标,从0开始 -->
           <constructor-arg index="0" value="李四"></constructor-arg>
   </bean>
</beans>

第二种:根据参数名称(name)来设置

<bean id="user" class="cn.sxt.vo.User">
           <!-- name指的是属性值 -->
           <constructor-arg name="name" value="王五"></constructor-arg>
   </bean>

第三种:根据参数类型(type)来设置

<bean id="user" class="cn.sxt.vo.User">
           <constructor-arg type="java.lang.String" value="徐六"></constructor-arg>
</bean>

3)通过工厂方法来创建对象(有两种);
第一种:静态工厂来创建;

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 id="user" class="cn.sxt.factory.UserFactory" factory-method="newInstance">
           <constructor-arg index="0" 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 id="userFacotry" class="cn.sxt.factory.UserDynamicFactory"/>
    <bean id="user" factory-bean="userFacotry" factory-method="newInstance">
        <constructor-arg index="0" value="王五"/>
</bean>

Spring容器

创建容器

  • 方式一:类路径加载配置文件
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 version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="bookDao" class="BeanDemo.dao.impl.BookDaoImpl" lazy-init="true"/>

</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相关:

<bean
      id="bookDao"										bean的Id
      name="dao bookDaoImpl daoImpl"					bean别名
      class="com.itheima.dao.impl.BookDaoImpl"			bean类型,静态工厂类,FactoryBean类
      scope="singleton"									控制bean的实例数量
      init-method="init"								生命周期初始化方法
      destroy-method="destory"							生命周期销毁方法
      autowire="byType"									自动装配类型
      factory-method="getInstance"						bean工厂方法,应用于静态工厂或实例工厂
      factory-bean="com.itheima.factory.BookDaoFactory"	实例bean
      lazy-init="true"									控制bean延迟加载
/>

3.依赖注入相关:

<bean id="bookService" class="com,itheima.service.impl.BookServiceImpl">
    <constructor-arg name="bookDao" ref="bookDao"/>			构造器注入引用类型
	<constructor-arg name="userDao" ref="userDao"/>
	<constructor-arg name="msg" value="WARN"/>				构造器注入简单类型
    <constructor-arg type="java.lang.String" index="3" value="WARN"/>	类型匹配与索引匹配
    <property name="bookDao" ref="bookDao"/>			setter注入引用类型
    <property name="userDao" ref="userDao"/>
	<property name="msg" value="WARN"/>					setter注入简单类型
	<property name="names">								setter注入集合类型
		<list>											list集合
			<value>itcast</value>						集合注入简单类型
			<ref bean="dataSource"/>					集合注入引用类型
        </list> 
    </property>
</bean>

注解开发

  • 使用@Component定义bean
@Component("bookDao")
public class BookDaoImpl implements BookDao {
}
@Component
public class BookServiceImpl implements BookService {
}
  • 核心配置文件中通过组件扫描加载bean
<context:component-scan base-package="com.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 {
    @Autowired
	private 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 {
    @Autowired
	@Qualifier("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-package="com.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 {
    @Autowired
	private 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 {
    @Autowired
	@Qualifier("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)]

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/834970.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Matlab滤波、频谱分析

Matlab滤波、频谱分析 滤波&#xff1a; 某目标信号是由5、15、30Hz正弦波混合而成的混合信号&#xff0c;现需要设计一个滤波器滤掉5、30Hz两种频率。 分析&#xff1a;显然我们应该设计一个带通滤波器&#xff0c;通带频率落在15Hz附近。 % 滤波 % 某目标信号是由5、15、3…

Python(六十四)字典元素的遍历

❤️ 专栏简介&#xff1a;本专栏记录了我个人从零开始学习Python编程的过程。在这个专栏中&#xff0c;我将分享我在学习Python的过程中的学习笔记、学习路线以及各个知识点。 ☀️ 专栏适用人群 &#xff1a;本专栏适用于希望学习Python编程的初学者和有一定编程基础的人。无…

Cesium 加载ArcGIS Server切片服务错级问题

1.首先上官方api说明 ArcGisMapServerImageryProvider - Cesium Documentation 里面没有 zoomoffset参数!!! 2.如果按照互联网栅格切片规则 3857、4326、4490常用切片层级参数,则直接加载显示地图 viewer.imageryLayers.addImageryProvider(new Cesium.ArcGisMapServerI…

购买阿里云vod视频点播服务流程

引言 在当前数字化时代&#xff0c;视频内容的传播越来越重要&#xff0c;而阿里云视频点播服务作为一种强大的视频存储和分发平台&#xff0c;受到越来越多企业和个人的青睐。但是&#xff0c;对于初次接触阿里云视频点播服务的用户来说&#xff0c;购买流程可能会让人有些困…

“东快西慢”格局被重塑 西安智能网联产业发展明显提速

近年来&#xff0c;全球汽车产业迎来新一轮的变革——智能化。智能化变革可谓是对全球汽车产业的再次重塑&#xff0c;这场变革不仅带来了动力及驱动能源和驾驶方式的转变&#xff0c;还使得汽车工业转向新兴市场&#xff0c;中国成为智能网联汽车产业发展的新高地。 在智能网…

企业架构NOSQL数据库之MongoDB

目录 一、背景描述及其方案设计 (一)业务背景描述 &#xff08;二&#xff09;模拟运维设计方案 二、Mongodb介绍 &#xff08;一&#xff09;nosql介绍 &#xff08;二&#xff09;产品特点 1、存储性 2、 效率性 3、结构 三、安装和配置 &#xff08;一&#xff09…

jmeter 5.1彻底解决中文上传乱码

1.修改源码,然后重新打jar包,就是所有上传文件名重新获取文件名 参考链接:多种Jmeter中文乱码问题处理方法 - 51Testing软件测试网 2.修改Advanced,必须选java

电商系统架构设计系列(七):如何构建一个电商的商品搜索系统?

上篇文章中&#xff0c;我给你留了一个思考题&#xff1a;如何构建一个商品搜索系统&#xff1f; 今天这篇文章&#xff0c;我们来说一下电商的商品搜索系统。 引言 搜索这个特性可以说是无处不在&#xff0c;现在很少有网站或者系统不提供搜索功能了&#xff0c;所以&#xf…

Unity Shader:常用的C#与shader交互的方法

俗话说久病成医&#xff0c;虽然不是专业技术美术&#xff0c;但代码写久了自然会积累一些常用的shader交互方法。零零散散的&#xff0c;总结如下&#xff1a; 1&#xff0c;改变UGUI的材质球属性 有时候我们需要改变ui的一些属性&#xff0c;从而实现想要的效果。通常UGUI上…

GEE:矢量数据去除重复值(输出样本点数据的标签信息)

作者:CSDN @ _养乐多_ 本文记录了在GoogleEarthEngine(GEE)平台上,将样本点数据中某个字段的值去除重复值,并将剩下的值打印到控制台的代码。该代码可以用于快速在GEE平台上查询土地利用分类信息中landcover的类别信息。 矢量数据信息如下所示, 打印结果如下所示, 文章…

【Linux】【docker】安装sonarQube免费社区版9.9

文章目录 sonarQube 镜像容器Linux 安装镜像出现 Permission denied的异常安装sonarQube 中文包重启服务 代码上传到sonarQube扫描配置 JS TS Php Go Python sonarQube 镜像容器 老样子第一步还是打开镜像容器官网https://hub.docker.com搜索sonarqube官方推荐的挂载目录 我就按…

LeetCode 热题 100 JavaScript --226. 翻转二叉树

给你一棵二叉树的根节点 root &#xff0c;翻转这棵二叉树&#xff0c;并返回其根节点。 示例 3&#xff1a; 输入&#xff1a;root [] 输出&#xff1a;[] 提示&#xff1a; 树中节点数目范围在 [0, 100] 内 -100 < Node.val < 100 var invertTree function(root…

使用“纯”Servlet做一个单表的CRUD操作

1. 项目说明 介绍&#xff1a; 这里我们使用 纯粹 的 Servlet 完成单表【对部门的】的增删改查操作。&#xff08;B/S结构的。&#xff09; 结构图 初始的欢迎页面 部门列表页面 部门详情 修改部门 删除部门&#xff1a; 新增部门&#xff1a; 2. 具体对应的功能的代码实现 …

为什么马斯克和奥特曼都想重振加密货币?

1、前言 加密货币已经死了吗&#xff1f;这个问题的答案取决于谁来回答。一个加密爱好者会给你一百个不同的理由来解释为什么加密货币没有死。特斯拉CEO埃隆马斯克和OpenAI CEO 山姆奥特曼都对加密货币及其在塑造未来世界中的潜在作用有着浓厚的兴趣。 在过去很长一段时间里&…

Ubuntu18.04 安装opencv 4.8.0教程

1. 安装准备 安装前需要下载一些必须的依赖项。 不同版本opencv依赖会有不同&#xff0c;具体见官网opencv安装 sudo apt-get install build-essential sudo apt-get install cmake git libgtk2.0-dev pkg-config libavcodec-dev libavformat-dev libswscale-dev sudo apt-…

视频汇聚平台EasyCVR视频广场侧边栏支持拖拽

为了提升用户体验以及让平台的操作更加符合用户使用习惯&#xff0c;我们在EasyCVR v3.3版本中&#xff0c;支持面包屑侧边栏的广场视频、分组列表、收藏这三个模块拖拽排序&#xff0c;并且该操作在视频广场、视频调阅、电子地图、录像回放等页面均能支持。 TSINGSEE青犀视频…

InfluxDB2如何求增量数据

需求 项目中需要接入电表设备&#xff0c;求用电量。 按天和设备统计用电量 按天统计用电量 统计总用电量 存在的问题 difference 函数可以求增量&#xff0c;但是以上计算均存在一个问题&#xff0c;比如xx设备有8.1号和8.2号的数据&#xff0c;我统计每天的用电量&#xf…

单篇笔记曝光248万+,素颜、寸头…小红书女性种草新趋势分析!

最近&#xff0c;小红书上刮起一阵素颜、寸头&#xff0c;拒绝美丽绑架的风潮&#xff0c;他们称之为“脱美役”&#xff0c;即脱离美丽枷锁&#xff0c;做自己&#xff0c;接纳原本的自己。这是女性觉醒的又一阵风&#xff0c;品牌要如何跟上这波种草新趋势呢&#xff1f; 单篇…

Swish for MacBook触控板窗口管理软件

Swish可以帮助您使用触控板&#xff0c;轻松对mac窗口进行管理&#xff0c;只需提前设置好预定的设置即可&#xff0c;非常方便&#xff01; 几乎所有的窗口管理工具用的都是快捷键或者鼠标拖移的方式来管理窗口&#xff0c;Swish 却另辟蹊径&#xff0c;为窗口管理引入了手势…

c语言——计算两个数值的最小公倍数

//计算两个数值的最小公倍数 //列如&#xff1a;4和6的最小公倍数是12. #include<stdio.h> int main() {int a,b,temp,i;printf("Input a&b:");scanf("%d,%d",&a,&b);if(a<b){tempa;ab;btemp;}for(ia;i>0;i)if(i%a0&&i%b0…