三、SpringIOC_19(笔记)

文章目录

Spring IOC

一、简介

Spring 是一个开源免费的框架,为了解决企业应用开发的复杂性而创建。Spring 框架是一个轻量级的解决方案,可以一站式地构建企业级应用。

image-20230926102340201

Spring 是模块化的,所以可以只使用其中需要的部分。可以在任何 web 框架上使用控制反转(IoC),也可以只使用 Hibernate 集成代码或 JDBC 抽象层。它支持声明式事务管理、通过RMI 或 web 服务实现远程访问,并可以使用多种方式持久化数据。它提供了功能全面的 MVC 框架,可以透明地集成 AOP 到软件中。

Spring 被设计为非侵入式的,这意味着你的域逻辑代码通常不会依赖于框架本身。在集成层(比如数据访问层),会存在一些依赖同时依赖于数据访问技术和 Spring,但是这些依赖可以很容易地从代码库中分离出来。

Spring 框架是基于 Java 平台的,它为开发 Java 应用提供了全方位的基础设施支持,并且它很好地处理了这些基础设施,所以你只需要关注你的应用本身即可。Spring 可以使用 POJO(普通的 Java 对象,plain old java objects)创建应用,并且可以将企业服务非侵入式地应用到 POJO。这项功能适用于 Java SE 编程模型以及全部或部分的 Java EE。

二、体系结构

image-20230926102458759

1、Core Container 核心容器

Spring 的核心容器是其他模块的基础,由 Beans 模块、Core 核心模块、Context 上下文模块和 Expression Language 表达式语言四个部分组成。

Beans 模块:提供了 BeanFactory,用于创建和管理对象,Spring 将产生出来的对象称为 BeanCore 核心模块:提供了 Spring 框架的基本组成部分,包括 IoCDI 功能。
Context 上下文模块:建立在 Core 核心和 Beans 模块的基础之上,它是访问定义和配置任何对象的媒介。ApplicationContext 接口是上下文模块的焦点。
Expression Language 模块:是运行时查询和操作对象图的强大的表达式语言。 

2、Data Access 模块

Data Access/Integration包括 JDBC、ORM、OXM、JMS 和 Transactions 五个模块,具体介绍如下。

JDBC 模块:提供了一个 JDBC 的抽象层,大幅度减少了在开发过程中对数据库操作的编码。
ORM 模块:对流行的 ORM框架管理的 API ,也是我们常用的组件之一,包括 JPAJDOHibernate 和 iBatis 提供了的集成层。
OXM 模块:提供了一个支持对象/XML 映射的抽象层实现,如 JAXBCastorXMLBeansJiBXXStreamJMS 模块:指 Java 消息服务,包含的功能为生产和消费的信息。
Transactions 事务模块:支持编程和声明式事务管理实现特殊接口类,并为所有的 POJO

3、Web 模块

Spring 的 Web 层包括 WebSocket、Servlet、Web 和 Portlet 组件,具体介绍如下。

WebSocket 模块:提供了客户端和服务器之间的一个持久链接,管理之间数据的发送。
Servlet模块:包括 Spring 模型—视图—控制器(MVC)实现 Web 应用程序。
Web 模块:提供了基本的 Web 开发集成特性,例如多文件上传功能、使用的 Servlet 监听器的 IoC 容器初始化以及 Web 应用上下文。
Portlet 模块:提供了在 Portlet 环境中使用 MVC 的实现,类似 Web-Servlet 模块的功能。

4、其他模块

Spring的其他模块还有 AOP、Aspects、Instrumentation 以及 Test 模块,具体介绍如下:

AOP 模块:提供了面向切面编程实现,允许定义方法拦截器和切入点,将代码按照功能进行分离,以降低耦合性。
Aspects 模块:提供与 AspectJ 的集成,是一个功能强大且成熟的面向切面编程(AOP)框架。
Instrumentation 模块:提供了类工具的支持和类加载器的实现,可以在特定的应用服务器中使用。
Test 模块:支持 Spring 组件,使用 JUnitTestNG 框架的测试。

二、特点

方便解耦,简化开发

通过 Spring 提供的 IoC 容器,我们可以将对象之间的依赖关系交由 Spring 进行控制,避免硬编码所造成的过度程序耦合。有了 Spring,用户不必再为单实例模式类、属性文件解析等这些很底层的需求编写代码,可以更专注于上层的应用。

AOP 编程的支持

通过 Spring 提供的 AOP 功能,方便进行面向切面的编程,许多不容易用传统 OOP 实现的功能可以通过 AOP 轻松应付。

声明事物的支持

在 Spring 中,我们可以从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活地进行事务的管理,提高开发效率和质量。

方便程序的测试

可以用非容器依赖的编程方式进行几乎所有的测试工作,在 Spring 里,测试不再是昂贵的操作,而是随手可做的事情。例如:Spring 对 Junit4 支持,可以通过注解方便的测试 Spring 程序。

方便集成各种优秀框架

Spring 不排斥各种优秀的开源框架,相反,Spring 可以降低各种框架的使用难度,Spring 提供了对各种优秀框架(如 Struts,Hibernate、Hessian、Quartz)等的直接支持。

降低 Java EE API 的使用难度

Spring 对很多难用的 Java EE API(如 JDBC,JavaMail,远程调用等)提供了一个薄薄的封装层,通过 Spring 的简易封装,这些 Java EE API 的使用难度大为降低。

Java 源码是经典学习范例

Spring 的源码设计精妙、结构清晰、匠心独用,处处体现着大师对Java设计模式灵活运用以及对 Java 技术的高深造诣。Spring 框架源码无疑是 Java 技术的最佳实践范例。如果想在短时间内迅速提高自己的 Java 技术水平和应用开发水平,学习和研究 Spring 源码将会使你收到意想不到的效果。

三、IOC 概述

控制反转 IoC(Inversion of Control):是一种设计思想,DI (依赖注入)是实现 IoC 的一种方法,也有人认为 DI 只是 IoC 的另一种说法。没有 IoC 的程序中我们使用面向对象编程对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制,控制反转后将对象的创建转移给第三方。

image-20230926103641154

**IoC 是指容器控制程序对象之间的关系,而不是传统实现中,由程序代码直接操控。**控制权由应用代码中转到了外部容器,控制权的转移就是所谓反转。对于 Spring 而言,就是由 Spring 来控制对象的生命周期和对象之间的依赖关系。IoC 还有另外一个名字——“依赖注入(Dependency Injection)”。从名字上理解,所谓依赖注入,即组件之间的依赖关系由容器在运行期决定,即由容器动态地将某种依赖关系注入到组件之中。

在 Spring 的工作方式中,所有的类都会在 spring 容器中登记,告诉 spring 这是个什么东西,你需要什么东西,然后 spring 会在系统运行到适当的时候,把你要的东西主动给你,同时也把你交给其他需要你的东西。所有的类的创建、销毁都由 spring 来控制,也就是说控制对象生存周期的不再是引用它的对象,而是 spring。对于某个具体的对象而言,以前是它控制其他对象,现在是所有对象都被 spring 控制,所以这叫控制反转。

在系统运行中,动态的向某个对象提供它所需要的其他对象。

依赖注入的思想是通过反射机制实现的,在实例化一个类时,它通过反射调用类中 set 方法将事先保存在 HashMap 中的类属性注入到类中。 总而言之,在传统的对象创建方式中,通常由调用者来创建被调用者的实例,而在 Spring 中创建被调用者的工作由 Spring 来完成,然后注入调用者,即所谓的依赖注入 or 控制反转。

IoC 的优点:降低了组件之间的耦合,降低了业务对象之间替换的复杂性,使之能够灵活的管理对象。

四、IOC 容器

面向对象最基本的就是对象,我们对于对象的使用都是从 new 开始的。

1、传统方式

我们需要用到那个类的对象,只需要 new 它就可以产生该类的对象了,然在在通过该对象调用它的属性和方法。例如:

Person guan1 = new Person();
Person guan2 = new Person(1,"关为","程序猿");

image-20230926110611461

类和类之间都是自行产生其他类的对象来进行使用,好处是,使用简单,需要的时候 new 就行,但是缺点就有很多,我用到哪些类都需要自身去创建,依赖度太高,不利于解耦,也有可能多个类都使用一个类的对象,但要产生多个对象,不能“共享”,冗余过多。

就类似关老师是一个快递人员,给同学们送快递,每一个快递都需要给每一个人打电话,等你们全部拿完快递后,我才能走。太麻烦了,为什么我不放到 CaiNiao 驿站呢?

2、IoC 方式

所有对象的产生都交给了容器来实现,当你需要对象时,在通过容器获取它就行。

image-20230926110650380

在启动 Spring 时读取 applicationContext.xml 文件,将文件中需要产生的对象都产生出来并存放到 Spring IOC 容器中,当需要使用这个对象时,从容器中将它获取出来就行。

3、IoC 容器的规范

IoC 容器有两种规范:BeanFactory 和 ApplicationContext。

  • BeanFactory:BeanFactory 为 IOC 容器提供了基础功能,Spring 文档中提到,当前该类仅仅是为了向后兼容老的版本,除非你有更好的原因否则就应该使用第二种容器。
  • ApplicationContext:通过 API 文档可以知道,ApplicationContext 是BeanFactory 的子接口,并且从文档中也可以看到 ApplicaionContext 除了包含有 BeanFactory 的所有功能还支持了更多的功能。

