创建并运行一个 Spring项目

第一个 Spring 项目

步骤

0

1. 创建一个 spring 项目

(1) 创建一个 maven 项目

spring 是基于 maven 项目的,所以我们首先就要选择 maven 项目。

1-1

(2) 添加 spring 框架支持 ( spring-context + spring-beans )

注意:
从 maven 仓库中找到这两个依赖,最好将两者的版本选择一样的。

1-2

<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.3.18</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework/spring-beans -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-beans</artifactId>
    <version>5.3.18</version>
</dependency>

(3) 配置国内源

我需要事先声明,配置国内源的时候,应在网络良好的情况下配置,否则在后续运行 spring 项目的过程中,可能会出现奇奇怪怪的异常。

1-3

settings.xml 文件中的内容:

<?xml version="1.0" encoding="UTF-8"?>

<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements.  See the NOTICE file
distributed with this work for additional information
regarding copyright ownership.  The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License.  You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied.  See the License for the
specific language governing permissions and limitations
under the License.
-->

<!--
 | This is the configuration file for Maven. It can be specified at two levels:
 |
 |  1. User Level. This settings.xml file provides configuration for a single user,
 |                 and is normally provided in ${user.home}/.m2/settings.xml.
 |
 |                 NOTE: This location can be overridden with the CLI option:
 |
 |                 -s /path/to/user/settings.xml
 |
 |  2. Global Level. This settings.xml file provides configuration for all Maven
 |                 users on a machine (assuming they're all using the same Maven
 |                 installation). It's normally provided in
 |                 ${maven.conf}/settings.xml.
 |
 |                 NOTE: This location can be overridden with the CLI option:
 |
 |                 -gs /path/to/global/settings.xml
 |
 | The sections in this sample file are intended to give you a running start at
 | getting the most out of your Maven installation. Where appropriate, the default
 | values (values used when the setting is not specified) are provided.
 |
 |-->
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
  <!-- localRepository
   | The path to the local repository maven will use to store artifacts.
   |
   | Default: ${user.home}/.m2/repository
  <localRepository>/path/to/local/repo</localRepository>
  -->

  <!-- interactiveMode
   | This will determine whether maven prompts you when it needs input. If set to false,
   | maven will use a sensible default value, perhaps based on some other setting, for
   | the parameter in question.
   |
   | Default: true
  <interactiveMode>true</interactiveMode>
  -->

  <!-- offline
   | Determines whether maven should attempt to connect to the network when executing a build.
   | This will have an effect on artifact downloads, artifact deployment, and others.
   |
   | Default: false
  <offline>false</offline>
  -->

  <!-- pluginGroups
   | This is a list of additional group identifiers that will be searched when resolving plugins by their prefix, i.e.
   | when invoking a command line like "mvn prefix:goal". Maven will automatically add the group identifiers
   | "org.apache.maven.plugins" and "org.codehaus.mojo" if these are not already contained in the list.
   |-->
  <pluginGroups>
    <!-- pluginGroup
     | Specifies a further group identifier to use for plugin lookup.
    <pluginGroup>com.your.plugins</pluginGroup>
    -->
  </pluginGroups>

  <!-- proxies
   | This is a list of proxies which can be used on this machine to connect to the network.
   | Unless otherwise specified (by system property or command-line switch), the first proxy
   | specification in this list marked as active will be used.
   |-->
  <proxies>
    <!-- proxy
     | Specification for one proxy, to be used in connecting to the network.
     |
    <proxy>
      <id>optional</id>
      <active>true</active>
      <protocol>http</protocol>
      <username>proxyuser</username>
      <password>proxypass</password>
      <host>proxy.host.net</host>
      <port>80</port>
      <nonProxyHosts>local.net|some.host.com</nonProxyHosts>
    </proxy>
    -->
  </proxies>

  <!-- servers
   | This is a list of authentication profiles, keyed by the server-id used within the system.
   | Authentication profiles can be used whenever maven must make a connection to a remote server.
   |-->
  <servers>
    <!-- server
     | Specifies the authentication information to use when connecting to a particular server, identified by
     | a unique name within the system (referred to by the 'id' attribute below).
     |
     | NOTE: You should either specify username/password OR privateKey/passphrase, since these pairings are
     |       used together.
     |
    <server>
      <id>deploymentRepo</id>
      <username>repouser</username>
      <password>repopwd</password>
    </server>
    -->

    <!-- Another sample, using keys to authenticate.
    <server>
      <id>siteServer</id>
      <privateKey>/path/to/private/key</privateKey>
      <passphrase>optional; leave empty if not used.</passphrase>
    </server>
    -->
  </servers>

  <!-- mirrors
   | This is a list of mirrors to be used in downloading artifacts from remote repositories.
   |
   | It works like this: a POM may declare a repository to use in resolving certain artifacts.
   | However, this repository may have problems with heavy traffic at times, so people have mirrored
   | it to several places.
   |
   | That repository definition will have a unique id, so we can create a mirror reference for that
   | repository, to be used as an alternate download site. The mirror site will be the preferred
   | server for that repository.
   |-->
  <mirrors>
   <mirror>
        <id>alimaven</id>
        <name>aliyun maven</name>
        <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
        <mirrorOf>central</mirrorOf>        
      </mirror>
    <!-- mirror
     | Specifies a repository mirror site to use instead of a given repository. The repository that
     | this mirror serves has an ID that matches the mirrorOf element of this mirror. IDs are used
     | for inheritance and direct lookup purposes, and must be unique across the set of mirrors.
     |
    <mirror>
      <id>mirrorId</id>
      <mirrorOf>repositoryId</mirrorOf>
      <name>Human Readable Name for this Mirror.</name>
      <url>http://my.repository.com/repo/path</url>
    </mirror>
     -->
  </mirrors>

  <!-- profiles
   | This is a list of profiles which can be activated in a variety of ways, and which can modify
   | the build process. Profiles provided in the settings.xml are intended to provide local machine-
   | specific paths and repository locations which allow the build to work in the local environment.
   |
   | For example, if you have an integration testing plugin - like cactus - that needs to know where
   | your Tomcat instance is installed, you can provide a variable here such that the variable is
   | dereferenced during the build process to configure the cactus plugin.
   |
   | As noted above, profiles can be activated in a variety of ways. One way - the activeProfiles
   | section of this document (settings.xml) - will be discussed later. Another way essentially
   | relies on the detection of a system property, either matching a particular value for the property,
   | or merely testing its existence. Profiles can also be activated by JDK version prefix, where a
   | value of '1.4' might activate a profile when the build is executed on a JDK version of '1.4.2_07'.
   | Finally, the list of active profiles can be specified directly from the command line.
   |
   | NOTE: For profiles defined in the settings.xml, you are restricted to specifying only artifact
   |       repositories, plugin repositories, and free-form properties to be used as configuration
   |       variables for plugins in the POM.
   |
   |-->
  <profiles>
    <!-- profile
     | Specifies a set of introductions to the build process, to be activated using one or more of the
     | mechanisms described above. For inheritance purposes, and to activate profiles via <activatedProfiles/>
     | or the command line, profiles have to have an ID that is unique.
     |
     | An encouraged best practice for profile identification is to use a consistent naming convention
     | for profiles, such as 'env-dev', 'env-test', 'env-production', 'user-jdcasey', 'user-brett', etc.
     | This will make it more intuitive to understand what the set of introduced profiles is attempting
     | to accomplish, particularly when you only have a list of profile id's for debug.
     |
     | This profile example uses the JDK version to trigger activation, and provides a JDK-specific repo.
    <profile>
      <id>jdk-1.4</id>

      <activation>
        <jdk>1.4</jdk>
      </activation>

      <repositories>
        <repository>
          <id>jdk14</id>
          <name>Repository for JDK 1.4 builds</name>
          <url>http://www.myhost.com/maven/jdk14</url>
          <layout>default</layout>
          <snapshotPolicy>always</snapshotPolicy>
        </repository>
      </repositories>
    </profile>
    -->

    <!--
     | Here is another profile, activated by the system property 'target-env' with a value of 'dev',
     | which provides a specific path to the Tomcat instance. To use this, your plugin configuration
     | might hypothetically look like:
     |
     | ...
     | <plugin>
     |   <groupId>org.myco.myplugins</groupId>
     |   <artifactId>myplugin</artifactId>
     |
     |   <configuration>
     |     <tomcatLocation>${tomcatPath}</tomcatLocation>
     |   </configuration>
     | </plugin>
     | ...
     |
     | NOTE: If you just wanted to inject this configuration whenever someone set 'target-env' to
     |       anything, you could just leave off the <value/> inside the activation-property.
     |
    <profile>
      <id>env-dev</id>

      <activation>
        <property>
          <name>target-env</name>
          <value>dev</value>
        </property>
      </activation>

      <properties>
        <tomcatPath>/path/to/tomcat/instance</tomcatPath>
      </properties>
    </profile>
    -->
  </profiles>

  <!-- activeProfiles
   | List of profiles that are active for all builds.
   |
  <activeProfiles>
    <activeProfile>alwaysActiveProfile</activeProfile>
    <activeProfile>anotherAlwaysActiveProfile</activeProfile>
  </activeProfiles>
  -->