BeanFactory 和 ApplicationContext 多是接口,都是对 IoC 容器提出了规范,对于它们而言是通过对应的实现类去实现,分别有四种实现方式。

1、FileSystemXmlApplicationContext 实现

加载配置文件的时候采用的是项目的路径。过去比较喜欢使用这种方式,以项目路径为相对路径起始点。(适用于非Maven项目)

ApplicationContext bean = new FileSystemXmlApplicationContext("ioc/src/main/resources/applicationContext.xml");
2、ClassPathXmlApplicationContext 实现

加载配置文件的时候根据 ClassPath 位置。maven 项目的标准规范,以 resources 文件夹为相对路径起始点

ApplicationContext bean = new ClassPathXmlApplicationContext("applicationContext.xml");
3、XmlWebApplicationContext 实现

在 Web 环境下初始化监听器的时候会加载。很少使用在 web 项目中启动 tomcat 时加载配置文件,但我们实际上有其他方式来完成,具体内容在Spring MVC 中讲解。

4、AnnotationConfigApplicationContext 实现

注解方式,零 xml 配置。根据注解的方式启动 Spring 容器。(推荐使用)

ApplicationContext bean = new AnnotationConfigApplicationContext("com.dailyblue.java.spring");

五、Spring IOC 实现方式

我们先以 xml 方式来搭建一个环境,后边在引入注解写法。

xml 方式

1、获取对象
  1. 引入 ioc 模块的依赖

     <!--引入springIOC依赖-->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context</artifactId>
                <version>5.3.22</version>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.18.26</version>
            </dependency>
    
  2. 在 resources 文件夹下创建 applicationContext.xml 配置文件

    image-20230926173738596

  3. 书写需要使用的类

    @Data
    public class DemoA {
    
        private Integer id;
        private String name;
        private Character gender;
    }
    
  4. 在 applicationContext.xml 中注册刚才创建类的对象

        <!--SpringIOC的容器,负责管理对象的-->
    
        <!--
            产生DemoA类的对象
            bean:代表一个对象
                ID:代表的是对象的名字
                class:代表对象是那个类产生的
        -->
        <!--
            DemoA a = new DemoA();
        -->
        <bean class="com.fzj.spring.ioc.xml.DemoA" id="a"/>
    

    5、App测试

    public class App {
        //获取对象
        private static void a() {
            //从Ioc容器中获取到对象
            System.out.println("-------------------------------");
            //加载配置文件
            ApplicationContext beans = new ClassPathXmlApplicationContext("applicationContext.xml");
            // 从容器中获取对象
            System.out.println("-------------------------------");
            Object obj = beans.getBean("a");
            System.out.println(obj);
        }
    
        public static void main(String[] args) {
            a();
        }
    }
    
2、获取对象并赋值

方式1:setter方法

方式2:构造器

1、书写需要使用的类

@Data
public class DemoA {

    private Integer id;
    private String name;
    private Character gender;


    public DemoA() {
        System.out.println("对象产生了");
    }

    public DemoA(Integer id) {
        this.id = id;
    }

    public DemoA(Integer id, String name) {
        this.id = id;
        this.name = name;
    }

    public DemoA(Integer id, String name, Character gender) {
        this.id = id;
        this.name = name;
        this.gender = gender;
    }
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class DemoB {


    private String[] array;
    private List<String> list;
    private Set<String> set;
    private Map<String, String> map;
}
@Data
public class DemoC {
    private DemoA a;
    private DemoB b;
}

2、在 applicationContext.xml

    <!--产生对象属性并赋值-->
    <!--Setter-->
    <bean class="com.fzj.spring.ioc.xml.DemoA" id="b" lazy-init="true">

        <!--给对象属性赋值 Setter方法-->
        <!--
        name:属性名
        value:-->
        <property name="id" value="1"/>
        <property name="name">
            <value>ff</value>
        </property>
        <property name="gender" value="男"/>
    </bean>
    <!--一个参数的构造器-->
    <bean class="com.fzj.spring.ioc.xml.DemoA" id="c1">
        <!--
            通过构造器赋值
            constructor-arg:给某个属性赋值
                value:值
                index:第几个参数
                name:参数名(和index作用一致)
        -->
        <constructor-arg value="1" index="0"/>
    </bean>
    <!--两个参数的构造器-->
    <!--
           通过构造器赋值
           constructor-arg:给某个属性赋值
               value:值
               index:第几个参数
               name:参数名(和index作用一致)
     -->
    <bean class="com.fzj.spring.ioc.xml.DemoA" id="c2">
        <constructor-arg value="1" name="id"/>
        <constructor-arg value="张三" index="1"/>
    </bean>
    <!--全参构造器-->
    <bean class="com.fzj.spring.ioc.xml.DemoA" id="c3">
        <constructor-arg name="id" value="1"/>
        <constructor-arg index="1" value="李四"/>
        <constructor-arg name="gender" value="男"/>
    </bean>


    <!--数组集合等方式赋值-->
    <!--setter方式-->
    <bean class="com.fzj.spring.ioc.xml.DemoB" id="b1">

        <property name="array">
            <array>
                <value>陕西</value>
                <value>福建</value>
                <value>广西</value>
            </array>
        </property>
        <property name="list">
            <list>
                <value>西安</value>
                <value>宝鸡</value>
                <value>商洛</value>
            </list>
        </property>
        <property name="set">
            <set>
                <value>足球</value>
                <value>篮球</value>
                <value>排球</value>
            </set>
        </property>
        <property name="map">
            <map>
                <entry key="A" value="aa"/>
                <entry key="B" value="bb"/>
            </map>
        </property>
    </bean>
    <!--构造器方式赋值-->
    <bean class="com.fzj.spring.ioc.xml.DemoB" id="b2">
        <constructor-arg index="0">
            <array>
                <value>陕西</value>
                <value>福建</value>
                <value>广西</value>
            </array>
        </constructor-arg>
        <constructor-arg index="1">
            <list>
                <value>西安</value>
                <value>宝鸡</value>
                <value>商洛</value>
            </list>
        </constructor-arg>

        <constructor-arg name="set">
            <set>
                <value>足球</value>
                <value>篮球</value>
                <value>排球</value>
            </set>
        </constructor-arg>
        <constructor-arg name="map">
            <map>
                <entry key="A" value="aa"/>
                <entry key="B" value="bb"/>
            </map>
        </constructor-arg>
    </bean>


    <!--给自定义类型属性赋值-->
    <bean class="com.fzj.spring.ioc.xml.DemoC" id="d1">
        <property name="a" ref="a"/>
        <property name="b" ref="b2"/>
    </bean>

3、App测试

public class App {
    
    //获取对象并赋值
    private static void b() {
        ApplicationContext beans = new ClassPathXmlApplicationContext("applicationContext.xml");

        //Setter方式
        Object obj = beans.getBean("b");
        System.out.println(obj);

        // 获取对象 构造器赋值
        Object obj2 = beans.getBean("c1");
        Object obj3 = beans.getBean("c2");
        Object obj4 = beans.getBean("c3");
        System.out.println(obj2);
        System.out.println(obj3);
        System.out.println(obj4);
    }

    //数组 集合获取对象并赋值
    private static void c() {
        ApplicationContext c = new ClassPathXmlApplicationContext("applicationContext.xml");

        Object b1 = c.getBean("b1");
        Object b2 = c.getBean("b2");

        System.out.println(b1);
        System.out.println(b2);
    }


    // 获取对象属性赋值
    public static void d() {
        ApplicationContext c = new ClassPathXmlApplicationContext("applicationContext.xml");
        Object o = c.getBean("d1");
        System.out.println(o);
    }

    public static void main(String[] args) {
        d();
    }
}
3、从IOC容器中获取对象

1、byName

2、byType、

3、byTyp+byName(推荐)

public class App {
   
    //获取对象的几种方式
    public static void e() {
        ApplicationContext c = new ClassPathXmlApplicationContext("applicationContext.xml");
        System.out.println("--------------------------");
        //byName
        Object a = c.getBean("b");
        System.out.println(a);
        //byType 必须保证容器中只有一个该类型的对象
        DemoC c1 = c.getBean(DemoC.class);
        System.out.println(c1);

        //byTyp+byName
        DemoA a1 = c.getBean("c2", DemoA.class);
        System.out.println(a1);
    }

    public static void main(String[] args) {
        e();
    }
}

常见标签 API

标签:每一个标签 bean 对应的就一个类对象,通过 id(或者Name)以及 Class 来定位一个类,这边 Class 要求是完全限定类名。
image-20230926111248729

标签:属性标签,负责给对象属性赋值。

image-20230926111323294

标签:构造器标签,负责给对象属性赋值。

image-20230926111348895

注解方式

注解不需要 applicationContext.xml 配置文件,以@注解的方式注册和管理 bean。

@Component
public class GuanWei{
 
    @Resource   //byName+byType  首先按照名称查找,如果找到赋值 如果没有找到,按照类型查询
    private ZhangSan zhang;
 
    public void print(){
        System.out.println("我是关为!");
        zhang.print();
    }
}

注解 API

一些常用的注解,产生对象,引入依赖,设置配置等。

image-20230926111437569

1、四种New对象的注解

​ @Controller
​ @Repository
​ @Service
​ @Component