</settings>

里面最重要的就是设置国内镜像,我当前设置的是 " 阿里云 "。

1-4

(4) 创一个启动类并添加 main 方法

1-5

2. 将 bean 对象存储到 spring 容器中

(1) 在 spring 项目中添加配置文件

在 【 resources 】目录下创建【 spring-config.xml 】文件,并往里面放入代码。

注意:
这里的【 resources 】目录不能选错,而 " xml 文件 " 的名字可以自定义,但是最好和我一样,顾名思义,它代表 " spring 配置文件 ",这是一种规范。

2-1

【 spring-config.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">

</beans>

(2) 创建一个 bean 对象

假设我们创建一个 User 类,并计划外面往里面传入一个 name 参数。

2-2

(3) 将 bean 对象通过配置文件注册到 spring 中

<?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="beans.User"> </bean>
</beans>

将 bean 标签中的," id " 和 " class " 想象成一种 Map 键值对结构,后续通过 " id ",就可以得到 " class " 类种的一些信息。

注意:

" class " 的写法是固定的,精确到哪个包下的哪个类。
" id " 的写法是非固定的,但是最好写成小驼峰,并与类的名字联系起来。这样有两点好处,一是对外规范;二是下次利用 " id " 从 spring 容器中取对象的时候,可以很好地记忆。

2-3

3. 将 bean 对象从 spring 中取出来

(1) 在启动类中,先得到 spring 上下文对象
(2) 通过上下文对象提供的方法,获取我们自己需要使用的 bean 对象
(3) 使用 bean 对象

public class Run {
    public static void main(String[] args) {
        // 1. 得到 spring 上下文对象
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");

        // 2. 根据上下文对象提供的方法获取到 bean
        User user = (User) context.getBean("user");
        // User user1 = new User(); // 传统写法

        // 3. 使用
        user.hello("李明");
    }
}

展示结果:

3-1

注意

注意1

1. 在上面的代码中,我们和以往的做法不同,以往是通过 new 一个 User 类,来创建一个 user 对象。而当前的做法是,我们先往 spring 容器中放对象,然后再将对象取出来。

至于为什么要这么做,我的上一篇博客就介绍到了它的关键之处:解耦。

我们平时利用 new 的方式创建一个对象,当我们对于类的构造方法进行改变时 ( 比方说临时加一个参数 ),那么,一个项目中所有用到 User 类的其他类,就统统需要修改。然而,利用 spring 容器作为中间介质,情况就不同了,从 spring 取出来的对象,是不依赖原始类的,也就是说,当我们之前的 User 类发生了改变,与 spring 中的对象毫无关联。

注意2

2. 在 main 方法中,一些关于 spring 配置文件的参数,在传入方法的时候,不能出错,否则就会出现这样那样的异常。所以,在我们之前配置 " xml " 文件的时候,就需要保持规范。