 /*@Controller
@Repository
@Service*/

//在没有起名字的时候:默认名字是:类名首字母小写(demoA)
//value属性自定义名字
@Component(value = "a2")
public class DemoA {

    //注解实现属性赋值
    @Value("1")
    private Integer id;
    @Value("张三")
    private String name;
    @Value("男")
    private Character gender;

    @Override
    public String toString() {
        return "DemoA{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", gender=" + gender +
                '}';
    }
}

注意:赋值@Value注解——>替代了setter方法和构造器赋值

public class App {
    public static void a() {
        //加载配置文件
        ApplicationContext beans = new AnnotationConfigApplicationContext("com.fzj.spring.ioc.annotation");
        //获取对象
        //byType
        DemoA a1 = beans.getBean(DemoA.class);
        System.out.println(a1);
        //byName
//        Object a2 = beans.getBean("demoA");
//        System.out.println(a2);
        Object a3 = beans.getBean("a2");
        System.out.println(a3);
    }
    
    public static void main(String[] args) {
        a();
    }
}
2、赋值注解

@Autowired

@Resource

@Autowired + @Qualifier

@Value

/*
* 注解
*/
@Controller
public class DemoB {

    //赋值注解
    @Autowired       // byType:按照类型自动赋值
    //byType
    private DemoA a;

    @Resource(name = "a2")
    //byName(+byType)   // byName(+byType):按照名称赋值,如果名称不存在,按照类型赋值
    private DemoA a1;

    @Autowired
    @Qualifier("a2")  // 辅助@Autowired注解的使用,在byType基础上增加了byName功能
    private DemoA a3;


    @Override
    public String toString() {
        return "DemoB{" +
                "a=" + a +
                ", a1=" + a1 +
                ", a3=" + a3 +
                '}';
    }
}
public class App {

    public static void b() {
        //加载配置文件
        ApplicationContext beans = new AnnotationConfigApplicationContext("com.fzj.spring.ioc.annotation");

        DemoB b = beans.getBean(DemoB.class);
        System.out.println(b);
    }

    public static void main(String[] args) {
        b();
    }
}

注意:

1、@Resource是Java注解,不是Ioc注解,使用需要导入依赖。

<!--引入@Resource注解的依赖-->
<dependency>
    <groupId>javax.annotation</groupId>
    <artifactId>javax.annotation-api</artifactId>
    <version>1.3.2</version>
</dependency>

2、Spring框架的自动装配机制

在Spring框架的应用中,可以为需要被Spring自动赋值的属性添加@Autowired,则Spring框架会从Spring容器中找出匹配的值,并自动完成赋值!

面试题:

1、ByName和ByType的区别?

byName根据名称实现自动装配,在这种模式下,要求被装配的属性名称,与被Spring管理的对象的名称(调用getBean()方法给出的参数名)必须相同;

byType根据类型实现自动装配,在这种模式,要求被装配的属性的类型,在Spring容器中存在匹配类型的对象,当应用这种机制时,必须在Spring容器中保证匹配类型的对象只有1个,否则,将会出现NoUniqueBeanDefinitionException异常;

2、@Autowrie和@Resource的区别?

@Autowrie:byType:按照类型自动赋值

用于自动装配bean,可以用于字段、方法和构造函数参数的注入。如果多个bean匹配,那么@Autowired会抛出异常。默认情况下,它使用byName方式进行匹配,可以通过autowire属性设置为byType方式进行匹配。

@Resource: byName(+byType):先按照名称赋值,如果名称不存在,再按照类型赋值

用于自动装配bean,可以用于字段、方法和构造函数参数的注入。如果多个bean匹配,那么@Resource会抛出异常。默认情况下,它使用byName方式进行匹配,可以通过name属性设置为byType方式进行匹配。Spring 3.0之后推荐使用@Autowired注解,而@Resource现在使用较少。

3、单例或多例注解

@Scope

/*
 *  设计模式
 */
@Component
//设置当前类只产生一个对象(也是默认效果)
//@Scope(value = "singleton")
//每次获取当前类对象时 产生对象
@Scope(value = "prototype")
public class DemoC {
    
}
public class App {

    public static void c() {
        //加载配置文件
        ApplicationContext beans = new AnnotationConfigApplicationContext("com.fzj.spring.ioc.annotation");

        DemoC c1 = beans.getBean(DemoC.class);
        DemoC c2 = beans.getBean(DemoC.class);
        System.out.println(c1);
        System.out.println(c2);
        System.out.println(c1 == c2);
    }

    public static void main(String[] args) {
        c();
    }
}
4、配置类注解+扫包注解+手动产生对象放入Ioc容器注解

@Configuration

@ComponentScan

@Bean

bean包

@Component
public class DemoD {

    public DemoD() {
        System.out.println("DemoD被创建了!");
    }
}
public class DemoE {
}

config包

//声明为配置类
@Configuration
@ComponentScan("com.fzj.spring.ioc.annotation.bean")
//扫描多个包的方式
//@ComponentScan(basePackages = {"com.fzj.spring.ioc.annotation.bean", "com.fzj.spring.ioc.annotation.config"})
public class SpringConfig {

    //手动添加对象到Ioc容器中
    @Bean
    public DemoE getDemoE() {
        return new DemoE();
    }
}

App

public class App {

    public static void d() {
        //加载配置文件
        ApplicationContext beans
                = new AnnotationConfigApplicationContext("com.fzj.spring.ioc.annotation.config");

        System.out.println("----------------------------");
        DemoD d = beans.getBean(DemoD.class);
        DemoE e = beans.getBean(DemoE.class);
        System.out.println(d);
        System.out.println(e);

    }

    public static void main(String[] args) {
        d();
    }
}

六、Spring IOC 中 bean 的生命周期

生命周期内容

Bean 的定义:一般 Bean 使用 XML 文件的方式进行定义,定义的时候将 Bean 之间的依赖关系和属性的赋值都进行了定义。

Bean 的初始化:其实 Bean 的初始化包括 Bean 的创建和初始化两个方法,Bean 的创建和初始化一般是同步进行的,Bean 在完成创建后直接就会进行初始化操作,创建的时机与 Bean 的 lazy-init 属性的设置有关。

Bean 的使用:在 web 程序运行期间,发生对某一个 Bean 的调用时,就会使用这个 Bean实例,如果使用编码的方式来获取 Bean 同样也是 Bean 的使用,Bean 的编码使用方式有三种,第一种是使用 BeanWarpper,第二种是使用 BeanFactory,第三种就是使用 ApplicationContext。

Bean 的销毁:Bean 实例在程序退出的时候会进行销毁,而在销毁之前会自动调用 Bean的 destory-method 属性指定名称的方法。

IOC管理对象的创建

勤加载

默认情况下,当启动Ioc容器时,加载创建XML或者注解中的对象

优点:无论对象使用与否,对象都会在IOC容器启动的时候创建出来,方便随时使用。

缺点:当创建多个对象而未使用,会浪费内存空间。

懒加载

当启动Ioc容器时,不创建XML或者注解中的对象,只有在使用的时才创建。

优点:不使用对象就不会创建对象,不会存在内存空间的浪费。

缺点:使用对象时,需要时间等待对象的创建,不方便随时使用。

七、加载 xml 文件后 bean 的加载方式

默认情况下,是勤加载模式,加载 xml 后加载类文件并产生对象

设置当前 bean 为懒加载模式,当设置为懒加载后,xml 加载后不会加载类文件,更不会产生对象

设置懒勤加载方式

1、XML形式

 <bean id="da" class="com.dailyblue.java.spring.bean.DemoA" lazy-init="true"/>
// 当懒加载时,DemoA类没有被加载
ApplicationContext beans = new ClassPathXmlApplicationContext("applicationContext.xml");
// 从容器中获取到DemoA类型的对象时才加载类并产生对象
DemoA guanwei = beans.getBean(DemoA.class); 

2、注解形式——@Lazy(value = false)

@Component
@Lazy(value = false)
public class DemoD {