如果修改之后,编译过程的代码没问题,但依旧出现了异常,这可能就是缓存的问题。这时候,我们就需要手动删除 【target】目录下的所有文件,再次编译。因为 JVM 实际上最终运行的是 " .class " 文件,我们只需要把之前缓存的 " .class " 文件清空,再次利用 IDEA 编译的时候,就会重新生成 " .class " 文件,这样就可能有效果。

3-2

注意3 ( 经典面试题 )

3. 在上面的例子中,我们获取 spring 上下文对象,是通过 ApplicationContext 这个类来完成的,实际上,我们也可以通过 BeanFactory 这个类 来作为 spring 的上下文对象。

public class Run {
    public static void main(String[] args) {
        // 1. 得到 spring 上下文对象
        BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("spring-config.xml"));

        // 2. 根据上下文对象提供的方法获取到 bean
        User user = (User) beanFactory.getBean("user");

        // 3. 使用
        user.hello("杰克");
    }
}

展示结果:

3-3

就因为上面的两个不同的获取对象方法,所以引入了一个经典面试题:

谈谈 ApplicationContext 和 BeanFactory 之间的区别和联系。

(1) 相同点:都可以利用 " getBean 方法 ",从容器中获取 bean 对象。

(2) 不同点:
① ApplicationContext 属于 BeanFactory 的子类,BeanFactory 只提供了基础访问Bean 的方法,而 ApplicationContext 除了拥有 BeanFactory 的所有功能之外,还拥有一些独立的特性,比如对国际化的支持、资源访问的支持、以及事件和传播等方面的支持。

因为在 Java 中,一般来说,子类继承父类,那么子类直接就会拥有父类的功能,然而,对于父类来说,子类拥有的独立特性,父类很多是没有的。因为这些独立的特性,就是子类衍生出来的,也是作为区分父类的一种表现形式。

② 从性能方面来说二者是不同的,BeanFactory 是按需加载,它很像单例模式中的懒懒汉模式,当我们需要用到了某个 bean 对象,它才会临时加载。
然而,ApplicationContext 是饿汉模式,在我们创建此类的时候,它会一次性地将所有的 bean 对象都加载起来,以备后续使用。

怎么验证呢?我们可以再创建一个 " Admin " 类,然后在 " Admin " 和 " User " 的构造方法中,做一些打印处理,然后在启动类的 main 方法中,看看两个类的加载的现象即可。结果就是,ApplicationContext 直接加载了 " Admin " 和 " User " ,而 BeanFactory 什么也没做。

③ 二者并无好坏,只能说根据场景自由选择吧,但是,ApplicationContext 确实是我们日常开发中,用的最多的情况。

注意4

4. 上述的 " getBean 方法 " 有很多重载,传入的参数可以由我们自己控制。

// 1
User user = (User) context.getBean("user");
// 2
User user = context.getBean(User.class);
// 3
User user = context.getBean("user", User.class);

第二种不建议使用,虽然写法简单,但容易出问题。当同一个类多次被注入到 spring 中的时候,就会出现异常。

第三种建议使用,因为它指明了当前需要找寻的是哪个类,所以就避免了强制转换这样的麻烦事。

第二个 Spring 项目

在上面的第一个 Spring 项目中,我们是通过将 bean 对象通过配置文件注册到 spring 中,之后再从配置文件中取出对象来,这一部分需要我们手动去输入 " id " 和 " class " 属性。

而在我们即将实现的第二个 Spring 项目中,我们是完全通过注解的方式进行了,这样一来,就可以更简单的存储对象和读取对象了。

如果说第一个 Spring 项目是一个手动挡的汽车,那么第二个 Spring 项目就是一个自动挡的汽车,因为实际上,一个注解就只有一行代码而已,通过注解这样的方式,很多工作并不需要我们自己动手去做,而是交给 spring 框架去做。

1. 搭建项目环境

(1) 创建一个 maven 项目
(2) 添加 spring 框架支持 ( spring-context + spring-beans )
(3) 配置国内源
(4) 创一个启动类并添加 main 方法
(5) 配置 " spring-config.xml " 文件

前 4 步,在第一个 spring 项目中,已经体现出来了。现在,我们来实现第 5 步。

我们在 spring-config.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"
       xmlns:content="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
    <content:component-scan base-package=" ">
    </content:component-scan>
</beans>

其中有一个 " base-package " 这样的属性十分重要!它表明了我们所有存放到 spring 中的 bean 的根路径。

如下图所示,只要我们涉及 spring 的类,就必须放在当前的 " beans " 包下,或者重新创建一个包 " abc " ,把类放在 " abc " 包下,但是 " abc " 包也应该在 " beans " 包下。也就是说," beans " 目录可以是 spring 类的父级目录,也可以是 " 爷爷目录 ",依然可以是 " 祖先目录 " …但不能是子孙目录。

4-1

2. 将 bean 对象存储到 spring 容器中

要想简单地将对象存储在 Spring 中,有如下两种注解类型可以实现。
必须明确,两种注解都是 spring 框架提供的,也就是从我们之前从 maven 仓库引入的依赖所提供的。两种注解实际上在底层就是对应着各自的 " .class " 文件。

(1) 类注解:
@Controller、@Service、@Repository、@Component、@Configuration.

(2) 方法注解:
@Bean.

(1) 类注解

创建一个 UserController 类,作为 bean 对象。

5-1

在 UserController 类的上方,添加一个 " @Controller " 注解。

@Controller
public class UserController {
    public void hello() {
        System.out.println("你好,Controller");
    }
}

在启动类中,我们通过 " getBean " 方法进行测试,结果发现并没有问题。

public class Run {
    public static void main(String[] args) {
        // 1. 得到 spring 上下文对象
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");

        // 2. 根据上下文对象提供的方法获取到 bean
        UserController userController = context.getBean("userController", UserController.class);

        // 3. 使用
        userController.hello();
    }
}

5-2

注意

注意1 五个类注解之间的联系

1. 在上面的程序中,我只测试了一个 " @Controller " 注解,如果我们使用其他四个类注解,在当前的打印功能上,它们其实都是一样的,我就不额外展示了。

但是我们应该着重理解这五个类注解,到底都是什么意思?能用来干什么?它们之间有什么区别和联系?

如下图所示,在一个企业项目中,最少会有下面的四层,或许会比四层多,但一定不会比下面的四层少。这四层,我们利用这四个注解来进行解释。

6-1

@Configuration:配置层

配置层用于存放当前项目的所有配置,其实在我们日常学习的时候,感受不到配置文件的分层问题,因为,平时我们可能不做项目,就将一个项目的所有文件放在一个目录下。而在实际的工作开发中,一个项目,可能有成千上万个文件,多数情况下,这些目录并不仅仅只有你的代码,也有你同事的代码。而配置层就可以通过一些配置类,来管理这些文件。当我们需要对当前项目的一些配置进行修改、维护的时候,只需要利用这一层来实现即可。

@Controller:控制层

控制层主要用作前端参数校验。比方说,现在我们通过前端登录一个网站,那么我们至少需要输入账号和密码这两个参数供后端验证,那么此时,就可以利用控制层来实现登录逻辑,验证成功后,才能到达下一层 " @Service ",验证失败,直接返回错误给前端用户。

举个例子:这就和当下疫情一样,当你的健康码是绿码的时候,你才能通行,否则,就可能需要马上去做核酸…那么控制层的作用就体现出来了,它就像安检关卡一样。

@Service:服务层

服务层主要实现了数据的组装和接口调用。

@Repository:数据持久层 ( DAO 层 )

数据持久层直接与数据库打交道,在这一层,可以实现数据库的增删查改。

@Component:上面四个注解的 " 父亲 "

查看上面四个注解的源码,就可以发现,它们四个都有 " @Component ",说明它们本身就是基于 " @Component " 实现的,换句话说,它们四个就是 " @Component " 的 " 孩子 "。