    public DemoD() {
        System.out.println("DemoD被创建了!");
    }
}

注意:Spring会因版本的不同懒勤加载方式更改默认的方式

八、bean 的单例和多例模式

默认情况下,bean 是单例的,你可以设置当前 bean 为多例。

<bean id="da" class="com.dailyblue.java.spring.bean.DemoA" scope="prototype"/>
// da1 是单例模式的,每一次 getBean 时获取的都是同一个对象
DemoA a1 = beans.getBean("da1", DemoA.class); // 从容器中获取到DemoA类型的对象
DemoA a2 = beans.getBean("da1", DemoA.class);
System.out.println(a1 == a2);
// da2 是多例效果的,每一次 getBean 时都是产生对象
DemoA a3 = beans.getBean("da2", DemoA.class);
DemoA a4 = beans.getBean("da2", DemoA.class);
System.out.println(a3 == a4);

九、getBean 获取对象的方式

从容器中获取对象,我们主要由三种方式

byName:根据名字获取
byType:根据类型获取
byName+byType:根据名字和类型获取

// byName 根据名称获取对象,缺点是返回结果是Object类型的,需要自行强转
Object obj1 = beans.getBean("db");
// byType 根据类型获取对象,缺点是容器中只能有一个DemoB类型的对象
DemoB obj2 = beans.getBean(DemoB.class);
// byName+byType:根据名称和类型获取,缺点是写得多
DemoB obj3 = beans.getBean("db", DemoB.class);

十、IOC 结合Mybatis

搭建项目

image-20231001095455896

导入依赖

<dependencies>
<!--        MySQL 数据库的 Java 连接器-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.30</version>
        </dependency>
<!--Java 对象与数据库进行映射-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.13</version>
        </dependency>
        <!--扫描mapper的包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.3.22</version>
        </dependency>
<!--        提供了 MyBatisSpring 中的支持,扫包-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>3.0.1</version>
        </dependency>
    </dependencies>

数据库准备

create table emp
(
    empno    int auto_increment
        primary key,
    ename    varchar(15)   null,
    job      varchar(10)   null,
    mgr      int unsigned  null,
    hiredate date          null,
    sal      decimal(7, 2) null,
    comm     decimal(7, 2) null,
    deptno   int           null,
    state    int default 1 null,
    constraint ename
        unique (ename)
)
    collate = utf8_general_ci;




insert into emp (empno, ename, job, mgr, hiredate, sal, comm, deptno, state)
values  (7369, 'SMITH', 'CLERK', 7902, '1980-12-17', 800.00, null, 20, 0),
        (7499, 'ALLEN', 'SALESMAN', 7698, '1981-02-20', 1600.00, 300.00, 30, 0),
        (7521, 'WARD1', 'SALESMAN', 7566, '1981-02-28', 1250.00, 500.00, 30, 1),
        (7566, 'JONES', 'MANAGER', 7839, '1981-04-02', 2976.00, null, 20, 1),
        (7654, 'MARTIN', 'SALESMAN', 7698, '1981-09-28', 1250.00, 1400.00, 30, 1),
        (7698, 'BLAKE', 'MANAGER', 7839, '1981-05-01', 2850.00, null, 30, 1),
        (7782, 'CLARK', 'MANAGER', 7839, '1981-06-09', 2450.00, null, 10, 1),
        (7788, 'SCOTT', 'ANALYST', 7566, '1987-07-13', 3000.00, null, 20, 0),
        (7839, 'KING', 'PRESIDENT', null, '1981-11-17', 5000.00, null, 10, 1),
        (7844, 'TURNER', 'SALESMAN', 7698, '1981-09-08', 1500.00, 0.00, 30, 1),
        (7876, 'ADAMS', 'CLERK', 7788, '1987-07-13', 1100.00, null, 20, 1),
        (7900, 'JAMES', 'CLERK', 7698, '1981-12-03', 950.00, null, 30, 1),
        (7902, 'FORD', 'ANALYST', 7566, '1981-12-03', 3000.00, null, 20, 1),
        (7903, '熊辉', '页面设计', 7369, '2022-06-06', 13456.00, 1980.00, 30, 1),
        (7904, '刘万松', '程序员', 7940, '2022-06-15', 19800.00, 701.00, 30, 0),
        (7905, '张三', '健身教练', 7937, '2022-06-15', 19800.00, 4500.00, 30, 0),
        (7906, '张大三', '页面设计', 7936, '2022-01-04', 4020.00, 200.00, 20, 1),
        (7907, '张晓散', '程序员', 7788, '2022-08-24', 5600.00, 3400.00, 10, 1),
        (7908, '方文山', '歌手', 7788, '2022-08-25', 4200.00, 1987.00, 30, 1),
        (7909, '马三立', '相声大师', 7521, '2022-08-29', 14500.00, 7400.00, 20, 1),
        (7910, '王浩江', '程序员', 7908, '2014-08-09', 9999.00, 1234.00, 20, 1),
        (7934, 'MILLER', 'CLERK', 7782, '1982-01-23', 1300.00, null, 10, 1),
        (7935, '张三丰', '程序员', 7788, '2020-06-14', 8000.00, 3000.00, 20, 1),
        (7936, '虞挺', '程序员', 7788, '2022-03-25', 80000.00, 3000.00, 20, 1),
        (7937, '杜瑜杰', '程序员', 7566, '2022-03-01', 18888.80, 4300.00, 30, 1),
        (7939, '张恒', '程序员', 7935, '2022-03-03', 8000.00, 3000.00, 10, 1),
        (7940, '王玮琪', '程序员', 7935, '2022-03-03', 300.00, 11000.00, 10, 1),
        (7941, '张鑫', '检测人员', 7839, '2022-04-06', 9010.00, 2000.00, 20, 1),
        (7942, '杨颖', '演员', 7566, '2022-04-15', 13800.00, 21777.00, 20, 1),
        (7943, '段卓越', '程序员', 7788, '2022-04-15', 9999.00, 4444.00, 10, 1),
        (7944, '张明杰', '程序员', 7782, '2022-04-15', 10001.00, 3333.00, 10, 1),
        (7945, '陈丹丹', '程序员', 7566, '2022-04-19', 7566.00, 7566.00, 10, 1),
        (7946, '董甜甜', 'MANAGER', 7876, '2022-04-12', 9998.00, 3456.00, 30, 1),
        (7947, '秋一凡', 'PRESIDENT', 7935, '2022-03-28', 11988.00, 2366.00, 40, 1),
        (7949, '周杰伦', 'SALESMAN', 7521, '2022-05-09', 22.00, 33.00, 20, 1),
        (7951, '刘耕宏', '健身教练', 7788, '2022-05-10', 19800.00, 7000.00, 10, 1),
        (7952, '王化楠', '程序员', 7788, '2022-11-03', 9876.00, 6789.00, 20, 1),
        (7953, '邓欢欢', '页面设计', 7788, '2022-11-03', 9999.00, 3333.00, 20, 1),
        (7954, '郑若琳', 'SALESMAN', 7369, '2022-11-03', 13800.00, 99.00, 30, 1),
        (7955, '张彤', '程序员', 7566, '2022-11-04', 9876.00, 56789.00, 20, 1),
        (7956, '张力士', '健身教练', 7905, '2011-08-09', 6666.80, 789.00, 30, 1),
        (7957, '董杭', '页面设计', 7788, '2023-04-01', 9800.00, 300.00, 20, 1),
        (7958, '付志杰', '检测人员', 7910, '2023-04-01', 12800.00, 888.00, 40, 1),
        (7960, '李毛', '程序员', 7566, '2023-04-14', 12000.00, 3450.00, 20, 1),
        (7962, '武路路', '程序员', 7936, '2023-04-21', 7890.00, 123.00, 10, 1),
        (7963, '赵国强', '程序员', 7788, '2023-04-21', 9800.00, 300.00, 20, 1),
        (7964, '李晓五', '页面设计', 7788, '2023-04-21', 9999.00, 200.00, 10, 1),
        (7965, '刘四姐', '歌手', 7566, '2023-04-21', 10987.00, 320.00, 20, 1),
        (7966, '白小白', 'ANALYST', 7654, '2023-05-04', 3000.00, 200.00, 30, 1),
        (7967, '测试数据1', '程序员', 7788, '2019-04-15', 9800.00, 67.00, 20, 0),
        (7969, '测试数据3', 'ANALYST', 7788, '2023-05-05', 9800.00, 67.00, 20, 1),
        (7971, '测试数据4', '程序员', 7369, '2023-05-09', 8900.00, null, 20, 1),
        (7972, '董雪', 'ANALYST', 7782, '2023-06-11', 12900.00, 100.00, 30, 1);

1、普通Mybatis方式

Bean包

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Emp implements Serializable {
    private Integer empNo;
    private String ename;
    private String job;
    private Integer mgr;
    private String hireDate;
    private Double sal;
    private Double comm;
    private Integer deptNo;
    private Integer state;
}

Mapper包

public interface EmpMapper {

    @Select("select  * from emp where state=1")
    List<Emp> findAll();
    
}

config配置

加载数据源

扫描包

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <settings>
        <!--输出日志到控制台中-->
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <!--分别书写驱动类、网址、账号和密码 注意网址中&使用&amp;代替-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/test"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <!--引入的资源文件-->
               <mapper resource="com.shixia.spring.mapper"/>
    </mappers>
</configuration>

App

image-20231001101730292

测试

image-20231001101812519

局限性:

1、需要自己书写数据源

2、需要扫描哪个包

3、需要产生Mapper对象

4、需要手动的加载配置文件‘

5、需要手动产生工厂和会话

2、IOC+Mybatis方式

1、@Mapper的作用:得到EmpMapper的代理对象,将其交由IOC进行管理

//得到EmpMapper的代理对象,将其交由IOC进行管理
@Mapper
public interface EmpMapper {

    @Select("select  * from emp where state=1")
    List<Emp> findAll();

}

2、IOC无法加载config.xml,需要手动解决加载数据源和扫包

config包

@Configuration
//扫描mapper包
@MapperScan("com.shixia.spring.mapper")
public class MyBatisConfig {

    @Bean
    //加载数据源
    public DataSource getDataSource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/com.zgh");
        dataSource.setUsername("root");
        dataSource.setPassword("root");
        return dataSource;
    }

    //获取会话工厂
    @Bean
    public SqlSessionFactoryBean getSsf(DataSource dataSource) {
        SqlSessionFactoryBean ssf = new SqlSessionFactoryBean();
        ssf.setDataSource(dataSource);
        return ssf;
    }
}

service包

调用mapper中的方法

@Service
public class EmpService {

    @Resource
    private EmpMapper empMapper;

    public List<Emp> findAllEmp() {

        return empMapper.findAll();
    }
}

App

public class App {

    private static void b() {
        AnnotationConfigApplicationContext beans
                = new AnnotationConfigApplicationContext("com.shixia.spring");
        EmpService empService = beans.getBean(EmpService.class);
        empService.deleteEmp(7996);
    }

    public static void main(String[] args) {
        b();
    }
}

测试

image-20231001104701083

总结

1、引入依赖

扫包依赖是为了将@MapperScan注解启动

2、书写config配置类

扫包

配置数据源

创建会话工厂

3、bean包

4、Mapper包(加@Mapper注解)

5、service包

6、执行类(得到service,将service输出)

3、增删改查

在搭建好的项目中完成CRUD

步骤:

​ 1、Mapper包

​ 2、service包

​ 3、App

Mapper包

package com.shixia.spring.mapper;

import com.shixia.spring.bean.Emp;
import org.apache.ibatis.annotations.*;

import java.util.List;

//得到EmpMapper的代理对象,将其交由IOC进行管理
@Mapper
public interface EmpMapper {

    //查询emp表中state为1的所有列
    @Select("select  * from emp where state=1")
    List<Emp> findAll();

    //根据ID删除
    @Update("update  emp set state=0 where empno=#{id}")
    void deleteEmp(Integer id);

    //添加
    @Insert("insert into emp values (null,#{ename},#{job},#{mgr},now(),#{sal},#{comm},#{deptNo},1)")
    void saveEmp(Emp emp);

    //修改
    @Update("update  emp set  ename=#{ename},job=#{job},mgr=#{mgr},sal=#{sal},comm=#{comm},deptno=#{deptNo} where empno=#{empNo}")
    void updateEmp(Emp emp);
}

service包

//@Service注解的主要作用是将这个类的实例注入到Spring的IoC容器中,并且可以被其他组件通过@Autowired注解自动注入
@Service
public class EmpService {

    //注入EmpMapper接口的实现类,通过Spring的依赖注入功能,将EmpMapper的实现类交给EmpService使用
    @Resource
    private EmpMapper empMapper;

    //查询emp表中所有列
    public List<Emp> findAllEmp() {
        return empMapper.findAll();
    }

    //删除方法
    public void deleteEmp(Integer id) {
        empMapper.deleteEmp(id);
    }

    //添加方法
    public void saveEmp(Emp emp) {
        empMapper.saveEmp(emp);
    }

    //修改方法
    public void updateEmp(Emp emp) {
        empMapper.updateEmp(emp);
    }
}

App

public class App {
    //查询
    private static void a() {
        AnnotationConfigApplicationContext beans
                = new AnnotationConfigApplicationContext("com.shixia.spring");
        EmpService empService = beans.getBean(EmpService.class);
        empService.findAllEmp().forEach(System.out::println);
    }

    //删除
    private static void b() {
        AnnotationConfigApplicationContext beans
                = new AnnotationConfigApplicationContext("com.shixia.spring");
        EmpService empService = beans.getBean(EmpService.class);
        empService.deleteEmp(7996);
    }

    //添加
    private static void c() {
        AnnotationConfigApplicationContext beans
                = new AnnotationConfigApplicationContext("com.shixia.spring");
        EmpService empService = beans.getBean(EmpService.class);
        Emp emp = new Emp();
        emp.setEname("shixia");
        emp.setJob("歌手");
        emp.setSal(19800.0);
        emp.setMgr(7788);
        emp.setComm(4555.0);
        emp.setDeptNo(20);
        empService.saveEmp(emp);
    }

    //更改
    private static void d() {
        AnnotationConfigApplicationContext beans
                = new AnnotationConfigApplicationContext("com.shixia.spring");
        EmpService empService = beans.getBean(EmpService.class);
        Emp emp = new Emp();
        emp.setEname("2");
        emp.setJob("SALESMAN");
        emp.setSal(18800.0);
        emp.setMgr(7789);
        emp.setComm(5000.0);
        emp.setDeptNo(10);
        emp.setEmpNo(7996);
        empService.updateEmp(emp);
    }

    //修改
    private static void e() {
        AnnotationConfigApplicationContext beans
                = new AnnotationConfigApplicationContext("com.shixia.spring");
        EmpService empService = beans.getBean(EmpService.class);
        Emp emp = new Emp();
        emp.setComm(100.0);
        emp.setSal(19800.0);
        emp.setJob("歌手");
        emp.setMgr(7966);
        emp.setEname("李唯佳");
        emp.setDeptNo(20);
        emp.setEmpNo(7967);
        empService.updateEmp(emp);
    }

    public static void main(String[] args)  {
        e();
    }
}

十一、jdk版本的注意事项

发布的有持久化版和非持久化版

持久化版:持续更新的jdk版本(8、11、17),17建议用2022版IDEA

十二、面试题

1. 什么是 Spring Framework ?

官方文档:

Spring makes it easy to create Java enterprise applications. It provides everything you need to embrace the Java language in an enterprise environment, with support for Groovy and Kotlin as alternative languages on the JVM, and with the flexibility to create many kinds of architectures depending on an application’s needs.

这个问题很难回答,在 Spring 官方文档中的描述也很抽象,答案在于你对 Spring 是如何理解的,想必每个人都有自己的回答方式,以下是我个人对于 Spring 的理解:

整个 Spring 生态在涉及到 Java 的项目中被广泛应用,它提供了非常多的组件,能够让你在开发 Java 应用的过程变得更加容易,弹性地支持其他软件框架,可以比作一个“排插座”,其他软件框架简单地“插上”即可结合 Spring 一起使用,给开发人员带来了非常多的便利。Spring 底层 IoC 容器的设计实现也是非常完美的,在整个 Spring 应用上下文的生命周期和 Spring Bean 的生命周期的许多阶段提供了相应的扩展点,供开发者自行扩展,使得框架非常的灵活。

2. Spring Framework 的优势和不足?

优势:Spring 面向模块进行开发,根据不同的功能进行划分,根据需求引入对应的模块即可,对于开发人员非常友好。例如 Spring IoC 容器,将我们的 Java 对象作为 Spring Bean 进行管理,管理着 Bean 的整个生命周期;Spring MVC 提供“模型-视图-控制器”(Model-View-Controller)架构和随时可用的组件,用于开发灵活且松散耦合的 Web 应用程序;Spring AOP 提供面向切面编程的接口,可以很方便的使用;还有许多其他的功能模块,就不一一讲述了。

不足:整个 Spring 体系比较复杂,对于开发人员需要一定的学习成本,遇到相关问题时需要对底层实现有充分的了解,这也就需要开发人员投入更多的时间和精力去学习。当然,如今 Spring 体系整合了 Java 生态非常多的东西,为开发人员带来的便利远大于这些不足,我觉得是有必要对 Spring 进行充分的学习,去了解 Spring 的贡献者们的设计思路,对自身也会有很大的提升,从中可以学习到许多的东西。

3. 你对 IoC 的理解?

Inversion of Control(IoC)是面向对象中的一种编程思想或原则。可以先回到传统方式,当我依赖一个对象,我需要主动去创建它并进行属性赋值,然后我才能去使用这个对象。对于 IoC 这种方式来说,它使得对象或者组件的创建更为透明,你不需要过多地关注细节,如创建对象、属性赋值,这些工作交都由 IoC 容器来完成,已达到解耦的目的。

IoC 控制反转,简单来理解其实就是把获取依赖对象的方式,交由 IoC 容器来实现,由“主动拉取”变为“被动获取”。

4. 为什么需要 IoC ?

1.效率方面
传统上创建对象都需要自己new出来,而ioc是将new对象的控制权交给工厂,由工厂来创建,相比于传统的方法,效率提升了很多。

2.管理对象方面
如果对象之间存在很多依赖关系,那么当需要修改维护其中一个对象时,会导致所有的对象都需要修改,牵一发而动全身。而ioc减少了代码之间的耦合,更好的管理对象。

5. IoC 和 DI 的区别?

DI 依赖注入不完全等同于 IoC,更应该说 DI 依赖注入是 IoC 的一种实现方式或策略。

依赖查找依赖注入都是 IoC 的实现策略。依赖查找就是在应用程序里面主动调用 IoC 容器提供的接口去获取对应的 Bean 对象,而依赖注入是在 IoC 容器启动或者初始化的时候,通过构造器、字段、setter 方法或者接口等方式注入依赖。依赖查找相比于依赖注入对于开发者而言更加繁琐,具有一定的代码入侵性,需要借助 IoC 容器提供的接口,所以我们总是强调后者。依赖注入在 IoC 容器中的实现也是调用相关的接口获取 Bean 对象,只不过这些工作都是在 IoC 容器启动时由容器帮你实现了,在应用程序中我们通常很少主动去调用接口获取 Bean 对象。

6. IoC 容器的职责?

主要有以下职责:

  • 依赖处理,通过依赖查找或者依赖注入
  • 管理托管的资源(Java Bean 或其他资源)的生命周期
  • 管理配置(容器配置、外部化配置、托管的资源的配置)