6-2

总结:一个 spring 项目为什么要分层呢?

举个例子,当我们去某家公司面试,公司会有 Java 面试的地方、前端面试的地方、C++ 面试的地方,这些地方我们可以理解为【配置层】分配好的区域。假设我们去面试 Java 后端工程师,首先,我们需要通过安检,出示我们的健康码,如果是绿码才能正常进入公司,此时这个安检就可以理解为【控制层】。当我们通过了安检,可能就会有服务人员告诉你,Java 面试在某某楼、某某房间,你需要通过他给你的提示路线,才能找到具体的面试地点,此时服务人员就可以理解为【服务层】。只有你到达了最终目的地,面试官才能面试你,那么最后一步,我们才能视为【持久层】。

再回到上面的问题,在实际项目中,我们不可能通过一次执行,就直接对数据库进行操作,这不符合权限、也不安全,更不科学。

注意2 BeanName 的命名规则

打开下面的一个类文件,查看底层类的源码,滑动到页面最后,打开 " decapitalize " 方法,再看其源码。

6-3

这一段源码核心地方在于下面的红框部分,如果第一个字母和第二个字母都是大写,那么直接返回;否则,就会将第一个字母变成小写,再返回。 现在,我们使用 " getBean " 方法,传入的参数就有依据了。

此外,值得注意的是,这是 JDK 提供的标准。

6-4

在下面,对源码进行测试,最终发现与我们理解的是一致的。

public class test {
    public static void main(String[] args) {
        String str1 = "UserController";
        String str2 = "APIController";
        System.out.println(Introspector.decapitalize(str1));
        System.out.println(Introspector.decapitalize(str2));
    }
}

6-5

综上所述,我们在今后使用 " getBean " 方法的时候,就只应该注意我们自己定义类的前两个字母即可,如果前两个字母都是大写,传入的就是原类名;反之,就将第一个字母变成小写即可。

UserController userController = context.getBean("userController", UserController.class);
APIController apiController = context.getBean("APIController", APIController.class);

(2) 方法注解

@Controller
public class UserBean {
    @Bean
    public User user1() {
        User user = new User();
        user.id = 1;
        user.name = "露丝";
        return user;
    }

    @Bean(name = {"user", "userinfo"})
    public User user2() {
        User user = new User();
        user.id = 2;
        user.name = "杰克";
        return user;
    }
}

注意

(1) " @Bean " 需要和五大类注解配合使用,才能生效。

(2) " @Bean " 只能放在方法上面,将当前方法返回的对象,存储到 spring 容器中。

(3) 使用 " @Bean " 存储到 spring 容器后,再从容器中取出来 bean 对象时,对传入 " getBean " 方法的参数,也有讲究。如果当前没有重命名,就传入 " @Bean " 注释的方法名;如果对 " @Bean " 重命名了,那么就只能使用新的名字,原来的方法名失效。重命名的规则很简单,在 " @Bean " 后面加上 name 数组即可,可以重新使用多个名字。

对上面的程序进行验证:

User user = context.getBean("user1", User.class); // true
User user = context.getBean("user2", User.class); // false
User user = context.getBean("user", User.class); // true
User user = context.getBean("userinfo", User.class); // true

3. 对象装配来获取 bean 对象

以往我们是通过 new 一个对象的传统写法,来从一个类中拿到外部类的对象。现在,我们使用对象装配的方式将一个类的对象放到另一个类中。对象装配也叫做对象注入,实际上就是将 bean 对象取出来放到某个类中,接着就可以在这个类中,直接使用 bean 对象了。

对象注入的实现方法有下面三种:

(1) 属性注入
(2) 构造方法注入
(3) Setter 注入

(1) 属性注入 bean 对象 ( 字段注入 )

我们往 " UserController " 类中,注入 " UserService " 类的对象,之后,我们就可以通过 " UserController " 类拿到 " UserService " 类的字段、方法等数据了。下面的 Run 启动类,只是用来测试一下,对象装配是否成功。

7-1

注意

以往我们是通过 new 一个对象的传统写法,来从一个类中拿到外部类的对象。

UserService userService = new UserService();

现在,我们是通过属性注入的方式,拿到外部类的 bean 对象。
但使用属性注入的前提是,我们需要保证被注入的对象是一个 bean 对象,也就是说,它得包含五大类注解才行。

@Autowired
private UserService userService;

这就像:一个容器里面装了一个大盒子,这个大盒子又装了一个小盒子。
当我们从 Spring 容器中,将 UserController 这个大盒子取出来的时候,UserService 这个小盒子,也会被取出来。

7-1-1

(2) 构造方法注入 bean 对象

我们往 " UserController2 " 类中,注入 " UserService " 类的对象,之后,我们就可以通过 " UserController2 " 类拿到 " UserService " 类的字段、方法等数据了。下面的 Run 启动类,只是用来测试一下,对象装配是否成功。

7-2

注意

注意1

下面这行代码,并不是属性注入,因为它上面并没有 " @Autowired " 这样的注解,所以很明显,它就是是一个值为 null 的一个字段而已。

private UserService userService;

接着,由于构造方法在外部 new 的时候,第一时间就能够使用,也就是说,构造方法的优先级很高。所以,通过构造方法注入,就是将原先值为 null 的 userService 字段,赋值了新的对象,这就好像激活了 UserService 类一样。

@Autowired
public UserController2(UserService userService) {
    this.userService = userService;
}
注意2

利用构造方法注入 bean 对象的时候,如果被注入的类出现了多个构造方法,我们只能使用一个构造方法来进行 bean 对象的注入,也就是只将其中一个构造方法设置为注解 " @Autowired "。

(3) Setter 注入 bean 对象

我们往 " UserController3 " 类中,注入 " UserService " 类的对象,之后,我们就可以通过 " UserController3 " 类拿到 " UserService " 类的字段、方法等数据了。下面的 Run 启动类,只是用来测试一下,对象装配是否成功。

7-3

注意

Setter 方法注入和构造方法注入的思想基本相同,这里我就不展开介绍了。但是它们之间也略有不同,因为构造方法可能根据参数不同,所以会有多个构造方法,然而,Setter 方法则不同,它只有唯一一个,它只针对于某个字段进行设置。

经典面试题1

对象注入的方式有哪些?
属性注入、构造方法注入、Setter 注入三者的区别?

答:

① 属性注入:写法最简单,但通用性较差,它只能用于 IOC 容器,如果是用于非 IOC 容器下,就会出现空指针异常。

② 构造方法注入:通用性好,此外,由于构造方法是一个类的优先级最高的成员,所以使用构造方法注入,就能够确保注入对象不会出差错,这也是现阶段官方推荐的写法。但是,由于构造方法可能根据参数不同,所以会有多个构造方法,如果多个构造方法用到了同样的对象注入,那么程序就会显得比较冗余。

③ Setter 注入:官方早期的推荐写法,但通用性依旧较构造方法差,因为在 Java 中的 Setter 方法,放在其他语言之中,可能就行不通了。

④ 官方推荐的写法是一种理论,我们在实际开发中,只要符合对应场景,用到最多的还是属性注入,因为理论和实践还是不一样的,实践以高效、实用为主。

经典面试题2

在进行对象注入时,除了可以使用 " @Autowired " 注解之外,我们还可以使用 " @Resource " 进行注入。

对象注入可以使用的哪两种注解 / 哪两种关键字?

① 出身不同:" @Autowired " 来自于 Spring 框架,而 " @Resource " 来自于 JDK.

② 用法不同:注解 " @Autowired " 支持属性注入、构造方法注入 和 Setter 注入,然而,注解 " @Resource " 不支持构造方法注入。

③ 支持的参数不同:" @Autowired " 只支持 required 参数设置,而 " @Resource " 支持更多的参数设置,比如 name、type 设置。

同一个类型的对象注入多次的问题

创建一个 User 类,作为待存储的类。