IoC 容器有非常多,例如 JDK 的 Java Beans,Java EE 的 EJB,Apache Avalon,Google guice,Spring,其中 Spring 是最成功的的一个,目前被广泛应用。

其中 Spring 借鉴了 JDK 的 Java Beans 设计思想,也使用到其中相关类(例如 java.beans.PropertyEditor 属性编辑器),开发过 IDE 的 GUI 界面的伙伴应该对 Java Beans 比较熟悉。

7. 什么是 Spring IoC 容器?

Spring 框架是一个 IoC 容器的实现,DI 依赖注入是它的实现的一个原则,提供依赖查找和依赖注入两种依赖处理,管理着 Bean 的生命周期。Spring 还提供了 AOP 抽象、事件抽象、事件监听机制、SPI 机制、强大的第三方整合、易测试性等其他特性。

8. 构造器注入和 Setter 注入

构造器注入:通过构造器的参数注入相关依赖对象

Setter 注入:通过 Setter 方法注入依赖对象,也可以理解为字段注入

对于两种注入方式的看法:

  • 构造器注入可以避免一些尴尬的问题,比如说状态不确定性地被修改,在初始化该对象时才会注入依赖对象,一定程度上保证了 Bean 初始化后就是不变的对象,这样对于我们的程序和维护性都会带来更多的便利;
  • 构造器注入不允许出现循环依赖,因为它要求被注入的对象都是成熟态,保证能够实例化,而 Setter 注入或字段注入没有这样的要求;
  • 构造器注入可以保证依赖的对象能够有序的被注入,而 Setter 注入或字段注入底层是通过反射机制进行注入,无法完全保证注入的顺序;
  • 如果构造器注入出现比较多的依赖导致代码不够优雅,我们应该考虑自身代码的设计是否存在问题,是否需要重构代码结构。

除了上面的注入方式外,Spring 还提供了接口回调注入,通过实现 Aware 接口(例如 BeanNameAware、ApplicationContextAware)可以注入相关对象,Spring 在初始化这类 Bean 时会调用其 setXxx 方法注入对象,例如注入 beanName、ApplicationContext

9. BeanFactory 和 ApplicationContext 谁才是 Spring IoC 容器?

BeanFactory 是 Spring 底层 IoC 容器,ApplicationContext 是 BeanFactory 的子接口,是 BeanFactory 的一个超集,提供 IoC 容器以外更多的功能。ApplicationContext 除了扮演 IoC 容器角色,还提供了这些企业特性:面向切面(AOP)、配置元信息、资源管理、事件机制、国际化、注解、Environment 抽象等。我们一般称 ApplicationContext 是 Spring 应用上下文,BeanFactory 为 Spring 底层 IoC 容器。

10. Spring Bean 的生命周期?

生命周期:

  1. Spring Bean 元信息配置阶段,可以通过面向资源(XML 或 Properties)、面向注解、面向 API 进行配置
  2. Spring Bean 元信息解析阶段,对上一步的配置元信息进行解析,解析成 BeanDefinition 对象,该对象包含定义 Bean 的所有信息,用于实例化一个 Spring Bean
  3. Spring Bean 元信息注册阶段,将 BeanDefinition 配置元信息 保存至 BeanDefinitionRegistry 的 ConcurrentHashMap 集合中
  4. Spring BeanDefinition 合并阶段,定义的 Bean 可能存在层次性关系,则需要将它们进行合并,存在相同配置则覆盖父属性,最终生成一个 RootBeanDefinition 对象
  5. Spring Bean 的实例化阶段,首先的通过类加载器加载出一个 Class 对象,通过这个 Class 对象的构造器创建一个实例对象,构造器注入在此处会完成。在实例化阶段 Spring 提供了实例化前后两个扩展点(InstantiationAwareBeanPostProcessor 的 postProcessBeforeInstantiation、postProcessAfterInstantiation 方法)
  6. Spring Bean 属性赋值阶段,在 Spring 实例化后,需要对其相关属性进行赋值,注入依赖的对象。首先获取该对象所有属性与属性值的映射,可能已定义,也可能需要注入,在这里都会进行赋值(反射机制)。提示一下,依赖注入的实现通过 CommonAnnotationBeanPostProcessor(@Resource、@PostConstruct、@PreDestroy)和 AutowiredAnnotationBeanPostProcessor(@Autowired、@Value)两个处理器实现的。
  7. Aware 接口回调阶段,如果 Spring Bean 是 Spring 提供的 Aware 接口类型(例如 BeanNameAware、ApplicationContextAware),这里会进行接口的回调,注入相关对象(例如 beanName、ApplicationContext)
  8. Spring Bean 初始化阶段,这里会调用 Spring Bean 配置的初始化方法,执行顺序:@PostConstruct 标注方法、实现 InitializingBean 接口的 afterPropertiesSet() 方法、自定义初始化方法。在初始化阶段 Spring 提供了初始化前后两个扩展点(BeanPostProcessor 的 postProcessBeforeInitialization、postProcessAfterInitialization 方法)
  9. Spring Bean 初始化完成阶段,在所有的 Bean(不是抽象、单例模式、不是懒加载方式)初始化后,Spring 会再次遍历所有初始化好的单例 Bean 对象,如果是 SmartInitializingSingleton 类型则调用其 afterSingletonsInstantiated() 方法,这里也属于 Spring 提供的一个扩展点
  10. Spring Bean 销毁阶段,当 Spring 应用上下文关闭或者你主动销毁某个 Bean 时则进入 Spring Bean 的销毁阶段,执行顺序:@PreDestroy 注解的销毁动作、实现了 DisposableBean 接口的 Bean 的回调、destroy-method 自定义的销毁方法。这里也有一个销毁前阶段,也属于 Spring 提供的一个扩展点,@PreDestroy 就是基于这个实现的
  11. Spring 垃圾收集(GC)

总结:

  1. 上面 123 属于 BeanDefinition 配置元信息阶段,算是 Spring Bean 的前身,想要生成一个 Bean 对象,需要将这个 Bean 的所有信息都定义好;
  2. 其中 45 属于实例化阶段,想要生成一个 Java Bean 对象,那么肯定需要根据 Bean 的元信息先实例化一个对象;
  3. 接下来的 6 属于属性赋值阶段,实例化后的对象还是一个空对象,我们需要根据 Bean 的元信息对该对象的所有属性进行赋值;
  4. 后面的 789 属于初始化阶段,在 Java Bean 对象生成后,可能需要对这个对象进行相关初始化工作才予以使用;
  5. 最后面的 1011 属于销毁阶段,当 Spring 应用上下文关闭或者主动销毁某个 Bean 时,可能需要对这个对象进行相关销毁工作,最后等待 JVM 进行回收。
11. BeanDefinition 是什么?

BeanDefinition 是 Spring Bean 的“前身”,其内部包含了初始化一个 Bean 的所有元信息,在 Spring 初始化一个 Bean 的过程中需要根据该对象生成一个 Bean 对象并进行一系列的初始化工作。

12. Spring 内建的 Bean 作用域有哪些?
来源说明
singleton默认 Spring Bean 作用域,一个 BeanFactory 有且仅有一个实例
prototype原型作用域,每次依赖查找和依赖注入生成新 Bean 对象
request将 Spring Bean 存储在 ServletRequest 上下文中
session将 Spring Bean 存储在 HttpSession 中
application将 Spring Bean 存储在 ServletContext 中
13. BeanPostProcessor 与 BeanFactoryPostProcessor 的区别?

BeanPostProcessor 提供 Spring Bean 初始化前和初始化后的生命周期回调,允许对关心的 Bean 进行扩展,甚至是替换,其相关子类也提供 Spring Bean 生命周期中其他阶段的回调。

BeanFactoryPostProcessor 提供 Spring BeanFactory(底层 IoC 容器)的生命周期的回调,用于扩展 BeanFactory(实际为 ConfigurableListableBeanFactory),BeanFactoryPostProcessor 必须由 Spring ApplicationContext 执行,BeanFactory 无法与其直接交互。

14. 依赖注入和依赖查找的来源是否相同?

否,依赖查找的来源仅限于 Spring BeanDefinition 以及单例对象,而依赖注入的来源还包括 Resolvable Dependency(Spring 应用上下文定义的可已处理的注入对象,例如注入 BeanFactory 注入的是 ApplicationContext 对象)以及 @Value 所标注的外部化配置

15. 如何基于 Extensible XML authoring 扩展 Spring XML 元素?

Spring XML 扩展

  1. 编写 XML Schema 文件(XSD 文件):定义 XML 结构
  2. 自定义 NamespaceHandler 实现:定义命名空间的处理器
  3. 自定义 BeanDefinitionParser 实现:绑定命名空间下不同的 XML 元素与其对应的解析器
  4. 注册 XML 扩展(META-INF/spring.handlers 文件):命名空间与命名空间处理器的映射
  5. 编写 Spring Schema 资源映射文件(META-INF/spring.schemas 文件):XML Schema 文件通常定义为网络的形式,在无网的情况下无法访问,所以一般在本地的也有一个 XSD 文件,可通过编写 spring.schemas 文件,将网络形式的 XSD 文件与本地的 XSD 文件进行映射,这样会优先从本地获取对应的 XSD 文件

Mybatis 对 Spring 的集成项目中的 <mybatis:scan /> 标签就是这样实现的,可以参考:NamespaceHandlerMapperScannerBeanDefinitionParserXSD 等文件

具体实现逻辑参考后续**《解析自定义标签(XML 文件)》**一文

16. Java 泛型擦写发生在编译时还是运行时?

运行时。编译时,泛型参数类型还是存在的,运行时会忽略。

17. 简述 Spring 事件机制原理?

主要有以下几个角色:

  • Spring 事件 - org.springframework.context.ApplicationEvent,实现了 java.util.EventListener 接口
  • Spring 事件监听器 - org.springframework.context.ApplicationListener,实现了 java.util.EventObject 类
  • Spring 事件发布器 - org.springframework.context.ApplicationEventPublisher
  • Spring 事件广播器 - org.springframework.context.event.ApplicationEventMulticaster

Spring 内建的事件:

  • ContextRefreshedEvent:Spring 应用上下文就绪事件
  • ContextStartedEvent:Spring 应用上下文启动事件
  • ContextStoppedEvent:Spring 应用上下文停止事件
  • ContextClosedEvent:Spring 应用上下文关闭事件

Spring 应用上下文就是一个 ApplicationEventPublisher 事件发布器,其内部有一个 ApplicationEventMulticaster 事件广播器(被观察者),里面保存了所有的 ApplicationListener 事件监听器(观察者)。Spring 应用上下文发布一个事件后会通过 ApplicationEventMulticaster 事件广播器进行广播,能够处理该事件类型的 ApplicationListener 事件监听器则进行处理。

18. @EventListener 的工作原理?

@EventListener 用于标注在方法上面,该方法则可以用来处理 Spring 的相关事件。

Spring 内部有一个处理器 EventListenerMethodProcessor,它实现了 SmartInitializingSingleton 接口,在所有的 Bean(不是抽象、单例模式、不是懒加载方式)初始化后,Spring 会再次遍历所有初始化好的单例 Bean 对象时会执行该处理器对该 Bean 进行处理。在 EventListenerMethodProcessor 中会对标注了 @EventListener 注解的方法进行解析,如果符合条件则生成一个 ApplicationListener 事件监听器并注册。

19. Spring 提供的注解有哪些?

核心注解有以下:

  • Spring 模式注解
Spring 注解场景说明起始版本
@Repository数据仓储模式注解2.0
@Component通用组件模式注解2.5
@Service服务模式注解2.5
@ControllerWeb 控制器模式注解2.5
@Configuration配置类模式注解3.0

Spring 模式注解都是 @Component 的派生注解,Spring 为什么会提供这么多派生注解?

@Component 注解是一个通用组件注解,标注这个注解后表明你需要将其作为一个 Spring Bean 进行使用,而其他注解都有各自的作用,例如 @Controller 及其派生注解用于 Web 场景下处理 HTTP 请求,@Configuration 注解通常会将这个 Spring Bean 作为一个配置类,也会被 CGLIB 提供,帮助实现 AOP 特性。这也是领域驱动设计中的一种思想。

领域驱动设计:Domain-Driven Design,简称 DDD。过去系统分析和系统设计都是分离的,这样割裂的结果导致需求分析的结果无法直接进行设计编程,而能够进行编程运行的代码却扭曲需求,导致客户运行软件后才发现很多功能不是自己想要的,而且软件不能快速跟随需求变化。DDD 则打破了这种隔阂,提出了领域模型概念,统一了分析和设计编程,使得软件能够更灵活快速跟随需求变化。

  • 装配注解
Spring 注解场景说明起始版本
@ImportResource替换 XML 元素 <import>2.5
@Import导入 Configuration 类2.5
@ComponentScan扫描指定 package 下标注 Spring 模式注解的类3.1
  • 依赖注入注解
Spring 注解场景说明起始版本
@AutowiredBean 依赖注入,支持多中依赖查找方式2.5
@Qualifier细粒度的 @Autowired 依赖查找2.5
  • @Enable 模块驱动
Spring 注解场景说明起始版本
@EnableWebMvc启动整个 Web MVC 模块3.1
@EnableTransactionManagement启动整个事务管理模块3.1
@EnableCaching启动整个缓存模块3.1
@EnableAsync启动整个异步处理模块3.1

@Enable 模块驱动是以 @Enable 为前缀的注解驱动编程模型。所谓“模块”是指具备相同领域的功能组件集合,组合所形成一个独立的单元。比如 Web MVC 模块、AspectJ 代理模块、Caching(缓存)模块、JMX(Java 管理扩展)模块、Async(异步处理)模块等。

这类注解底层原理就是通过 @Import 注解导入相关类(Configuration Class、 ImportSelector 接口实现、ImportBeanDefinitionRegistrar 接口实现),来实现引入某个模块或功能。

  • 条件注解
Spring 注解场景说明起始版本
@Conditional条件限定,引入某个 Bean4.0
@Profile从 Spring 4.0 开始,@Profile 基于 @Conditional 实现,限定 Bean 的 Spring 应用环境4.0
20. 简述 Spring Environment ?

统一 Spring 配置属性的存储,用于占位符处理和类型转换,还支持更丰富的配置属性源(PropertySource);

通过 Environment Profiles 信息,帮助 Spring 容器提供条件化地装配 Bean。

21. Environment 完整的生命周期是怎样的?

在 Spring 应用上下文进入刷新阶段之前,可以通过 setEnvironment(Environment) 方法提前设置 Environment 对象,在刷新阶段如果没有 Environment 对象则会创建一个新的 Environment 对象

22. Spring 应用上下文的生命周期?

Spring 应用上下文就是 ApplicationContext,生命周期主要体现在 org.springframework.context.support.AbstractApplicationContext#refresh() 方法中,大致如下:

  1. Spring 应用上下文启动准备阶段,设置相关属性,例如启动时间、状态标识、Environment 对象
  2. BeanFactory 初始化阶段,初始化一个 BeanFactory 对象,加载出 BeanDefinition 们;设置相关组件,例如 ClassLoader 类加载器、表达式语言处理器、属性编辑器,并添加几个 BeanPostProcessor 处理器
  3. BeanFactory 后置处理阶段,主要是执行 BeanFactoryPostProcessor 和 BeanDefinitionRegistryPostProcessor 的处理,对 BeanFactory 和 BeanDefinitionRegistry 进行后置处理,这里属于 Spring 应用上下文的一个扩展点
  4. BeanFactory 注册 BeanPostProcessor 阶段,主要初始化 BeanPostProcessor 类型的 Bean(依赖查找),在 Spring Bean 生命周期的许多节点都能见到该类型的处理器
  5. 初始化内建 Bean,初始化当前 Spring 应用上下文的 MessageSource 对象(国际化文案相关)、ApplicationEventMulticaster 事件广播器对象、ThemeSource 对象
  6. Spring 事件监听器注册阶段,主要获取到所有的 ApplicationListener 事件监听器进行注册,并广播早期事件
  7. BeanFactory 初始化完成阶段,主要是初始化所有还未初始化的 Bean(不是抽象、单例模式、不是懒加载方式)
  8. Spring 应用上下文刷新完成阶段,清除当前 Spring 应用上下文中的缓存,例如通过 ASM(Java 字节码操作和分析框架)扫描出来的元数据,并发布上下文刷新事件
  9. Spring 应用上下文启动阶段,需要主动调用 AbstractApplicationContext#start() 方法,会调用所有 Lifecycle 的 start() 方法,最后会发布上下文启动事件
  10. Spring 应用上下文停止阶段,需要主动调用 AbstractApplicationContext#stop() 方法,会调用所有 Lifecycle 的 stop() 方法,最后会发布上下文停止事件
  11. Spring 应用上下文关闭阶段,发布当前 Spring 应用上下文关闭事件,销毁所有的单例 Bean,关闭底层 BeanFactory 容器;注意这里会有一个钩子函数(Spring 向 JVM 注册的一个关闭当前 Spring 应用上下文的线程),当 JVM “关闭” 时,会触发这个线程的运行

总结:

  • 上面的 12345678 都属于 Sping 应用上下文的刷新阶段,完成了 Spring 应用上下文一系列的初始化工作;
  • 9 属于 Spring 应用上下文启动阶段,和 Lifecycle 生命周期对象相关,会调用这些对象的 start() 方法,最后发布上下文启动事件;
  • 10 属于 Spring 应用上下文停止阶段,和 Lifecycle 生命周期对象相关,会调用这些对象的 stop() 方法,最后发布上下文停止事件;
  • 11 属于 Spring 应用上下文关闭阶段,发布上下文关闭事件,销毁所有的单例 Bean,关闭底层 BeanFactory 容器。
23. Spring 应用上下文生命周期有哪些阶段?

参考Spring 应用上下文的生命周期

  • 刷新阶段 - ConfigurableApplicationContext#refresh()
  • 启动阶段 - ConfigurableApplicationContext#start()
  • 停止阶段 - ConfigurableApplicationContext#stop()
  • 关闭阶段 - ConfigurableApplicationContext#close()
24. 简述 ObjectFactory?

ObjectFactory(或 ObjectProvider) 可关联某一类型的 Bean,仅提供一个 getObject() 方法用于返回目标 Bean 对象,ObjectFactory 对象被依赖注入或依赖查找时并未实时查找到关联类型的目标 Bean 对象,在调用 getObject() 方法才会依赖查找到目标 Bean 对象。