public class User{
    public int id;
    public String name;

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

在 UserBean 类下,通过注解 " @Bean " ,将 User 类的对象往 spring 容器中注入了两次,一个 beanName 为 " user1 ",一个 beanName 为 " user2 ".

@Controller
public class UserBean {
    @Bean
    public User user1() {
        User user = new User();
        user.id = 1;
        user.name = "露丝";
        return user;
    }

    @Bean
    public User user2() {
        User user = new User();
        user.id = 2;
        user.name = "杰克";
        return user;
    }
}

创建一个 UserController4 类,进行 User 类的对象注入。

@Controller
public class UserController4 {

    @Autowired
    private User user;

    public void hello() {
        System.out.println("你好," + user.name);
    }
}

利用 Run 启动类,进行测试。

public class Run {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
        UserController4 userController4 = context.getBean("userController4", UserController4.class);
        userController4.hello();
    }
}

通过 Run 类测试,我们发现,如果我们直接注入一个 beanName 为 " user " 的一个对象,就会出现如下错误。这其实很好理解,因为当初存入的对象,一个名为 " user1 " ,一个名为 " user2 ",现在,我们却让 spring 容器为我们找一个名称不匹配的对象,自然是找不到的,因为容器中 User 类的对象不唯一。

8-1

备注: 如果在 Spring 容器中,同一个类型的对象只有一个,那么,就算我们对象注入的名字与那唯一对象的名字不匹配,也不影响 spring 能够找出来。然而,同一个类型,有多个对象,就算 spring 再智能,它也不知道你究竟想要哪个了。

解决方案

综上所述,我们就知道了,往 spring 中存入 " bean 对象 " 的时候,没有发生问题,但是,从 spring 中取出 " bean 对象 " 的时候,却发生了问题。那么,我们只需要修改【对象注入】的弊端即可。

方案1 精确描述 beanName

如果你想要 user1,属性注入的时候就直接注入 user1;
同样地,如果你想要 user2,就直接注入 user2.

@Autowired
private User user1;

public void hello() {
    System.out.println("你好," + user1.name);
}

测试结果:

8-2

@Autowired
private User user2;

public void hello() {
    System.out.println("你好," + user2.name);
}

8-3

方案2 通过 " @Resource " 设置 【name 参数】 来重命名

之前,我们提到 " @Autowired " 和 " @Resource ",发现 " @Resource " 在提供的参数方面,更胜一筹。所以我们直接利用其 【name 参数】进行重命名设置即可,这样,就相当于告诉了 spring 容器,你到底取的是哪个对象了。

@Resource(name = "user1")
private User user;

public void hello() {
    System.out.println("你好," + user.name);
}

// 你好,露丝
@Resource(name = "user2")
private User user;

public void hello() {
    System.out.println("你好," + user.name);
}

// 你好,杰克
方案3 通过 " @Autowired " + " @Qualifier " 的方式来限定名称

实际上, " @Qualifier " 注解只有一个参数,就是 value,可写可不写。但我们最好还是加上 value,因为这样更准确,此外,如果有一天,spring 框架升级了 " @Qualifier " 注解,让它不止一个参数,那么我们也能很好地预防意外。

@Autowired
@Qualifier(value = "user1")
private User user;

public void hello() {
    System.out.println("你好," + user.name);
}

// 你好,露丝
@Autowired
@Qualifier(value = "user2")
private User user;

public void hello() {
    System.out.println("你好," + user.name);
}

// 你好,杰克

总结

之前,我们说第一个 spring 项目需要依赖 " xml " 配置文件,来进行存储和取出 bean 对象的,这就像开手动挡的车一样。之后,在第二个 spring 项目中,我们又通过注解的方式,来进行存储和读取 bean 对象,这时候,就像开自动挡的车子一样了。

而实际上,这就是一个随着时代发展,技术升级的过程,后面还有更现代的框架可供使用。但是,我们需要记住一点,在当前的 spring 框架中,核心功能就是存储和读取 bean 对象,所以,项目不管以什么方式实现,牢记核心才是至上的。

  • 16
    点赞
  • 59
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

十七ing

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值