根据 ObjectFactory 的特性,可以说它提供的是延迟依赖查找。通过这一特性在 Spring 处理循环依赖(字段注入)的过程中就使用到了 ObjectFactory,在某个 Bean 还没有完全初始化好的时候,会先缓存一个 ObjectFactory 对象(调用其 getObject() 方法可返回当前正在初始化的 Bean 对象),如果初始化的过程中依赖的对象又依赖于当前 Bean,会先通过缓存的 ObjectFactory 对象获取到当前正在初始化的 Bean,这样一来就解决了循环依赖的问题。

注意这里是延迟依赖查找而不是延迟初始化,ObjectFactory 无法决定是否延迟初始化,而需要通过配置 Bean 的 lazy 属性来决定这个 Bean 对象是否需要延迟初始化,非延迟初始化的 Bean 在 Spring 应用上下文刷新过程中就会初始化。

提示:如果是 ObjectFactory(或 ObjectProvider)类型的 Bean,在被依赖注入或依赖查找时返回的是 DefaultListableBeanFactory#DependencyObjectProvider 私有内部类,实现了 ObjectProvider<T> 接口,关联的类型为 Object。

25. 简述 FactoryBean?

FactoryBean 关联一个 Bean 对象,提供了一个 getObject() 方法用于返回这个目标 Bean 对象,FactoryBean 对象在被依赖注入或依赖查找时,实际得到的 Bean 就是通过 getObject() 方法获取到的目标类型的 Bean 对象。如果想要获取 FactoryBean 本身这个对象,在 beanName 前面添加 & 即可获取。

我们可以通过 FactoryBean 帮助实现复杂的初始化逻辑,例如在 Spring 继集成 MyBatis 的项目中,Mapper 接口没有实现类是如何被注入的?其实 Mapper 接口就是一个 FactoryBean 对象,当你注入该接口时,实际的到的就是其 getObject() 方法返回的一个代理对象,关于数据库的操作都是通过该代理对象来完成。

26. ObjectFactory、FactoryBean 和 BeanFactory 的区别?

根据其名称可以知道其字面意思分别是:对象工厂,工厂 Bean

ObjectFactory、FactoryBean 和 BeanFactory 均提供依赖查找的能力。

  • ObjectFactory 提供的是延迟依赖查找,想要获取某一类型的 Bean,需要调用其 getObject() 方法才能依赖查找到目标 Bean 对象。ObjectFactory 就是一个对象工厂,想要获取该类型的对象,需要调用其 getObject() 方法生产一个对象。
  • FactoryBean 不提供延迟性,在被依赖注入或依赖查找时,得到的就是通过 getObject() 方法拿到的实际对象。FactoryBean 关联着某个 Bean,可以说在 Spring 中它就是某个 Bean 对象,无需我们主动去调用 getObject() 方法,如果想要获取 FactoryBean 本身这个对象,在 beanName 前面添加 & 即可获取。
  • BeanFactory 则是 Spring 底层 IoC 容器,里面保存了所有的单例 Bean,ObjectFactory 和 FactoryBean 自身不具备依赖查找的能力,能力由 BeanFactory 输出。
27. @Bean 的处理流程是怎样的?

Spring 应用上下文生命周期,在 BeanDefinition(@Component 注解、XML 配置)的加载完后,会执行所有 BeanDefinitionRegistryPostProcessor 类型的处理器,Spring 内部有一个 ConfigurationClassPostProcessor 处理器,它会对所有的配置类进行处理,解析其内部的注解(@PropertySource、@ComponentScan、@Import、@ImportResource、@Bean),其中 @Bean 注解标注的方法会生成对应的 BeanDefinition 对象并注册。

28. BeanFactory 是如何处理循环依赖?

前言,下面的“循环依赖”换成“循环依赖注入”比较合适,在 Spring 中通过 depends-on 配置的依赖对象如果出现循环依赖会抛出异常

说明:这里的循环依赖指的是单例模式下的 Bean 字段注入时出现的循环依赖。构造器注入对于 Spring 无法自动解决(应该考虑代码设计是否有问题),可通过延迟初始化来处理。Spring 只解决单例模式下的循环依赖。

在 Spring 底层 IoC 容器 BeanFactory 中处理循环依赖的方法主要借助于以下 3 个 Map 集合:

  1. singletonObjects(一级 Map),里面保存了所有已经初始化好的单例 Bean,也就是会保存 Spring IoC 容器中所有单例的 Spring Bean;
  2. earlySingletonObjects(二级 Map),里面会保存从 三级 Map 获取到的正在初始化的 Bean
  3. singletonFactories(三级 Map),里面保存了正在初始化的 Bean 对应的 ObjectFactory 实现类,调用其 getObject() 方法返回正在初始化的 Bean 对象(仅实例化还没完全初始化好),如果存在则将获取到的 Bean 对象并保存至 二级 Map,同时从当前 三级 Map 移除该 ObjectFactory 实现类。

当通过 getBean 依赖查找时会首先依次从上面三个 Map 获取,存在则返回,不存在则进行初始化,这三个 Map 是处理循环依赖的关键。

例如两个 Bean 出现循环依赖,A 依赖 B,B 依赖 A;当我们去依赖查找 A,在实例化后初始化前会先生成一个 ObjectFactory 对象(可获取当前正在初始化 A)保存在上面的 singletonFactories 中,初始化的过程需注入 B;接下来去查找 B,初始 B 的时候又要去注入 A,又去查找 A ,由于可以通过 singletonFactories 直接拿到正在初始化的 A,那么就可以完成 B 的初始化,最后也完成 A 的初始化,这样就避免出现循环依赖。

问题一:为什么需要上面的 二级 Map

因为通过 三级 Map获取 Bean 会有相关 SmartInstantiationAwareBeanPostProcessor#getEarlyBeanReference(…) 的处理,避免重复处理,处理后返回的可能是一个代理对象

例如在循环依赖中一个 Bean 可能被多个 Bean 依赖, A -> B(也依赖 A) -> C -> A,当你获取 A 这个 Bean 时,后续 B 和 C 都要注入 A,没有上面的 二级 Map的话,三级 Map 保存的 ObjectFactory 实现类会被调用两次,会重复处理,可能出现问题,这样做在性能上也有所提升

问题二:为什么不直接调用这个 ObjectFactory#getObject() 方法放入 二级Map 中,而需要上面的 三级 Map

对于不涉及到 AOP 的 Bean 确实可以不需要 singletonFactories(三级 Map),但是 Spring AOP 就是 Spring 体系中的一员,如果没有singletonFactories(三级 Map),意味着 Bean 在实例化后就要完成 AOP 代理,这样违背了 Spring 的设计原则。Spring 是通过 AnnotationAwareAspectJAutoProxyCreator 这个后置处理器在完全创建好 Bean 后来完成 AOP 代理,而不是在实例化后就立马进行 AOP 代理。如果出现了循环依赖,那没有办法,只有给 Bean 先创建代理对象,但是在没有出现循环依赖的情况下,设计之初就是让 Bean 在完全创建好后才完成 AOP 代理。

提示:AnnotationAwareAspectJAutoProxyCreator 是一个 SmartInstantiationAwareBeanPostProcessor 后置处理器,在它的 getEarlyBeanReference(…) 方法中可以创建代理对象。所以说对于上面的问题二,如果出现了循环依赖,如果是一个 AOP 代理对象,那只能给 Bean 先创建代理对象,设计之初就是让 Bean 在完全创建好后才完成 AOP 代理。

为什么 Spring 的设计是让 Bean 在完全创建好后才完成 AOP 代理?

因为创建的代理对象需要关联目标对象,在拦截处理的过程中需要根据目标对象执行被拦截的方法,所以这个目标对象最好是一个“成熟态”,而不是仅实例化还未初始化的一个对象。

29. Spring 中几种初始化方法的执行顺序?

有以下初始化方式:

  • Aware 接口:实现了 Spring 提供的相关 XxxAware 接口,例如 BeanNameAware、ApplicationContextAware,其 setXxx 方法会被回调,可以注入相关对象
  • @PostConstruct 注解:该注解是 JSR-250 的标准注解,Spring 会调用该注解标注的方法
  • InitializingBean 接口:实现了该接口,Spring 会调用其 afterPropertiesSet() 方法
  • 自定义初始化方法:通过 init-method 指定的方法会被调用

在 Spring 初始 Bean 的过程中上面的初始化方式的执行顺序如下:

  1. Aware 接口的回调
  2. JSR-250 @PostConstruct 标注的方法的调用
  3. InitializingBean#afterPropertiesSet 方法的回调
  4. init-method 初始化方法的调用
30、IOC的优缺点?

优点:

1.加快了开发的效率,可以不用自己一个个new来创建对象,能通过工厂统一的创建。

2.消减了代码之间的耦合,提高了代码的可维护性和灵活性。

3.节约内存,减少了大量的创建对象,维护依赖,销毁对象的代码。

缺点:

1.利用反射来创建对象,单从反射和new的两种创建对象方法上对比,效率上折损了一些。

2.创建对象的步骤变复杂了,不直观,当然这是对不习惯这种方式的人来说的。

3.缺少IDE重构的支持,如果修改了类名,还需到XML文件中手动修改,这似乎是所有XML方式的缺憾所在。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值