1. Spring框架简介

框架:在具体的表现来看,是一系列的jar包文件,普通的jar包只是工具包,而框架更多的是解决了某些特定存在的问题,例如开发效率的问题、执行效率的问题、安全性的问题、代码的可维护性的问题等……使得开发人员在使用框架开发项目时,不必再关心这些问题,或者这些问题已经得到很大程度的缓解!在使用框架时,可能还需要遵循框架的特定使用方式来编程!

某一些框架可以算作是容器。

在学习框架课程时,需要有一些“不求甚解”的心态,切不可尝试深度理解框架!更多的应该是掌握框架的正确使用方式,对框架的原理不要过度纠结!

Spring框架主要是用于创建对象和管理对象的!

网摘:

什么是Spring框架?

​ Spring是一种轻量级框架,旨在提高开发人员的开发效率以及系统的可维护性。

​ 我们一般说的Spring框架就是Spring Framework([ˈfreɪmwɜːrk] 框架),它是很多模块的集合,使用这些模块可以很方便地协助我们进行开发。这些模块是核心容器、数据访问/集成、Web、AOP(面向切面编程)、工具、消息和测试模块。比如Core Container中的Core组件是Spring所有组件的核心,Beans组件和Context组件是实现IOC和DI的基础,AOP组件用来实现面向切面编程。

​ Spring官网(https://spring.io/)列出的Spring的6个特征:

核心技术:依赖注入(DI),AOP,事件(Events),资源,i18n,验证,数据绑定,类型转换,SpEL。

测试:模拟对象,TestContext框架,Spring MVC测试,WebTestClient。

数据访问:事务,DAO支持,JDBC,ORM,编组XML。

Web支持:Spring MVC和Spring WebFlux Web框架。

集成:远程处理,JMS,JCA,JMX,电子邮件,任务,调度,缓存。

语言:Kotlin,Groovy,动态语言。

列举一下Spring的重点模块?

Spring Core([kɔːr] :Spring核心):基础,可以说Spring其他所有的功能都依赖于该类库。主要提供IOC和DI功能。

Spring Aspects( [ˈæˌspɛkts] 哎死百科死):该模块为与AspectJ的集成提供支持。

Spring AOP:提供面向方面的编程实现。

Spring JDBC:Java数据库连接。

Spring JMS:Java消息服务。

Spring ORM:用于支持Hibernate等ORM工具。

Spring Web:为创建Web应用程序提供支持。

Spring Test:提供了对JUnit和TestNG测试的支持。

2. 创建Maven Project项目,配置Spring框架

2.1创建Maven并配置Spring依赖

(1)第一步:创建Maven项目

新建一个Maven Project,在创建过程中,勾选中Create a simple project选项,在Group Id一栏输入cn.mypro,在Artifact Id一栏输入spring01,下方的Packaging选择war,刚创建好的项目因为没有web.xml文件会报错,生成该文件即可。

本次课程在Packaing一栏也可以选择jar,并不一定必须选择war,后续的课程会要求必须使用war,所以,提前体验也是可以的!

(2)第二步:配置pom.xml的Spring的依赖

打开项目的pom.xml文件,在其中添加Spring的依赖!在使用Spring框架时,需要添加的依赖是spring-context,具体代码是:

 <dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.1.5.RELEASE</version></dependency></dependencies>

当需要查询某个依赖时,在浏览器中搜索“mvn”关键字(maven的意思),打开 http://www.mvnrepository.com 网站,并搜索依赖的名称(spring-context),其实就是搜索的artifactId(最好记一下这个groupId),在查询结果中找到匹配度最高的依赖,选择版本,就可以查询到依赖的代码。

此次依赖名称为:spring-context

在搜索栏搜索后,找到名字完全一样的,点击后,筛选用的人多的,下载。这里是下载的5.1.5版本的。

对于版本要求,最低要求是4.2以上的就行。

课后笔记小节:

1.dependencies复数形式,表示可以添加多个依赖。里面的dependency表示添加的单独依赖。
2.粘贴后,用Source(根源)中Format(安排…的版式)来排版,快捷键是:Ctrl+Shift+f.

3.粘贴后,在Java Resources——Libraries——Maven Dependencies中有关于5.15版本的“奶瓶”

(3)第三步:生成部署描述符,即web.xml(WEB-INF文件夹下)

在项目中:

Deployment Descriptor: spring01——右键——点击——Generate Deployment Descriptor

点最长的,右键,然后点最长的“奶瓶”,生成web.xml(WEB-INF文件夹下)

翻译:

Deployment:部署

Descriptor:叙词;段描述符;描述符;角色描述

Generate :生成

Stub:存根;短线;末节

Deployment descriptor(部署描述符): 部署描述符指的是配置文件对于一个假象部署到一些容器/发动机。

Generate Deployment Descriptor :生成部署描述符

(4)第四步:添加:applicationContext.xml文件(spring.xml)

如果该文件提示错误,可以不处理,并不影响开发和运行!因为是连接的国外的网站,所以下载慢是正常的。

可以在Spring的配置文件中配置由Spring创建并管理的对象!

在创建applicationContext.xml文件之后,就把这个文件改成了spring.xml文件。

后面会介绍无参数构造方法等。

(5)第五步:关联Tomcat(汤姆猫)

点击项目右键——点击属性(Properties)——查找Targeted Runtimes ,勾选对应的Apache Tomcat版本

翻译:Targeted Runtimes :目标运行时

2.2检查与方法:

1.在Eclipse软件中,点击上面的Help——最底下有一个About Eclipse IDE,能看到Eclipse版本,最好是换成4.11以上的版本。

2.阿里云仓库Windows——Preferences——Maven下面User Settings中 open file中配置的是阿里云的仓库:

http://maven.aliyun.com/nexus/content/groups/public/

如果上述和这两个都没有问题,而项目还在报错,那么只有两种解决方法

方法1:清理项目:Project——clean,清理项目

方法2:强制更新项目中Maven环境的jar包:项目右键——Maven——Update Project,勾选要更新的项目,并且勾选下面的Force Update of Snapshots/Releases,表示强制更新的意思。

​ 翻译:Force:强迫,迫使

​ Snapshots:简介;简要说明的复数形式

​ Releases:释放、发行、发布

如果下载的jar包是坏的,那就把坏的文件的整个文件夹删掉,然后下载其他版本的jar包,比如换成5.1.6,不需要在去网页上搜索复制,直接在pom.xml文件中更改版本号就行,就会自动重新下载

3.关于创建项目使用的JDK不是Java1.8,右键项目,找到最下面的属性Properties,在其中搜索Facets,找到Project Facets,找到Java,在Version中选择1.8版本。

翻译:Facets:钻石刻面体;小平面;切面;晶面。

​ Version:版本,型式。

4.设置使用eclipse创建Maven项目时,默认使用的jdk是1.8

(1)打开eclipse->window->Preferences->Maven(没有Maven这个选项的说明eclipse没有装maven插件)->User Settings,找到Global Settings(全局设置)和User Settings(用户设置),

(2)打开其中的两个settings.xml文件,找到<profiles><profiles/>这对标签,可使用键盘ctrl+f搜索找profiles这个关键字,在这对标签里面添加如下代码:

    <profile>  <id>jdk-1.8</id>  <activation>  <activeByDefault>true</activeByDefault>  <jdk>1.8</jdk>  </activation>  <properties>  <maven.compiler.source>1.8</maven.compiler.source><maven.compiler.target>1.8</maven.compiler.target> <maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>  </properties>  </profile>

(3)点击Update Settings(更新设置),完成。

3.通过Spring框架创建对象,并从中获取所需的对象

可以在Spring的配置文件中配置由Spring创建并管理的对象!

3.1介绍无参数构造方法:

1)用Spring创建一个Date类的对象

假设需要由Spring创建一个Date类的对象,则在该文件中(src/main/resources中的spring中)添加:

添加一个<bean></bean>节点,这个节点表示需要由Spring创建和管理的对象。

需要在里面配置两个属性:class属性、id属性。

class属性,需要写类的全名,写完后,按住Ctrl键动一下,出现下划线即为正确。

Spring创建对象:class="java.util.Date"写完后,这个项目启动,一旦加载这个对象,Spring就会创建这个对象了。

Spring管理对象:id=“date”,自定义名称,后续管理,获取对象通过id名字来获取对象。

<!-- id属性:自定义名称,后续将根据这个名称来获取对象,推荐使用类名将首字母改为小写 -->
<!-- class属性:需要Spring框架创建哪个类的对象,必须取值是类的全名,即包名与类名 -->
<bean id="date" class="java.util.Date"></bean>

至此,已经配置好了需要由Spring管理的对象,后续,当该配置文件被加载时,Spring框架就会创建java.util.Date类的对象,开发人员也可以通过id属性值date获取该对象!

可以创建SpringDemo类,输入:

四个步骤:

package cn.mypro.spring;import java.util.Date;import org.springframework.context.support.ClassPathXmlApplicationContext;public class SpringDemo {public static void main(String[] args) {// 1.加载Spring的配置文件,获取Spring容器ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");/** ClassPathXmlApplicationContext是专门用来读applicationContext.xml文件的工具* 打出来ClassP,然后快捷键,自动导包* 取名ac,因为ApplicationContext缩写* =new之后,直接快捷键回车,出来构造方法* 在构造方法的参数里面,用字符串把配置文件的名字写上:"applicationContext.xml"* 名字建议复制粘贴 */// 2.从Spring容器中获取由Spring管理的对象// getBean()方法的参数就是配置文件中<bean>节点的id值/** 在构造方法中读了"applicationContext.xml"配置文件,其实ac也就是Spring容器* Spring是一种容器,容器的特征是可以从这个容器中获取对象* 方法是getBean方法,方法内参数是配置文件中<bean>节点的id值*/Date date = (Date) ac.getBean("date");//import java.util.Date;导包要保持一致,<bean中写的就是 class="java.util.Date"这个包>//还需要类型转换// 3.测试System.out.println(date);// 4.关闭,释放资源ac.close();}}

Java Application的方式运行以上main()方法即可执行。

第一次运行:双击main,然后运行:输出得到:此时此刻程序运行的时间。

其实就是这4行代码:

注意:凡是能够通过调用方法或者添加参数等方式可以解决的问题,就不要使用强制转换

package cn.mypro.spring;
import java.util.Date;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringDemo {public static void main(String[] args) {// 1.加载Spring的配置文件,获取Spring容器,这个容器就是acClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");//2.从Spring容器中(即ac中)获取由Spring管理的对象,把<bean>节点的id值作为参数//传到getBean方法中,然后获取到date对象Date date  =(Date) ac.getBean("date");//可以写成Date date = ac.getBean("date",Date.class);//凡是能够通过调用方法或者添加参数等方式可以解决的问题,就不要使用强制转换。//3.测试,也就是输出这个data对象System.out.println(date);ac.close();}
}

2)用Spring创建一个自定义类的对象

如果我们自己写的一个类(也就是自定义),也希望被Spring所管理,做法是一样的。

如果需要Spring框架创建自定义的某个类,**比如:**可以先创建例如cn.mypro.spring.User类,再在Spring的配置文件中添加配置:每一个<bean>节点都表示一个由Spring创建和管理的对象

<bean id="user" class="cn.mypro.spring.User"></bean>

在执行程序中,补充获取对象的代码:(除了测试用的输出语句其他代码不变,新增这句代码就行)

User user = (User) ac.getBean("user");

然后,可以通过输出该对象,以检查配置效果。

注意: 以上创建对象的方式仅限于对应的类存在无参数构造方法!如果某个类并不存在无参数构造方法,则不适用于以上做法!

(注意:无论是java.util.Date还是cn.mypro.spring.User这两个类,都有无参数构造方法。)

在java中,如果自己没有写一个构造方法,则在编译时,会添加默认的构造方法

注意:

1.是在编译期添加默认构造方法,所谓编译期,就是把java源文件变成class文件这个过程就叫编译期

也就是说java源代码中可能没有这句话,但是对应的class文件内就有这句话。

2.“编译器添加无参构造方法”这种说法不精确,应该是“编译器添加了默认构造方法”。

默认构造方法,有3个特点:

特点1:无参数;

特点2:权限是公有的public;

特点3:自动调用父类的无参构造方法;

package cn.mypro.spring;
public class User {//以下为默认的构造方法public User() {super();/** 1.super没有打点,直接括号,表示:调用的是父类的构造方法*      而且默认添加的代码内,调用父类的构造方法时,调用也是父类中无参数构造方法* 2.那么:如果父类中没有无参的构造方法,而子类中又不写构造方法,那么相当于自动添加*     这个public user()的方法,那么代码就会出错。* 3.super();这句话写与不写都行,因为即使是不写,在编译期,还是会加上这句话。*/}
}

3.2介绍静态工厂方法:

如果某个类确实不存在无参数构造方法,但是,有静态工厂方法,也是可以的!

静态工厂方法:指的是该类存在某个被static修饰的方法,并且该方法的返回值类型就是当前类的类型!

例如Calendar类就是有静态工厂方法的:

public abstract class Calendar {//Calendar这个类本身是抽象类,是不能直接new的,框架也是new不出来的,但是,是不能直接创建对象// 以下getInstance()方法就是静态工厂方法//一般来说工厂方法的命名,都叫getInstancepublic static Calendar getInstance() {// Calendar这个类中本省就存在着:getInstance()这个方法  // ... 方法的具体实现//一般来说工厂方法的命名,都叫getInstance()}
}

配置:

在src/main/resources中的spring中的<bean>标签中,除了id和class属性,多写一个factory-method属性

factory-method(字面意思翻译:工厂方法):工厂方法的名称,不需要添加参数的括号,且必须与方法名称完全一致。如getInstance中的i要大写。即为:factory-method="getInstance"

<!-- factory-method:工厂方法的名称,不需要添加参数的括号,且必须与方法名称完全一致 -->
<bean id="calendar" class="java.util.Calendar"factory-method="getInstance">
</bean>

配置完成后,在SpringDemo类中添加代码:

Calendar calendar = (Calendar)ac.getBean("calendar");
System.out.println(calendar);

另外,如果自定义的某个类也是没有无参数构造方法的,也可以通过这样的方式进行处理,例如:

package cn.mypro.spring;public class Person {// 添加带某参数的构造方法,使之不能通过默认构造方法创建对象public Person(Object object) {}// 添加静态工厂方法public static Person newInstance() {//返回值必须是Person,方法名字叫什么都行//但是一般来说工厂方法的命名,都叫getInstance,但是这个是new,所以叫newInstancereturn new Person(null);//返回什么的值也不重要,因为构造方法没有用到返回值}
}

然后,在Spring的配置文件中配置为:

<bean id="person" class="cn.mypro.spring.Person"factory-method="newInstance">
</bean>

然后,在SpringDemo中添加代码:

Person person = (Person)ac.getBean("person");
System.out.println(person);

3.3介绍实例工厂方法:

如果某个类既没有无参数构造方法,也没有静态工厂方法,如果这个类存在实例工厂方法,也是可以的!

实例工厂方法:实例,即为对象,也就是说调用另外一个对象的方法,能够创建出Student的对象出来。

例如存在:

package cn.mypro.spring;
//这个类的目的只是为了举例,使之不能通过默认构造方法创建对象
public class Student {// 添加带某参数的构造方法,使之不能通过默认构造方法创建对象public Student(Object object) {}}

另外,还需要有一个工厂类,后续,创建工厂类的对象后,调用工厂中的某个方法,就可以创建以上类的对象:

package cn.mypro.spring;
//工厂类,一般类名后缀为:Factory
//工厂类内需要有一个方法,方法的返回值就是Student类型的
public class StudentFactory {// 能生产Student类的对象的工厂方法,该方法不需要使用static来修饰// 应该先创建工厂的实例(对象),再调用方法得到Student类的对象,所以,这样的方法叫做“实例工厂方法”public Student newInstance() {return new Student(null);//这个方法每次调用一次都会new一个新的对象}
}

接下来,在Spring的配置文件中进行配置:

(1)先配置工厂

(2)再配置需要创建对象的类:新增了factory-bean和factory-method两个属性。

<!-- 先配置工厂(最原始的方式,用无参构造方式) -->
<bean id="studentFactory" class="cn.mypro.spring.StudentFactory"></bean>
<!-- 工厂id是(自定义名称):studentFactory,注意s是小写的。推荐使用类名将首字母改为小写 --><!-- 再配置需要创建对象的类 -->
<!-- factory-bean与factory-method属性:调用谁的哪个方法可以创建对象 -->
<bean id="student" class="cn.mypro.spring.Student"factory-bean="studentFactory" factory-method="newInstance">
</bean>

目前,介绍了3种使用Spring创建对象的方式

  • 通过无参数构造方法来创建;
  • 通过类中的静态工厂方法来创建
  • 通过其它类的实例的工厂方法来创建

在实际使用时,**优先选取最简单的做法即可!**如果使用的类存在无参数构造方法,就应该优先选取这种做法,但是,并不是任何时候都可以这样选择,在项目中,可能需要配置的是第三方提供的类,我们无法修改这些类的源代码,或者,这些类没有通过构造方法传递参数就无法正常工作,就只能通过第2种或第3种做法来进行配置!

另外,如果某个类完全不符合以上所有条件,还可能通过构造方法注入值来实现创建对象,该做法在后续课程中讲解!

4. Spring管理对象的作用域与生命周期

4.1. Spring管理对象的作用域

对象的作用域:就表示这个对象是何时创建出来的,到何时被销毁,在这个过程中,是可以使用这个对象的,超出这个时间范围就无法使用这个对象了!

默认是单例:由Spring所管理的对象,默认都是单例(单一实例:在同一个时间节点,多次尝试获取对象,最终获取到的都是同一个对象,并不会因为反复获取而得到不同的对象)的!

也就是说,在

User user = (User) ac.getBean("user");

这句代码中,无论get多少次,get出来的user都是同一个对象。

package cn.mypro.spring;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringDemo {public static void main(String[] args) {//要记住代码的过程//1.加载Spring的配置文件,获取Spring容器ClassPathXmlApplicationContext ac = new   ClassPathXmlApplicationContext("spring.xml");//2.从Spring容器中获取所需的对象User user1 = ac.getBean("user",User.class);//能给参数解决就不要强制转换User user2 = ac.getBean("user",User.class);User user3 = ac.getBean("user",User.class);//3.测试System.out.println(user1);System.out.println(user2);System.out.println(user3);//4.关闭(释放资源)ac.close();}
}

user定义一次,但是获取3次,最后的输出结果是一样的。

cn.mypro.spring.User@8bd1b6a
cn.mypro.spring.User@8bd1b6a
cn.mypro.spring.User@8bd1b6a

为什么输出成这样的格式,因为在User类中:

public class User extends Object {}

相当于是继承自Object,而在Object中有toString方法:

public String toString() {return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

而在输出的时候,就默认调用了这个toString方法,输出的时候,getClass().getName()就获取了这个类的全名,而Integer.toHexString(),表示通过Integer包装的类的方法,把其中内容转换成了一个16进制的数。

toHexString()是转换成16进制的方法,被转换的是hashCode,所以本质上,输出结果中@后面的就是:当前对象的hashCode的返回值。

但是不能说hashCode值相同就是同一个对象,因为可以在User类中重写hashCode方法,重写返回值。

重写的方法:has+快捷键

package cn.mypro.spring;
public class User extends Object {@Overridepublic int hashCode() {// TODO Auto-generated method stubreturn super.hashCode();}
}

可以说:如果这个hashCode方法没有被恶意的重写(这是前提),是可以参考是否是同一个对象的

很多时候,把hashCode值理解为数据在内存中的地址

scope属性:在Spring的配置文件中,可以为<bean>节点添加scope属性([skoʊp] 斯 狗 破),用于配置该<bean>节点对应的类是否通过单例模式进行管理,该属性的常用取值可以是singletonprototype,其中,singleton是默认值,表示单例,而prototype表示非单例!

singleton:n.(所提及的) 单项物,单个的人; 单身男子(或女子); (非孪生的) 单生儿,单生幼畜;

prototype:n.原型; 雏形; 最初形态; [ˈproʊtətaɪp] 破 肉 特 泰破

所以,如果希望某个类的对象不是单例的,可以配置为:

<bean id="user" class="cn.mypro.spring.User" scope="prototype">
</bean>

单例模式——懒汉式加载 、懒汉式加载:

lazy-init属性:另外,还可以在<bean>节点添加lazy-init(lazy:懒惰的,init:初始化)属性,以配置单例模式的情况下,是否为懒汉式加载!是否懒加载的前提是必须是单例模式

lazy-init属性的值可以是truefalse

lazy-init=“false”,表示"饿汉式加载",默认也是这个,不写表示默认

lazy-init=“true”,表示"懒汉式加载"

例如:

单例(不写默认了)——饿汉式加载

<bean id="user" class="cn.mypro.spring.User" lazy-init="false">
</bean>

附1:使用Spring框架的原因

Spring框架的主要作用之一就是创建对象,但是,创建对象本身是一件非常简单的事情,例如:

User user = new User();

但是,在实际开发项目时,一个项目中的类会非常多,需要创建的对象也非常多,并且,类与类之间的依赖会比较复杂,如果直接创建对象,会导致耦合度较高,不利于项目的管理和维护!

所以用Spring创建对象,会降低耦合度,耦合度低更加易于管理和维护

依赖的举例解释

例如在此前学习的Java EE课程,可能存在这样的代码:

public class UserJdbcDao {//把数据库编程相关的内容直接写在这个方法里面,里面写JDBC方法public void reg() {// 通过JDBC实现插入用户数据到数据库中}public void login() {// ...}public void changePassword() {// ...}
}

另外,还有:

// 处理用户注册的Servlet
public class UserRegServlet {public UserJdbcDao userDao = new UserJdbcDao();// 处理用户注册的方法public void doPost() {//重写其中的doPost方法// 将用户提交的注册信息写入到数据库中,但是,此处并不会编写JDBC相关代码,应该是调用方法来完成userDao.reg();}
}

在一个项目中,还可能有UserLoginServlet用于处理用户登录,或者还有UserChangePasswordServlet等其它Servlet类,在处理用户请求时,都会涉及到数据库相关操作,则这些Servlet类中都需要添加:

public UserJdbcDao userDao = new UserJdbcDao();

这样的代码,就可以称之为UserRegServlet等类是依赖于UserJdbcDao的!

如果后续需要将UserJdbcDao换掉,例如不再使用JDBC来实现数据库编程,而是使用MyBatis框架来实现,那么就要替换UserJdbcDaoUserMyBatisDao,则原有的public UserJdbcDao userDao = new UserJdbcDao();这句代码就全部都需要替换,一个项目中,可能有几十个甚至几百个Servlet中都有类似的代码,则这几十甚至几百个Servlet类的源代码都需要调整为例如:

public UserJdbcDao userDao = new UserJdbcDao();
替换为:
public UserMyBatisDao userDao = new UserMyBatisDao();

则这样的代码是不易于管理和维护的!而且随着技术进步,以后这句代码也可能会改。

那么如何解决这个问题?

1.用接口解决

为了解决这个问题,可以先声明一个接口:

public interface UserDao {//接口内写抽象方法void reg();void login();void changePassword();
}

并且,让DAO类都实现该方法,例如:

public class UserJdbcDao implements UserDao {// 重写接口中的抽象方法
}public class UserMyBatisDao implements UserDao {// 重写接口中的抽象方法
}

则声明DAO的代码就可以改为:

这么写的好处是,以前左边右边都要更改,现在只改右边就行了,工作量小了一半

public UserDao userDao = new UserMyBatisDao();
//“用接口来声明,new的是实现类的对象”体现了多态,//这也是多态的主要表现:
//1.父类来声明,new子类的对象
//2.用接口来声明,new的是实现类的对象

当存在依赖关系时,应该尽量的依赖接口,而不是依赖于某个具体的类,如果直接依赖于某个类,就是耦合度较高的表现

耦合度低更加易于管理和维护

把代码从耦合度高调整为耦合度低,这也是称之为解耦的表现。

2.工厂模式解决

然后,还可以使用**“设计模式”中的“工厂模式”**进一步优化以上代码!例如:

public class UserDaoFactory {public static UserDao newInstance() {return new UserJdbcDao();}
}

则以上声明DAO的代码可以进一步调整为:

public UserDao userDao = UserDaoFactory.newInstance();

可以发现,以上代码中完全没有体现实现类的名称,后续,如果需要更换所使用的实现类,则以上代码也不需要进行任何调整!唯一需要修改的位置就只有工厂方法的返回值!

//就是改这句话就行
return new UserJdbcDao();//改成
return new UserMyBatisDao();

所以,使用接口及工厂方法,就可以有效的提高代码的管理和可维护性

在这个过程中,起到关键作用的就是工厂类!在实际编写项目时,为每个所使用的类都创建对应的工厂是非常不现实的!而Spring框架就相当于一个庞大的万能工厂,可以由它创建任何所需的对象,当开发人员需要某个对象时,从Spring容器中获取即可!

附2:设计模式之单例模式

单例模式是设计模式中,关于生产对象类型的设计模式的一种!

单例模式的具体表现是不可以获取同一个类的多个对象,反复获取也只会得到同一个对象!

总的来说,饿汉式的特点是:刚刚加载时就创建了对象,而懒汉式的特点是:当第1次尝试获取对象时才创建对象!

单件模式用途:
单件模式属于工厂模式的特例,只是它不需要输入参数并且始终返回同一对象的引用。
单件模式能够保证某一类型对象在系统中的唯一性,即某类在系统中只有一个实例。它的用途十分广泛,打个比方,我们开发了一个简单的留言板,用户的每一次留言都要将留言信息写入到数据库中,最直观的方法是每次写入都建立一个数据库的链接。这是个简单的方法,在不考虑并发的时候这也是个不错的选择。但实际上,一个网站是并发的,并且有可能是存在大量并发操作的。如果我们对每次写入都创建一个数据库连接,那么很容易的系统会出现瓶颈,系统的精力将会很多的放在维护链接上而非直接查询操作上。这显然是不可取的。
如果我们能够保证系统中自始至终只有唯一一个数据库连接对象,显然我们会节省很多内存开销和 cpu 利用率。这就是单件模式的用途。

下面对单例模式饿汉式懒汉式进行简单介绍:
1、饿汉式:在程序启动或单件模式类被加载的时候,单件模式实例就已经被创建。
2、懒汉式:当程序第一次访问单件模式实例时才进行创建。

如何选择:
如果单件模式实例在系统中经常会被用到,饿汉式是一个不错的选择。
反之如果单件模式在系统中会很少用到或者几乎不会用到,那么懒汉式是一个不错的选择。

懒汉式在多线程中是不支持的,所以相对来说,更多的是用饿汉式
比如,现在有 A 线程和 B 线程,A 线程刚好在这个 getInstance()方法中,刚刚判断完非空(此时为 null),即需要创建实例,然而,就是这么巧,B 线程抢到了 CPU 的执行权,A 线程 sleep 了,这时,B 线程也进行了这个判断,和 A 一样,都需要创建实例,而这时,A 也抢到了 CPU,这时,B 就 sleep 了,然后 A 执行了实例化后,B 又抢到 CPU 执行权,然后 B 也实例化,这时,出现问题了,A 和 B 都实例化了一个对象,这就是两个对象,没有单例也不唯一。

1.饿汉式单例模式

假设需要将King类设计为单例的,首先,普通的类的代码例如:

public class King {}

这样的类是可以随意创建对象的,例如:

King k1 = new King();
King k2 = new King();
King k3 = new King();

显然,随意创建对象是违背了单例模式的设计思想的,所以,为了避免随意对象,可以:

public class King {private King() {// 将构造方法私有,使之不可以在类的外部来创建对象}
}

当然,私有化构造方法的目的并不是“不允许创建对象”,而是“不允许随意创建对象”,为了保证在类的外部依然可以获取当前类的对象,还可以在类中添加获取对象的方法:

public class King {private King() {// 将构造方法私有,使之不可以在类的外部来创建对象}public King getInstance() {return new King();}
}

则外部通过调用getInstance()方法仍可以获取对象,但是,每调用一次,都会创建一个新的对象,所以,还需要进一步调整:

public class King {private King king = new King();private King() {// 将构造方法私有,使之不可以在类的外部来创建对象}public King getInstance() {return king; // 直接返回私有的全局属性}
}

由于在初始化时,private King king = new King();只会执行1次,反复调用getInstance()方法,获取到的都是同一个对象!这就符合了单例模式的设计思想!

但是,在以上代码中,如果没有King的对象,是无法调用getInstance()方法,而获取对象的唯一途径就是要调用这个方法!所以,为了解决这个矛盾,需要使用static修饰这个方法:(用类名.就可以调用方法了)

public class King {private King king = new King();private King() {// 将构造方法私有,使之不可以在类的外部来创建对象}public static King getInstance() {return king; // 直接返回私有的全局属性}
}

一旦在getInstance()方法的声明中添加了static关键字,还需要在private King king = new King();这个属性的声明中也补充static关键字,因为“static修饰的成员不可以直接访问没被static修饰的成员”,所以,代码还需要改成:

static修饰的成员不可以直接访问没被static修饰的成员:解释:static class装载的时候,就存在了。 而类的其他部分,变量,方法,都需要在类成为实例的时候才会建立。 static的生命周期长于非static的对象,所以static的成员如果访问无static的成员时。对方可能还未产生,会发生错误。所以禁止了。也就是说:静态的方法不能直接调用非静态的方法,要有对象之后才能调用非静态的方法

-----饿汉式单例模式全代码:

//饿汉式单例模式
public class King {private static King king = new King();//自己创建一个类的实例化private King() {// 将构造方法私有,使之不可以在类的外部来创建对象}public static King getInstance() {return king; // 直接返回私有的全局属性}
}

至此,单例模式就设计完成!

同时,以上单例的代码是**“饿汉式单例模式”**。

为什么单例对作用域是有影响的?

是因为设计为单例的对象,一定是有static修饰的,而static这个关键字,带来的编程体验是不需要new对象就可以调用它的成员,比如属性、方法,但是static成员是常驻内存的,一旦加载到内存中,只有整个程序完全结束之后,这个数据才会从内存中消失。而局部变量,只要方法调用完毕,这个变量的作用域就结束了。

所以被static修饰的成员,它的作用域是非常长的,而没有被static修饰的成员就不会一直在内存中,所以是否单例就会影响它是否在内存中存在非常长的时间,所以单例就影响到作用域。

2.懒汉式单例模式

另外,还有**“懒汉式单例模式”**,其特征是“不到逼不得已,不创建对象”!例如:

public class King {private static King king;private King() {// 将构造方法私有,使之不可以在类的外部来创建对象}public static King getInstance() {// 判断全局的king是否被创建了对象,如果没有,则创建if (king == null) {king = new King();}return king;}
}

但是,以上代码是存在线程安全风险的!当存在多个“同时”运行的线程时,是可能创建出多个对象的!为了解决线程安全问题,需要:

public class King {private static King king;private static final Object lock = new Object();private King() {// 将构造方法私有,使之不可以在类的外部来创建对象}public static King getInstance() {// 为了解决线程安全问题,需要锁上以下代码synchronized(lock) {// 判断全局的king是否被创建了对象,如果没有,则创建if (king == null) {king = new King();}}return king;}
}

但是,一旦加了锁,效率又会变得比较低下!因为每次调用以上方法都需要锁上代码才可以继续向后执行,在多线程的应用场景中,多个“同时”运行的线程在执行这段代码是互斥的,为了解决效率偏低的问题,还会进一步改进:

-----懒汉式单例模式全代码:

//懒汉式单例模式
public class King {private static King king;// 自己创建一个类的实例化private static final Object lock = new Object();//创建一个锁对象private King() {// 将构造方法私有,使之不可以在类的外部来创建对象}public static King getInstance() {// 判断是否有必要锁住代码if (king == null) {// 为了解决线程安全问题,需要锁上以下代码synchronized(lock) {// 判断全局的king是否被创建了对象,如果没有,则创建if (king == null) {king = new King();}}}return king;}
}

至此,懒汉式的单例模式也就完成了!

总的来说,饿汉式的特点是:刚刚加载时就创建了对象,而懒汉式的特点是:当第一次尝试获取对象时才创建对象。

写在最后

1.软件下载

下载Typora软件来记录笔记。

2.创建maven时选择的war包与jar包的区别?

JAR文件的目的是把类和相关的资源封装到压缩的归档文件中,而对于WAR文件来说,一个WAR文件代表了一个Web应用程序,它可以包含 Servlet、HTML页面、Java类、图像文件,以及组成Web应用程序的其他资源,而不仅仅是类的归档文件。

3. Spring管理对象的作用域与生命周期

3.2. Spring管理对象的生命周期

**生命周期:**某个对象从创建到最终销毁的整个历程!在整个生命周期历程中,会设计一些生命周期的“阶段”,约定这个“阶段”应该做哪些事情。

Servlet的生命周期介绍:以此前学习的Servlet课程中,就将Servlet的生命周期划分为init()service()destroy()这3大“阶段”,具体的表现为3个方法!与Servlet相似的这类的组件的特点是:开发人员只需要在编写代码时创建出对应的类,并重写其中指定的方法即可,并不需要自行创建类的对象,更不需要调用方法,最终,程序在运行起来后,方法会被自动的调用,例如Servlet组件就是由Tomcat容器创建的对象,并由Tomcat调用的其中的方法!由于类的创建、方法的调用都是由Tomcat来完成的,所以,开发人员不必关心Servlet中的方法在几点几分几秒被调用,只知道某些会在满足什么条件时被调用,并决定被调用时应该如何处理数据,所以,在开发Servlet时,开发人员只需要重写对应的方法即可!总的来说,在讨论生命周期问题时,特定的组件(某些类)的创建过程、销毁过程,及其中某些方法的调用的主动权都不在开发人员手里,而是由某些容器进行管理的,开发人员就不必关心方法的调用时间,只需要关心特定的方法在哪种情景下会被调用,从而决定被调用时应该做哪些事情!生命周期在管理对象中的表现就是一个个的方法,在学习生命周期时,一定要了解这些方法在什么情景下被调用、调用多少次!

所以:在面试资料里找到的:

Servlet的生命周期

1、web容器加载servlet,servlet实例化,生命周期开始。

2、通过调用servlet的init()方法进行servlet的初始化。

3、通过调用 service()方法实现,根据请求的不同调用不同的doGet()或者doPost()方法。

4、结束服务,web容器调用servlet的destroy()方法。

在学习Spring时,可以发现,所编写的类如果交给Spring来管理,那么由何时创建对象、何时销毁对象也不由开发人员来管理了!如果开发人员需要在整个对象的管理过程中添加一些管理方法,例如“在销毁之前释放某些资源”,则可以在类中添加创建和销毁时的生命周期方法,例如:

package cn.mypro.spring;
public class User {public User() {System.out.println("创建了User类的对象!!!");}// 生命周期方法:初始化时执行的方法// 方法的声明:自定义名称,空参数列表。建议:public void。//由于是Spring框架调用方法,所以无法识别参数和返回值。而权限最好是public。public void init() {System.out.println("User.init()");}// 生命周期方法:销毁之前执行的方法public void destroy() {(dis 赵 诶)System.out.println("User.destroy()");}}

然后,还需要在Spring的配置文件中进行配置:

(在安卓里面,方法名称一般通用为:onCreate和onDestroy)

<!-- init-method:初始化方法的名称,应与User类里初始化方法的自定义的名称相一致 -->
<!-- destroy-method:销毁方法的名称,应与User类里销毁方法的自定义的名称相一致 -->
<bean id="user" class="cn.mypro.spring.User"init-method="init" destroy-method="destroy">
</bean>

当运行时,可以发现:在执行了构造方法之后,就会自动调用初始化方法,在最终释放资源之前,就会执行销毁方法!顺序是:构造方法——初始化方法——销毁方法——释放资源。

当编写了某个类由交Spring管理后,并不是必须声明并配置生命周期方法,仅在有需要的时候添加方法并配置即可,也不一定需要同时添加初始化和销毁这2个方法,完全可以只添加并配置其中的某1个,按需使用即可

(生命周期的方法,在绝大多数时候,的确不需要用。)

4. Spring IoC(Spring中最重要的话题)

4.1. Spring IoC简介

IoC:控制反转(Inversion of Control),在传统开发模式下,都是由开发人员自行创建对象,并管理对象(也包括属性赋值的问题),当使用了Spring框架后,这些事情都交给框架来完成(在加载spring配置文件时,创建对象,ac.close的时候销毁),可以理解“将开发人员对对象的管理权交给了框架”,所以,就出现了**“管理权”的移交**,则称之为“控制反转”。(Inversion: [ɪnˈvɜːrʒn] ,“in 窝 伸”,: 反转)

所以控制反转的意思是,开发人员把控制权交给了框架,所以叫做控制翻转。

DI:依赖注入(Dependency Injection),具体表现为通过框架为对象的属性赋值!( [dɪˈpendənsi]:依赖。[ɪnˈdʒekʃn] :注入)。注入:其实就是一个属性赋值的过程。

IoC与DI的区别:Spring框架通过DI实现了IoC,DI是实现手段,而IoC是希望实现的目标!

关于Spring IoC的学习,就是学习如何通过Spring框架确定某个类的对象的属性值

然后创建了spring03项目,目的:希望实现spring ioc的效果,即:我们把一个类交个spring去管理的同时,这个类中如果有属性的话,我们还希望这个属性也是有值的。

如下:

4.2. 通过SET方式注入属性的值

假设存在User类,其中有String password属性,希望Spring在创建User类的对象时,还能为password属性注入值(赋值),则先在 src/main/java 下面准备好User类,例如:

(1)注入User类中的password属性值:

首先,在User了中添加两个属性:password,并且生成get和set方法:

package cn.mypro.spring;
public class User {private String password;//然后添加get set方法public String getPassword() {return password;}public void setPassword(String password) {System.out.println("User.setPassword()");this.password = password;}}

然后,在src/main/resources中的Spring的配置文件中进行配置:

如果一个类要用Spring来管理,那么在中就要写上这个类的名字和包名

只要是使用Set方式为属性注入值,一定是在的子级内去加property节点

<bean id="user" class="cn.mypro.spring.User">在bean的头和尾中间加入一行代码<!-- 在bean的内部(子集)写:property节点:用于配置属性的值。翻译:property:属性 --><property name="password" value="123456"/><!-- property节点:用于配置属性的值 --><!-- name属性:需要配置值的属性名称,实际是set方法对应的名称 --><!-- value属性:需要配置值的属性的值 --><!-- 如果标签内部不需要写子集的话,可以这样写:<property name="password" value="123456"/> --><property name="age" value="23"/><!-- Spring做法: --><!-- password 对应的是——>setPassword --><!-- value 对应的是——>setPassword(123456)--></bean>

然后,在SpringDemo类中:

package cn.mypro.spring;
/*** SET方式注入属性值测试*/
import java.util.Arrays;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringDemo {public static void main(String[] args) {//1.加载Spring配置文件,获取Spring容器ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml");//2.从Spring容器中获取对象User user = ac.getBean("user",User.class);//User user = (User) ac.getBean("user");能不转换,就不转换//3.测试System.out.println(user); //cn.mypro.spring.User@727803deSystem.out.println(user.getPassword());//123456System.out.println(user.getAge());//23//4.关闭,释放资源ac.close();

最后,运行时,当获取对象后就可以直接输出属性值,可以看到,该属性已经存在所配置的值!

注意:在<property>属性中,name属性其实是SET方法对应的名称,例如以上配置为name="password",则Spring框架会基于这个配置值,将首字母大写,并在左侧拼上set,得到setPassword,以此为方法名称,结合value属性值,调用了setPassword("000111"),从而使得password属性被赋值!所以,本质是要求name属性的值与类的SET方法是对应的!但是,规范的SET方法名称与Spring组织出SET方法的方式是完全相同的,所以,也可以简单的理解为name属性指的就是属性名称!

建立一个类:SampleBean(体验节点类)

package cn.mypro.spring;import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
//属性是集合
public class SampleBean {//Sample:体验//David, Lucy, Tom, Lily, Alexprivate List<String> names;//9527private int[] numbers;//Beijing, Shanghai, Guangzhou, Shenzhenprivate Set<String> cities;//city复数:cities// {username=root,password:1234, from:Beijing}private Map<String, String> session;//name:Jack, age:25, gender:Maleprivate Properties profile;//property的复数形态,多个属性的意思//数据来自于jdbc.properties文件private Properties jdbc;//然后生成所有的get、set方法public List<String> getNames() {return names;}public void setNames(List<String> names) {this.names = names;}public int[] getNumbers() {return numbers;}public void setNumbers(int[] numbers) {this.numbers = numbers;}public Set<String> getCities() {return cities;}public void setCities(Set<String> cities) {this.cities = cities;}public Map<String, String> getSession() {return session;}public void setSession(Map<String, String> session) {this.session = session;}public Properties getProfile() {return profile;}public void setProfile(Properties profile) {this.profile = profile;}public Properties getJdbc() {return jdbc;}public void setJdbc(Properties jdbc) {this.jdbc = jdbc;}}

(2)注入List集合类型的值names:

首先,在SampleBean类中:

// David, Lucy, Tom, Lily, Alex
private List<String> names;

然后,在src/main/resources中的Spring的配置文件中进行配置:

当要为一个list集合类型的属性赋值的时候,在property内部加入list节点,在list节点的子集再加value节点

<bean id="sampleBean" class="cn.mypro.spring.SampleBean"><property name="names">因为里面的属性名字是names,实际上是set方法是getNames<list><value>David</value><value>Lucy</value><value>Tom</value><value>Lily</value><value>Alex</value></list></property>
</bean>

最后,在SpringDemo类的main方法中:

     System.out.println(sampleBean.getNames());//[David, Lucy, Tom, Lily, Alex]System.out.println(sampleBean.getNames().get(3));//LilySystem.out.println(sampleBean.getNames().getClass());//class java.util.ArrayList

注意:

List集合在Spring中是用什么实现类的来实现的?

通过代码:System.out.println(sampleBean.getNames().getClass());测试可知,是class java.util.ArrayList,那么在spring.xml文件中:

     <property name="numbers"><list>这里用<array>配置也可以,但不提倡。<value>9</value><value>5</value><value>2</value><value>7</value></list></property>

在配置List集合属性的时候,用array和list都是可以的,同理,在配置array的时候,也可以用list,因为ArrayList的本质,在内存中的数据表现就是数组,所以array和list这两个节点可以混为一谈,想怎么用就怎么用,但是不提倡,只是了解即可。

(3)注入数组类型的值numbers:

首先,在SampleBean类中:

// 9, 5, 2, 7
private int[] numbers;

然后,在src/main/resources中的Spring的配置文件中进行配置:

数组对应的是<array>节点

<property name="numbers"><array><value>9</value><value>5</value><value>2</value><value>7</value></array>
</property>

最后,在SpringDemo类的main方法中:

     System.out.println(sampleBean.getNumbers());//[I@704921a5/*** 注意:* 数组与其他的数据类型不同,并不继承自Object,虽然可以用Object去声明,但是却没有里面的* toString方法* 如果要让数组显示合理,就要用到Arrays的工具类,这个工具类有toString方法,然后把* 数组作为参数放进去* 所以如果输出数组,那么一般用Arrays.toString()方法,如下:*/System.out.println(Arrays.toString(sampleBean.getNumbers()));//[9, 5, 2, 7]

(4)注入Set集合类型的值cities:

首先,在SampleBean类中:

// Beijing, Shanghai, Guangzhou, Shenzhen
private Set<String> cities;//一般如果是数组或者集合的变量,变量名一般是用复数形式

然后,在src/main/resources中的Spring的配置文件中进行配置:

set集合类型的属性,对应的就是节点

<property name="cities"><set><value>Beijing</value><value>Shanghai</value><value>Guangzhou</value><value>Shenzhen</value></set>
</property>

最后,在SpringDemo类的main方法中:

     //3.测试System.out.println(sampleBean.getCities());//[Beijing, Shanghai, Guangzhou, Shenzhen]/** 注意:输出的城市顺序是和放入的城市顺序一致的* 原因是:Spring框架默认的用的是LinkedHashSet,要验证的话,用下面验证:*/System.out.println(sampleBean.getCities().getClass());//class java.util.LinkedHashSet

(5)注入Map类型的值session

首先,如果需要注入的是Map类型的数据,在SampleBean类中:

// {username:root, password:1234, from:Beijing}
private Map<String, String> session;//session翻译:阶段;一场;一节;一段时间;

然后,在src/main/resources中的Spring.xml的配置文件中进行配置:

property标签中——map标签中——entry标签(entry翻译:条目)

<property name="session"><map><entry key="username" value="root"/><entry key="password" value="1234"/><entry key="from" value="Beijing"/></map>
</property>

最后,在SpringDemo类的main方法中:

     System.out.println(sampleBean.getSession().getClass());//class java.util.LinkedHashMap//依然是通过链表的形式来存储数据的,所以输出的顺序是和配置的顺序是保持一致的。System.out.println(sampleBean.getSession());//{username=root, password=1234, from=Beijing}
Set与Map的实质:

世上本没有Set,每一个Set都是只关心Key,不关心Value的Map

看HashSet的源代码:

    public HashSet() {map = new HashMap<>();}

当new一个HashSet的时候,其本质是new一个HashMap。

并且:

public class HashSet<E>extends AbstractSet<E>implements Set<E>, Cloneable, java.io.Serializable
{//HashSet有一个声明,用的是占位符E,而内部声明了一个HashMap,HashMap的泛型Key也是E,同时,它的Value是Object,Object是随便声明的一个类型static final long serialVersionUID = -5024744406713321676L;private transient HashMap<E,Object> map;// Dummy value to associate with an Object in the backing Mapprivate static final Object PRESENT = new Object();

并且在add方法中:

    public boolean add(E e) {return map.put(e, PRESENT)==null;}

其本质是往Map中put了一个数据,而添加进来的参数就变成了Map这个数据的Key。

所以,Map内的Key的表现特征,与Set非常相似,或者说就是一样的。而实际上,Set本身就是基于Map来实现的!

这是HashSet的源代码,而TreeSet也是一样的。

    public TreeSet() {this(new TreeMap<E,Object>());}

所以,Set实际上一直都是用Map来作为一个内部实现的工具,看到用的是Set,其实本质上都是Map,所以才有:世上本没有Set,每一个Set都是只关心Key,不关心Value的Map

(6)注入Properties类型的数据:

1.Properties是一种集合类型,它也是能放很多数据的。

2.在哪出现过:以前在学JDBC的时候,写的一个数据库连接的jdbc.properties文件,读到程序中,就是一个Properties类型的数据,写配置文件的时候用的。这个配置文件读出来之后,和Map差不多,与Map有相似之处,但不是Map。

3.如果需要注入的是java.util.Properties类型的数据,在SampleBean类中:

// name:Jack, age:25, gender:Male
private Properties profile;
//属性是Properties,是property的复数形态,多个属性的意思
//profile翻译:概述、简介。里面准备一些个人资料。
//然后生成set、get方法

4.注意,这个Properties属性类型的数据,很少在xml里面写死,更多的还是像以前JDBC一样,在项目中会有一个jdbc.properties这样的一个配置文件,那么private Properties profile;这个对象的值就应该是来自于配置文件中,所以,关于Properties的注入就有这两种方式:常用:使用配置文件注入属性值,不常用:直接在Spring.xml中写死。

不常用方式:直接在Spring.xml写,在src/main/resources中的Spring的配置文件中进行配置:

<property name="profile"><props><!-- props是Properties的简写 --><!-- 子集只有一个,注意,没有value --><prop key="name">Jack</prop><prop key="age">25</prop><prop key="gender">Male</prop></props>
</property>

然后在SpringDemo中新增:

     System.out.println(sampleBean.getProfile().getClass());//class java.util.Properties//不是其他实现类,Properties类型就是一种直接可用的类型System.out.println(sampleBean.getProfile());//{age=25, name=Jcak, gender=Male}//输出顺序和写的顺序不一样,数据在内存中是散列存储的,在实际使用中,也并不关心先后顺序/*因为在实际使用这样数据的时候,更多的是读配置文件,去获取某一个属性的值,比如获取数据库连接的用户名,另外一处再获取数据库连接的密码,是取出其中的某一个数据来用,其实并不关心先后顺序*/

常用方式:当然,Properties类型的数据也可以从.properties类型的文件中读取!可以先在src/main/resources下创建jdbc.properties文件,右键new出File(搜File的话,是在General下面的),并在其中进行属性与值的配置:

url=jdbc:mysql://localhost:3306/database_name
driver=com.mysql.jdbc.Driver
username=root
password=root
initialSize=2
maxActive=10#initialSize=2:表示如果用数据库连接池的话,初始化连接数有几个,这个值一般就是随便写,正式编程的时候也是随便写,因为这个值我们自己无法直接确定,后期根据运营情况要一直调整的。

本次练习时,并不关心以上属性名称与值是否正确,只需要关心后续能否读取到这些属性与值即可。

这种方式配置Spring时,就和之前的不太一样了

<bean>节点并列:在Spring的配置文件中,需要使用<util:properties>节点来读取这个文件,这个节点与<bean>节点是同一个级别的节点,不要将这个节点写在<bean>的子级!在配置时:

<util:properties id="jdbc" location="classpath:jdbc.properties"></util:properties><!-- util:properties节点:用于读取.properties文件中的配置信息 -->
<!-- location属性:需要读取的文件的位置。 (location翻译:位置)-->
<!-- classpath:指的就是resources文件夹 -->

如果这些数据需要被读取到程序中或者某个属性中,是希望类当中是有这个属性的,比如读到SampleBeanProperties jdbc属性中,那么要先在SampleBean中添加:

// 数据来自于jdbc.properties文件
private Properties jdbc;
//现在是需要为这个属性注入值
//然后生成set、get方法

那么现在要配置的是SampleBean中的jdbc的这个属性,我希望这个属性是有值的,

那么,在Spring的配置文件中,在配置SampleBean<bean>节点子级添加:(在后面补充)

<property name="jdbc"/>

name为jdbc之后,那么jdbc的值来自下面这个properties节点:

<util:properties location="classpath:jdbc.properties"></util:properties>

只需要为这个properties节点指定一个location,它就会自动的把jdbc.properties这个文件给读了,自动的读取这个文件中的所有信息,并且把它封装到一个propertie类型的对象里面去,如果这个jdbc要来自上面properties节点读取的结果,首先,这个读取结果就要给他一个名字,否则他们两个之间就无法有对应关系,所以给名字:依然是用id给名字,一般建议:描述的是同一个数据,就用同样的名字就行

所以写成:

<util:properties id="jdbc" location="classpath:jdbc.properties"></util:properties>
关于ref属性:

然后,既然希望数据是来自于这个properties节点,那么在中加入一个新的属性ref,ref表示引用的意思,引用的是"jdbc",所以最后写成:

<property name="jdbc" ref="jdbc"/>

写到这里,配置就写完了。

最终配置代码,在spring.xml文件中写成:

<property name="jdbc" ref="jdbc"/>
<util:properties id="jdbc" location="classpath:jdbc.properties"></util:properties>

**知识点:**在为属性注入值时,如果该属性值是另一个Bean,则应该通过ref属性引用到另一个Bean的id。

其实可以理解为,使用的<util:properties>是一种比较特殊的<bean>

然后,在SpringDemo中添加:

     System.out.println(sampleBean.getJdbc().getClass());System.out.println(sampleBean.getJdbc());//输出结果//class java.util.Properties//{maxActive=10, password=root, url=jdbc:mysql://localhost:3306/database_name, driver=com.mysql.jdbc.Driver, initialSize=2, username=root}
//这些属性就是在jdbc.properties文件中写的属性。

以后,凡是直接能写的值,就写value,不能直接写的值,如果是来自于另外一个Bean,就写ref

小结:如果某个类中的某个属性需要被注入值,

1.首先,需要为这个属性添加匹配的SET方法(一般GET方法也都直接添加了),

2.在Spring的配置文件中,在该类对应的<bean>节点的子级,添加<property>节点,用于为属性注入值,如果有多个属性都需要注入值,则同一个<bean>节点的子级应该有多个<property>节点,

3.在每个<property>节点中,都通过name属性指定需要注入值的属性名称,假设需要注入值的属性叫password,则应该配置为name="password"

(1)如果该属性的值是可以直接写出来的(例如数值、字符串、布尔值),则在<property>节点中添加value属性来配置值,

(2)如果该属性的值是另一个Bean对象,则在<property>节点添加ref属性来引用另一个<bean>节点或等同节点的id值,

(3)如果该属性的值是集合类型的,则需要在<property>节点的子级添加<list> / <set> / <array> / <map> / <props>进行配置,具体配置方式参考以上笔记中的示例代码。

附0:jdbc.properties文件的配置

使用配置文件访问数据库的优点是:一次编写随时调用,数据库类型发生变化只需要修改配置文件。

配置文件的设置:在配置文件中,key-value对应的方式编写。

开发中获得连接的4个参数(驱动、URL、用户名、密码)通常都存在配置文件中,方便后期维护,程序如果需要更换数据库,只需要修改配置文件即可。

使用properties文件,Java中有专门用来装载配置文件的类Properties(Java.util.Properties),配置文件用来保存一些在程序中可能修改的值,修改时只要修改配置文件即可,而不用修改程序本身。Java配置文件常为.properties文件,格式为文本文件,文件的内容的格式是“键=值”的格式。在properties的文件中,在行首加上 # 就是注释这行,或者是用 包括这行也是注释

加载配置文件

 public static void loadPro() throws Exception {//从文件中读取输入流FileInputStream fis = new FileInputStream("src/database.properties");//创建Properties对象Properties pro = new Properties();//从流中加载数据pro.load(fis);//关闭流fis.close();//从Properties对象中根据键读取值String driverClass = pro.getProperty("driverClass");String url = pro.getProperty("url");String username = pro.getProperty("username");String password = pro.getProperty("password");//打印值System.out.println(driverClass);System.out.println(url);System.out.println(username);System.out.println(password);}

1.通过IO读取文件

2.创建Properties对象

3.使用Properties对象的load(流)方法加载数据

4.使用Properties对象的getProperty(键)方法获取对应值

用JDBC建立连接:

/* 假设已经取得 driverClass,url, username, password 的值 */
// 注册驱动
Class.forName(driverClass);
// 建立连接
Connection con = DriverManager.getConnection(url, username, password);
// 获取SQL语句执行对象
Statement stat = con.createStatement();
// 调用执行者对象方法,执行SQL语句获取结果集
String sql = "SELECT * FROM sort";
ResultSet rs = stat.executeQuery(sql);
// ResultSet接口方法 boolean next() 返回true,有结果集,返回false没有结果集
while(rs.next()){// 获取每列数据,使用是ResultSet接口的方法 getXX方法参数中,建议写String列名System.out.println(rs.getInt("sid")+"   "+rs.getString("sname")+"   "+rs.getDouble("sprice")+"   "+rs.getString("sdesc"));
}
// 关闭对象,先开后闭rs.close();
stat.close();
con.close();

附0:Properties对象的常用方法

  1. getProperty ( String key),用指定的键在此属性列表中搜索属性。也就是通过参数 key ,得到 key 所对应的 value。

  2. load ( InputStream inStream),从输入流中读取属性列表(键和元素对)。通过对指定的文件(比如说上面的 test.properties 文件)进行装载来获取该文件中的所有键 - 值对。以供 getProperty ( String key) 来搜索。

  3. setProperty ( String key, String value) ,调用 Hashtable 的方法 put 。他通过调用基类的put方法来设置 键 - 值对。

  4. store ( OutputStream out, String comments),以适合使用 load 方法加载到 Properties 表中的格式,将此 Properties 表中的属性列表(键和元素对)写入输出流。与 load 方法相反,该方法将键 - 值对写入到指定的文件中去。

  5. clear (),清除所有装载的 键 - 值对。该方法在基类中提供。

  6. keySet(),获取键集合(Properties继承Hashtable,内部通过Map实现)。

4.3. 通过构造方法注入属性的值(仅了解)

一般在写一个类的时候,都是非常提倡这个类是有无参数构造方法的,这样才便于框架对其进行管理,但是:

如果某个类没有无参数的构造方法,并且,希望通过构造方法为某些属性注入值,例如:

package cn.mypro.spring;// 使用构造方法注入属性的值
// 使用构造方法注入时,并不要求每个属性都有SET/GET方法
public class Person {// Zoeprivate String username;//拓展:Spring框架对于权限的要求是不严格的,因为它的核心工作底层工作原理都是通过反射机制来操作的,所以即使是写成私有的也能正常用,所以对权限没有太多的要求,但是从开发规范上来说,建议还是用私有的。// 26private int age;//生成构造方法:快捷键Alt+Shift+S,Opublic Person(String username, int age) {//此构造方法有两个参数,第一个是username,第二个是agesuper();this.username = username;this.age = age;}//为了好看一会的执行效果,生成toString,快捷键:Alt+Shift+S, S@Overridepublic String toString() {return "Person [username=" + username + ", age=" + age + "]";}}

在Spring的配置文件中,需要配置为:

<bean id="person" class="cn.mypro.spring.Person"><!-- constructor-arg节点:配置构造方法的参数 --><!-- index属性:参数的位置,即第几个参数 --><constructor-arg index="0" value="Zoe"/><constructor-arg index="1" value="26"/><!-- 子级代码是不需要写后续的,所以用/结尾 -->
</bean>

然后在SpringDemo中写:

由于新写了个Person类,要获取这个Person对象,所以在第2步,从Spring容器中获取对象中补充:

Person person = ac.getBean("person",Person.class);
System.out.println(person);
//Person [username=Zoe, age=26]

构造方法注入属性值-小节:

所以 ,如果构造方法中有多个参数,在<bean>节点的子级就需要添加多个<constructor-arg>节点进行配置,在<constructor-arg>节点中,index属性表示参数的位置,是从0开始顺序编号,也就是:第1个参数的index值是0,第2个参数的index值是1,以此类推,如果对应的参数值是数值、字符串、布尔值,就通过value属性进行配置,如果对应的参数值是另一个Bean对象,就需要通过ref属性引用到另一个<bean>节点的id值,如果对应的参数值是某种集合类型的,则需要在<constructor-arg>子级添加集合节点进行配置!

如下:

     <constructor-arg index="0" value="Zoe"/><constructor-arg index="1" value="26"/>假设还有第三个参数,如果第三个参数的类型是数组或者集合,如下:<constructor-arg index="2"><list><value>11</value><value>22</value><value>33</value></list></constructor-arg>

value取值的实质:

不管是通过Set方式还是通过构造方法来注入属性值,关于“值”应该怎么取,它的原则是不变的:

1.能够直接写,就是value="";

比如

<property name="password" value="123456"/><constructor-arg index="0" value="Zoe"/>

2.如果值是另外一个<bean>,那就写ref

<property name="jdbc" ref="jdbc"/>
<util:properties id="jdbc" location="classpath:jdbc.properties"></util:properties><constructor-arg index="0" ref="jdbc"/>
<util:properties id="jdbc" location="classpath:jdbc.properties"></util:properties>

3.如果是集合,就在子级添加节点再来配置就行,而其他类型,比如数组、Set、Map、Properties等,也是参照上述写法来写。

        <bean id="sampleBean" class="cn.mypro.spring.SampleBean"><property name="names"><list><value>David</value><value>Lucy</value><value>Tom</value><value>Lily</value><value>Alex</value></list></property></bean><constructor-arg index="0" value="Zoe"/><constructor-arg index="1" value="26"/>假设还有第三个参数,如果第三个参数的类型是数组或者集合,如下:<constructor-arg index="2"><list><value>11</value><value>22</value><value>33</value></list></constructor-arg>

4.4.小经验:index从0开始

在规范的程序开发中,程序员有一个不成文的习惯:

如果某个变量或属性的名字叫 index / position /pos 中3个的某一个,那么都应该是从0开始往后面顺着编号

这3个名字,表示的都是位置的信息

要注意,如果某个变量的数值不是从0开始编号的,那么就不要用这3个名字了,要另外再去想名字,比如统计数量,可以用count来命名。

目前,所有学习的API中,只有一个叫Index,却不是从0开始的,而是从1开始的

在学习JDBC中,写的SQL语句:

String sql = "select * from user where id=?";
PreparedStatenebt pstmt = ..
pstmt.setInt();

pstmt.setInt();方法中,括号内的第一个参数就是index,但是如果id=?设置的是第一个问号的值,那么就要设置成:

pstmt.setInt(1,9527);

index是从1开始的,也是学到现在唯一的一个。

附3:Eclipse快捷键

1.生成构造方法:Alt+Shift+S,O

或者:在Source菜单-> Generate Constructor using Fields (Generate:生成,Constructor:构造方法,Field:属性),这句话的意思是根据属性生成构造方法

**注意:**如果添加属性之后再添加构造方法,则会多出一个构造方法。所以补充构造方法的话,最好删掉之前的属性再一起重新添加。

2.生成toString方法:Alt+Shift+S, S

或者:在Source菜单->Generate toString()

**注意:**如果添加属性之后再添加toString方法,可以直接生成,会覆盖原来的方法。所以toString方法如果要补充,可以直接添加。

3.生成Get与Set方法:Alt+Shift+S, R

或者:在Source菜单-> Generate Getters() and Setters()

**注意:**如果添加属性之后再添加Get与Set方法,会只有要添加属性的Get与Set方法勾选项,所以Get与Set方法补充的话,可以直接添加。

4.批量改名:Alt+Shift+R 或者 Ctrl+2, R

附1:List集合与Set集合的区别

一般来说,如果被别人问到一个问题:谁和谁的区别,在回答问题的时候,第一步应该回答的是他们之间有什么相似之处,充分变现出对技术的理解

相同点:ListSet都是Collection集合的子级接口!所以都具有Collection这个借口所定义的所有的方法,比如添加和移除元素。

不同点:List序列的,主要表现为其中的各元素在内存中是存在顺序规则的;另外,List中的元素是可以重复的,即可以向同一个List集合中反复添加相同的数据;

Set散列的,主要表现为其中的各元素在内存中的位置是散列的,如果使用不同的实现类来存储数据,最终在显示Set集合中的所有元素时,显示结果可能是无序的(HashSet),或根据排序规则进行排列的(TreeSet),或根据添加顺序进行排列的(LinkedHashSet);(注意并不是说整个集合是,而是最后的显示结果,看到的结果是这样的)

另外,Set中的元素是不可重复的,即不可以向同一个Set集合中反复添加相同的数据,关于“是否相同”,取决于equals()的对比结果与hashCode()值的对比,如果2个对象的equals()对比为true并且hashCode()值相等,则视为“相同”!

所以,是否相同,取决于hashCode和equals方法,是先比较的hashCode再比较equals,如果hashCode相同,再比较equals,如果hashCode不同,就不比较了。

一、注意:不要用有序和无序来描述List集合和Set集合

比如Set集合,要看用的是哪个实现类:

(1)使用HashSet作为实现类,结果是无序的;

(2)使用TreeSet作为实现类,结果为12345有序的;

(3)使用LinkedHashSet实现类,结果与添加顺序一致;

所以,直接说Set是无序的,这种说法是不精准的

详细如下:

1.使用HashSet作为实现类,输出为:[str-4, str-5, str-2, str-3, str-1],显示结果是无序的

package cn.mypro.spring;import java.util.HashSet;
import java.util.Set;public class CollectionDemo {public static void main(String[] args) {Set<String> strings = new HashSet<String>();strings.add("str-1");strings.add("str-5");strings.add("str-3");strings.add("str-2");strings.add("str-4");System.out.println(strings);//输出为:[str-4, str-5, str-2, str-3, str-1]}
}

2.使用TreeSet作为实现类,输出为[str-1, str-2, str-3, str-4, str-5],显示结果是有序的

package cn.mypro.spring;import java.util.Set;
import java.util.TreeSet;public class CollectionDemo {public static void main(String[] args) {Set<String> strings = new TreeSet<String>();strings.add("str-1");strings.add("str-5");strings.add("str-3");strings.add("str-2");strings.add("str-4");System.out.println(strings);//输出为:[str-1, str-2, str-3, str-4, str-5]}
}

3.使用 LinkedHashSet作为实现类,输出为:[str-1, str-5, str-3, str-2, str-4],和添加的顺序一致,显示结果是有序的

package cn.mypro.spring;import java.util.LinkedHashSet;
import java.util.Set;public class CollectionDemo {public static void main(String[] args) {Set<String> strings = new LinkedHashSet<String>();strings.add("str-1");strings.add("str-5");strings.add("str-3");strings.add("str-2");strings.add("str-4");System.out.println(strings);//输出为:[str-1, str-5, str-3, str-2, str-4]}
}

所以,直接说Set是无序的,这种说法是不精准的

而对于Set中的元素是不可重复的:

演示代码:

package cn.mypro.spring;import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;public class CollectionDemo {public static void main(String[] args) {Set<String> strings = new LinkedHashSet<String>();strings.add("str-1");strings.add("str-5");strings.add("str-3");strings.add("str-2");strings.add("str-2");strings.add("str-2");strings.add("str-2");strings.add("str-2");strings.add("str-4");System.out.println(strings);//重点关注Set集合能不能重复,是哪个实现类不重要Set<Student> students = new HashSet<Student>();students.add(new Student("Mike", 18));students.add(new Student("Frank", 20));students.add(new Student("Frank", 20));//这行代码是复制上一行的//这个相同的student,是可以加入进去的,但绝对不是因为是new出来的这个原因students.add(new Student("Frank", 25));students.add(new Student("Joe", 25));//为了不一横排输出,写一个遍历好看些for (Student student : students) {System.out.println(student);}}}class Student {public String name;public int age;public Student(String name, int age) {this.name = name;this.age = age;}//下面的两个重写方法,都是用Source来生成的,一键生成//而开发工具不同,可能生成的代码是不同的//生成的这两个方法,目的是为了加入的属性值的内容相同,即使是new出来的对象,也看做同一个数据,也不能加入集合//各个属性,比如name和age的值相同,它的hashcode就一定相同,equals的对比结果也一定是true@Overridepublic int hashCode() {final int prime = 31;int result = 1;result = prime * result + age;result = prime * result + ((name == null) ? 0 : name.hashCode());return result;}@Overridepublic boolean equals(Object obj) {if (this == obj)return true;if (obj == null)return false;if (getClass() != obj.getClass())return false;Student other = (Student) obj;if (age != other.age)return false;if (name == null) {if (other.name != null)return false;} else if (!name.equals(other.name))return false;return true;}//下面这个是自动生成的@Overridepublic String toString() {return "Student [name=" + name + ", age=" + age + "]";}
}

如何输出Set集合中的单个元素?

一般Set集合,就是遍历输出,挨个输出里面的所有元素,

只有一种情况是输出Set中的单个元素,就是确定这Set的实现类是LinkedHashSet,因为Linked的元素存储结构是一个链一个的,所以在LinkedHashSet里面的数据其实是有下标的,或者说是有索引位置的,所以只有这种Set我们才会在当中获取第3个元素或第5个元素。如果是一般的HashSet或TreeSet我们根本就不考虑要获取其中的某一个。

附2:Java中的Properties类详解

1.Properties类是什么?

Properties(Java.util.Properties),该类主要用于读取Java的配置文件,不同的编程语言有自己所支持的配置文件,配置文件中很多变量是经常改变的,为了方便用户的配置,能让用户够脱离程序本身去修改相关的变量设置。就像在Java中,其配置文件常为.properties文件,是以键值对的形式进行参数配置的。

2.API 中的Properties类

java.util

类 Properties

java.lang.Object
java.util.Dictionary<K,V>
java.util.Hashtable<Object,Object>
java.util.Properties

所有已实现的接口:

Serializable, Cloneable, Map<Object,Object>

直接已知子类:

Provider

3.API方法

getProperty(String key)   在此属性列表中搜索具有指定键的属性。如果在此属性列表中找不到该键,则会检查默认属性列表及其默认值(递归)。如果未找到该属性,则该方法返回默认值参数。list(PrintStream out)  将此属性列表打印到指定的输出流。此方法对于调试很有用。load(InputStream inStream)  从输入字节流中读取属性列表(键和元素对)。输入流采用加载(Reader)中指定的简单的面向行的格式,并假定使用ISO 8859-1字符编码;即每个字节是一个Latin1字符。不在Latin1中的字符和某些特殊字符在使用Unicode转义符的键和元素中表示。 此方法返回后,指定的流仍保持打开状态。setProperty(String key, String value) 调用 Hashtable 的方法 put 。他通过调用基类的put方法来设置 键值对。store(OutputStream out, String comments) 将此Properties表中的此属性列表(键和元素对)以适合使用load(InputStream)方法加载到Properties表的格式写入输出流。 此Properties方法不会写出此Properties表的defaults表中的属性(如果有)。storeToXML(OutputStream os, String comment, String encoding) 使用指定的编码发出表示此表中包含的所有属性的XML文档。clear()  清除此哈希表,使其不包含任何键。stringPropertyNames()  返回此属性列表中的一组键,其中键及其对应的值是字符串,如果尚未从主属性列表中找到相同名称的键,则包括默认属性列表中的不同键。键或键不是String类型的属性将被省略。

4.Properties类的特点:

  1. 属性列表(键和值)中每个键及其对应值都是一个字符串
  2. 可保存在流中或从流中加载,可以对IO流进行操作。把属性集合存到文件中,或从文件中读取所有的属性。

5.属性文件的格式要求:

​ 属性文件中不能直接汉字,所有的汉字会转成Unicode编码
​ 1) 格式:属性名=属性值
​ 2) 每个属性占一行
​ 3) 注释:以#号开头的行是注释行
​ 4) 属性文件中所有空行被忽略
​ 5) 扩展名:properties

6.常用的方法

1. 构造方法:使用无参的构造方法 new Properties(),创建了一个空的集合。

2. 设置/得到属性值:
不建议使用从Map中继承下来的put()/get()方法添加/获得元素,因为put()可能添加非字符串类型的数据。
添加属性值:setProperty(属性名, 属性值)
得到属性值:String getProperty(“属性名”),如果没有属性,则返回null
String getProperty(“属性名”, “默认值”) 始终保证可以得到一个非空的值
遍历的方法,得到所有的属性名stringPropertyNames()

7.将集合中内容存储到文件

1. 保存到IO流:(字节流,字符流)
store(OutputStream 字节输出流, String 注释)
store(Writer 字符输出流, String 注释)

        @Testpublic void testPropertiesSave() throws IOException {//创建属性集Properties prop = new Properties();prop.setProperty("name", "Jack");prop.setProperty("age", "18");prop.setProperty("gender", "male");//写到FileOutputStream字节输出流FileOutputStream fos = new FileOutputStream("d:/emp.properties");prop.store(fos, "I am a employee");//写到io流中 fos.close();         }

2. 读取文件中的数据,并保存到属性集合

从流中加载:
load(InputStream 字节输入流)
load(Reader 字符输入流)

    @Testpublic void testLoad() throws IOException {//创建一个空的集合Properties properties = new Properties();//创建一个文件输入流FileInputStream fis = new FileInputStream("d:/emp.properties");properties.load(fis);System.out.println(properties);//关闭流fis.close();}

题外话:集合的实现类的存储结构

List集合的常用实现类有ArrayListLinkedList,其中,ArrayList是使用数组结构来存储的,数组结构要求数据在内存中必须是连续的,存在查询效率高,但修改效率低的问题;而LinkedList是使用链表结构来存储的,存在查询效率低,但修改效率高的问题!

Set集合的常用实现类有HashSet,其数据是完全散列的,而LinkedHashSet也是使用链表结构来存储数据的!

**问题:**java.util.Set 有没有像 list 一样的get方法吧?
答案是没有;
List特点:元素有放入顺序,元素可重复
Set特点:元素无放入顺序,元素不可重复
List的get方法需要下标,其实也就是放入顺序,
由于Set没有放入顺序,所以这应该就是没有给Set设计get方法的原因
(扩展:在Set中的位置是由元素的HashCode决定的,位置其实是固定的)
要取Set的值,一般是用iterator() 方法获取迭代器进行操作,
也可以用toArray()方法转成数组来操作。

5. Spring表达式

Eclipse使用小技巧:

1.关闭项目并隐藏:

点击Project Explorer 右侧第三个,像漏斗似的图标,弹出的标签名为:Filters and Customization(过滤器以及自定义的设置),在里面可以点击Closed projects,来关闭项目。

2.删除项目并恢复:

删除项目:

右键删除项目时,会有两行英文:

Delete project contents on disk(cannot be undone):删除磁盘上的项目内容(无法撤消);
Project loction:项目地址;

如果勾选了第一个,那么这个项目就再也找不回来了。

恢复项目:

①在Package Explor空白处点击右键,选择import,将出现窗口;

②在窗口中:点击General,然后选择Existing Projects into Workspace(现有项目到工作区),点击下一步:

③出现以上窗口之后点击Browse,选择你所误删的项目所在的文件夹,然后文件夹中所误删的项目将出现如下图所示的文本框中;

④点击完成即可。

(练习前)

Spring表达式的作用:

(1)获取另一个Bean中的某个属性的值

在使用Spring框架时,可以通过Spring表达式,获取另一个Bean中的某个属性的值!

演示案例:spring03

建立一个类来存储获取到的另一个的Bean中的某个属性的值,创建新的类:ValueBean,会在这个类中声明一些属性,这些属性的值都来自Person类、SampleBean类中的某个的值,例如:

// 当前类中的属性都将来自Person或SampleBean中的某个属性
public class ValueBean {// 值是Person中的username,那么不光要用到Spring表达式,也是一种为属性注入值的操作private String username;//这里是通过Set方法为属性注入值,所以生成Get和Set方法,虽然其实这里注入值只是需要Set方法public String getUsername() {return username;}// 由于username属性的值还是会通过SET方式注入进来,所以,需要有对应的SET方法public void setUsername(String username) {this.username = username;}}

然后,在Spring的配置文件中,需要配置为:

<!-- 以下是使用Spring表达式的做法 -->
有一个新的类valueBean,要交给Spring来管理,所以要有一个新的节点
<bean id="valueBean" class="cn.mypro.spring.ValueBean"><!-- 属性值是Person对象的username的属性值 -->因为是Set方式为属性注入值,一定是在<bean>的子级里去加<property><property name="username" value="#{person.username}"></property>注意:如果value是另一个Bean对象,则用ref,而现在只是Person里面的一个属性,所以用这种方式。</bean>

其实,Spring表达式的基本结构/格式就是使用#{}框住某个式子,如果需要获取另一个Bean中指定的属性的值,格式为:

#{Bean的id.属性的名称}

然后在SpringDemo中添加代码:

//2.从Spring容器中获取对象
ValueBean valueBean = ac.getBean("valueBean",ValueBean.class);
//3.测试所得到的对象
System.out.println(valueBean.getUsername());
//Zoe

这时,对应的Person类必须有getUsername()方法,因为之前在测试构造方法注入属性值的时候没有添加Person类中的Get、Set方法,严格说这里需要的是Get方法,但是开发习惯是连个方法都加上。

Spring表达式的工作原理:属性名称的Get方法:

对于 value="#{person.username}",用这个表达式以后,获取这个username的值,Spring的工作原理,就会调用这个person的一个叫做getUsername()的方法,这里写的username,会基于这个username去组织出getUsername()这样一个方法名称,并调用这个方法。

即,需要注意,Spring框架会根据以上表达式中的“属性名称”拼接出GET方法,并调用该GET方法以获取值!例如以上配置的是#{person.username},则Spring就会拼出getUsername这个名称,并调用getUsername()方法,所以,对应的Person类必须有getUsername()方法!

异常注意:

NotWritablePropertyException:一看到这个异常,一定是属性没有对应的Get、Set方法所导致的。

练习:

 ValueBean类中:    //值是Person中的ageprivate int age;
 Spring中:<property name="age" value="#{person.age}"/>
 在SpringDemo中添加:System.out.println(valueBean.getAge());//26//注意,valueBean.getAge()是不能.getClass()的,因为getAge()的返回值是int类型,基本数据类型是没有getClass()的

网摘:Java中9大内置基本数据类型Class实例和数组的Class实例

1、Java中9大内置几本数据类型:

对于对象来说,可以直接使用对象.getClass()或者Class.forName(className);类名.class都可以获取Class实例.
但是我们的基本数据类型,就没有类的权限定名,也没有getClass方法.
问题:那么如何使用Class类来表示基本数据类型的Class实例?
byte,short,int,long,char,float,double,boolean,void关键字
上述8种类型和void关键字,都有class属性.
表示int的Class对象: Class clz = int.class;
表示boolean的Class对象: Class clz = boolean.class;
表示void的Class对象:Class clz = void.class;
所有的数据类型都有class属性,表示都是Class对象.
思考:
int的包装类是Integer
Integer.class ==?== int.class 相等吗????
结果是false,说明是两份字节码.

Integer 和int是同一种数据类型吗? *不是*

但是在八大基本数据类型的包装类中都有一个常量:TYPE,
TYPE表示的是该包装类对应的基本数据类型的Class实例.
如:


Integer.TYPE----->int.class

Integer.TYPE==int.class;//YES

Integer.TYPE == Integer.class;//ERROR
摘自jdk源码:基本数据类型包装类TYPE的实现。

 /** The {@code Class} instance representing the primitive type* {@code int}.** @since   JDK1.1*/
public static final Class<Integer>  TYPE = (Class<Integer>) Class.getPrimitiveClass("int");/** Return the Virtual Machine's Class object for the named* primitive type.*/
static native Class getPrimitiveClass(String name);
2、数组类型的Class实例

每个数组属于被映射为 Class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象(摘自JDK原话)。
数组的Class实例:
String[] sArr1 ={"A","C"};
String[] sArr2 = {};
String[][] sArr = {};
int[] sArr = {};
表示数组的Class实例:
String[] sArr1 = {"A","C"};
Class clz = String[].class;//此时clz表示就是一个String类型的一位数组类型

所有具有相同元素类型和维数的数组才共享同一份字节码(Class对象);
注意:和数组中的元素没有一点关系

eg:


public static void main(String[] args) {
    String[] s1 = {};String[] s2 = {"A"};String[] s3 = {"A","b"};int[] i ={};    System.out.println(s1.getClass() == s2.getClass());//trueSystem.out.println(s1.getClass() == s3.getClass());//trueSystem.out.println(s2.getClass() == s3.getClass());//trueString[][] s4 = {{"1","2"}};Class zz = s4.getClass();System.out.println(s2.getClass() == zz);//falseClass zz1 = i.getClass();System.out.println(s2.getClass() == zz1);//false
}

原文地址:https://blog.csdn.net/spy19881201/article/details/23995267

(2)获取另一个类的集合属性中的某一个元素

使用Spring表达式时,还可以获取另一个类的集合属性中的某一个元素!例如:

在ValueBean类中添加:

// 值是SampleBean的names中的第3个
private String name;
然后生成Set、Get方法

则配置Spring为:

<!-- 属性值是SampleBean对象中的names(List类型)中的第3个值 -->
<property name="name" value="#{sampleBean.names[2]}"/>

所以,如果要访问的是List集合中的某个值,Spring表达式的语法是:

#{Bean的id.List集合的属性名[索引位置]}

注意:数组和Set集合也是用这种方法。

如果要获取数组中的某个元素,也可以使用以上语法 ,由于Spring在管理Set时使用的实现类是LinkedHashSet,所以,也可以使用以上语法获取Set集合中的某个元素!但是并不推荐这么用Set集合,因为一般用Set集合就不关注第几个,如果要关注,就用List集合。

练习:

 ValueBean类中://练习//值是SampleBean的numbers中的第2个private int i;//值是SampleBean的cities中的第4个private String city;
 Spring中:<!-- 属性值是SampleBean对象中的numbers(int[]数组类型)中的第2个值 --><property name="i" value="#{sampleBean.numbers[1]}"/><!-- 属性值是SampleBean对象中的cities(Set类型)中的第4个值 --><property name="city" value="#{sampleBean.cities[3]}"/>

最后:

 在SpringDemo中添加:System.out.println(valueBean.getI());//5System.out.println(valueBean.getCity());//Shenzhen

(3)获取MapProperties类型的数据中的某个元素

如果需要获取MapProperties类型的数据中的某个元素(没有顺序的),例如:在类ValueBean中:

先是Properties类型
// 值是SampleBean的profile中的gender
private String gender;
// 添加Set、Get方法然后是Map类型
// 值是SampleBean的session中的password
private String password;
// 添加Set、Get方法

则Spring需要配置为:

<!-- 属性值是SampleBean类(对象)中的profile属性(Properties类型)中的gender的值 -->
在类SampleBean有:
//name:Jack, age:25, gender:Male
private Properties profile;
<property name="gender" value="#{sampleBean.profile.gender}"/><!-- 属性值是SampleBean类(对象)中的session属性(Map类型)中的password的值 -->
在类SampleBean有:
// {username=root,password:1234, from:Beijing}
private Map<String, String> session;
<property name="password" value="#{sampleBean.session.password}"/>

也就是说,获取MapProperties类型中的值时,Spring表达式的语法是:

#{Bean的id.Map或Properties类型数据的名称.Map的Key或Properties的属性名}

另外,还可以写成:

#{Bean的id.Map或Properties类型数据的名称['Map的Key或Properties的属性名']}

例如:

<!-- 属性值是SampleBean对象中的session(Map类型)中的password的值 -->
<property name="password" value="#{sampleBean.session['password']}"/>

两种方式对比:

<property name="password" value="#{sampleBean.session.password}"/>
<property name="password" value="#{sampleBean.session['password']}"/>

没有了点,加了单引号和中括号,但是没有必要这样写,这样写还麻烦,没有之前方便。

最后:

在SpringDemo中测试输出:

System.out.println(valueBean.getGender());
//Male
System.out.println(valueBean.getPassword());
//1234

6. 自动装配

1.用新案例来演示自动装配:

创建项目spring04,用来模拟一下真实的开发环境,比如要处理用户的注册或者登录需求,一般来说项目中会有Servlet的类,在这里模拟,在包cn.mypro.spring中存在UserLoginServlet类,因为不是真实的Servlet,所以不要求去继承谁。

(1)在cn.mypro.spring包中创建UserLoginServlet

package cn.mypro.spring;
/*** 模拟实际工作中登录的Servlet*/
public class UserLoginServlet {private UserDao userDao;public void doPost() {System.out.println("UserLoginServlet.doPost()");userDao.login();}public void setUserDao(UserDao userDao) {this.userDao = userDao;}/** 1.先重写doPost方法,使用syst快捷键(如果是IDEA中的快捷键是soutm);* 2.以前学习Servlet的阶段,如果要处理用户的登录,那现在必然要连接数据库,去做数据库的操作,*     比如数据库的查询等等,但是数据库的操作我们并不会写在doPost方法中;* 3.在以前上课的时候,用过UserDao,以前学习的时候,UserDao就是用来实现注册的,这里用来模拟登陆,*    所以新建UserDao类;* 4.在UserLoginServlet类中,最终实现用户登陆的时候,肯定是用userDao去调用它的login方法来实现,*      但是这时候还没有userDao,所以声明一个userDao,即在前面添加代码:private UserDao userDao;*       以前写代码就是这样的套路,声明一个userDao,然后去调用userDao的login方法;* 5.此时准备工作已经做完,但是此时userDao是没有值的,所以用Spring注入值的方式赋值,那么首先,*   要生成Set方法(Get方法不需要,多余);* 6.如果userDao一直没有值,那么userDao.login();这句必出错:*   会报空指针异常:NullPointerException* 7.然后建立SpringDemo类,1、2、3、4点;* 8.通过Set注入方式来注入值,配置Spring文件:* */
}

(2)在cn.mypro.spring包中创建UserLoginServlet

package cn.mypro.spring;
/*** UserDao的作用就是用来实现注册的,但是这里模拟登陆,* 模拟一下真实的生成环境*/
public class UserDao {public void login() {System.out.println("UserDao.login()");}
}

(3)在cn.mypro.spring包中创建SpringDemo

package cn.mypro.spring;import org.springframework.context.support.ClassPathXmlApplicationContext;public class SpringDemo {public static void main(String[] args) {//1.加载Spring的配置文件,获取到Spring容器acClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml");//2.从Spring容器中获取所需的对象UserLoginServlet userLoginServlet = ac.getBean("userLoginServlet",UserLoginServlet.class);/** 用快捷键写这句代码:* 1.类名写到一半:UserL,就用快捷键生成出来;* 2.然后空格,然后继续快捷键,选择与类名一样但是首字母小写的名字;* 3.把 = ac.getBean(""); 剩下的这些写完;* 4.把userLoginServlet复制粘贴到双引号中;* 5.再把类名UserLoginServlet复制粘贴后,点class。*///3.测试:不看其中的属性,只是看这个方法能不能运行//用获取到的userLoginServlet这个对象来调用doPost方法userLoginServlet.doPost();//4.关闭(释放资源)ac.close();    }
}

(4)开始配置:Spring.xml文件

src/main/resources下的配置文件Spring.xml,进行配置:

1.用最原始的Set方式来注入值:
<bean id="userDao" class="cn.mypro.spring.UserDao"></bean><bean id="userLoginServlet" class="cn.mypro.spring.UserLoginServlet" ><property name="userDao" ref="userDao"></property><!-- name="userDao"这里name对应的是属性名称,也就是类UserLoginServlet中的这个属性private UserDao userDao,严格的说是对应的Set方法的名称,但是就理解为属性名就行。而这个private UserDao userDao应该有值,它的值应该是UserDao的对象!但是现在Spring来看还没有userDao,所以要在前面先把userDao给配出来,这样的话Spring才有userDao,所以要让Spring先创建cn.mypro.spring.UserDao这个类的对象,只有Spring创建这个对象之后,才有可能赋值到<property name="userDao"></property>这个属性中来。以上就是用最原始的方式来注入值。--></bean>

然后,运行SpringDemo中的main方法,输出为:

UserLoginServlet.doPost()
UserDao.login()

配置成功。

2.自动装配方式:

自动装配方式就不用了写那么多了,只是在<bean>标签中加入一个autowire属性即可:

按照名字自动装配:

<bean id="userDao" class="cn.mypro.spring.UserDao"></bean><bean id="userLoginServlet" class="cn.mypro.spring.UserLoginServlet" autowire="byName">
</bean>

下面详细介绍自动装配:

2.自动装配介绍与机制:

**自动装配介绍:**在配置某个类的<bean>时,如果需要为其中的某些属性注入值,不必添加子级的<property>节点来注入,只需要在<bean>节点配置autowire属性即可,Spring框架就会完成“自动装配”!

自动装配的机制:Spring框架会在容器中查询匹配的对象,为需要自动装配的Bean的属性自动赋值!

例如:

<bean id="userDao" class="cn.mypro.spring.UserDao"></bean><bean id="userLoginServlet" class="cn.mypro.spring.UserLoginServlet" autowire="byName">
</bean>

**按照名称自动装配:**在运行时,Spring会创建一个UserDao的类型的对象(根据第一个<bean>节点中的全类名),然后,创建UserLoginServlet的对象时,由于对应的<bean>节点配置了autowire属性,则Spring还会查找UserLoginServlet类中有哪些属性,其实,其实质是看属性对应的Set方法的名字,

就在这个类中找到了private UserDao userDao;这个属性,其实,其实质是找

同时,在Spring容器已经存在UserDao的对象,与private UserDao userDao;属性是匹配的,就会调用该属性的SET方法为这个属性注入值!整个过程是Spring框架自动完成的,开发人员只需要在配置时添加autowire属性的配置即可!这个就是Spring框架的自动装配机制

3.autowire的值:

在配置时,autowire属性的常用取值可以是byName,表示“按照名称实现自动装配”,也可以是byType,表示“按照类型实现自动装配”,还有其它取值,但一般不使用!

(1)按照名称实现自动装配:

autowire的取值byName时:

例如在XML配置中存在:

<bean id="userDao" class="cn.mypro.spring.UserDao"></bean><bean id="userLoginServlet" class="cn.mypro.spring.UserLoginServlet" autowire="byName">
</bean>

要实现:把UserDao这个类的对象,装配到UserLoginServlet这个类中的private UserDao userDao;这个属性中,那么在配置文件Spring.xml对应的<bean>节点中,class属性是cn.mypro.spring.UserDao这个类的id值,要和UserLoginServlet这个类中的属性名称相同,本质上是和UserLoginServlet这个类中的Set方法名称要一致。

即:

<bean id="userDao" class="cn.mypro.spring.UserDao"></bean>

中的id值,要和下面UserLoginServlet类中:

Public class UserLoginServlet {private UserDao userDao;public void doPost() {System.out.println("UserLoginServlet.doPost()");userDao.login();}//Set方法public void setUserDao(UserDao userDao) {this.userDao = userDao;}

Set方法的名字一致!

一致的意思是:

比如id=“dao”,那么Set方法名要是:setDao,即:Set方法名=set+首字母大写的id名

所以:

按名字自动装配的本质是:

Spring 的本质是,当看到UserLoginServlet这个类,在<bean>中有自动装配的时候,Spring就会去找UserLoginServlet这个类,看这个类中有哪些Set方法,然后在配置里看这个Set方法有没有与之一样名字的<bean>节点,有的话,就把这个<bean>作为参数,拿来调用了

 public void setUserDao(UserDao userDao) {this.aaaaa = userDao;}

这个方法,由于调用了这个方法,那么aaaaa这个就有值了,那么在下面:

public class UserLoginServlet {private UserDao aaaaa;public void setUserDao(UserDao userDao) {this.aaaaa = userDao;}public void doPost() {System.out.println("UserLoginServlet.doPost()");aaaaa.login();}

aaaaa.login();这句代码中就可以正常的使用了。

所以,按照名称实现自动装配,其本质依然是通过Set方法来注入的,要求名称是一致的

(2)按照类型实现自动装配:

当取值为byType时,对各个名称都没有要求(包括SET方法,也只需要是以set 作为前缀即可,后缀没有任何要求),只要类型能够匹配,就可以实现自动装配的效果!但是,能够匹配类型的对象必须最多只有1个,如果有2个或更多个,则在加载Spring配置文件时就会报错!

**异常:**NoUniqueBeanDefinitionException:不唯一的Bean匹配异常。

在后面附9里讲解了这个错误

无论是使用byName还是byType,Spring框架都是尝试在Spring容器中查找匹配的对象进行注入,如果完全没有匹配的对象,就会放弃装配,并不会导致装配过程出现错误!

<bean>节点中添加autowire实现自动装配是比较方便的,但是,在实际开发时,永远不会这样来处理!因为当某个类中有若干个属性时,这个又被配置了autowire,则哪些属性已经成功的装配了值,而哪些却没有被装配值,从代码中的表现是非常不直观的!比如看的是别人的代码。

所以此时,重点理解byNamebyType的特征即可!后面会学更好的方法。

网摘备注:

1.按 Bean 名称自动装配存在的问题。

按照 Bean 名称自动装配存在错误装配 JavaBean 的可能,如果配置文件中定义了与需要自动装配的 JavaBean 的名称相同而类型不同的 JavaBean ,则会错误的注入不同类型的 JavaBean。

2.关于 byName 与 byType 两者之间的区别:

byName 通过 Bean 的名称自动装配。如果一个 Bean 的 name  和另外一个 Bean 的 name 相同,就自动装配。
byType 通过 Bean 的属性类型自动自动装配。如果一个 Bean 的属性类型和另一个 Bean 的属性类型兼容,就自动装配。

3.无论是按照 Bean 的类型,还是按照 Bean 的名称,自动装配有时候都会出现无法自动装配的情况。例如在配置文件中再添加一个 User 类的实现对象,byType 自动装配类型会因为无法自动识别装配那一个JavaBean 而抛出 org.springframework.beans.factory.UnsatisfiedDependencyException 异常。要解决此类问题,只能通过混合使用手动装配来指定装配哪个 JavaBean 。

Eclipse批量改名小技巧:

先选中任意需要改名的变量、方法等名称,再按下快捷键:Ctrl+2,全部松开,然后按字母R。

或者:

在Refactor—Rename,快捷键是:Alt + Shift + R。

重写方法速写:

重写方法没有快捷键,但可以先打出来父类的方法名称,然后用Alt+/

7. Spring注解(Spring阶段最重要)

这一部分,除了Set注入,其他学的,都可以用Spring注解来用。

7.1. 组件扫描与通用注解

1.组件扫描

在Spring的配置文件中添加:

<!-- 组件扫描     component:组件(重要的、核心的组成部件)  scan:扫描-->
<!-- base-package属性:被扫描的 根级包 -->
<context:component-scan base-package="cn.mypro.spring"/>

当Spring的配置文件被加载时,Spring框架就会扫描以上配置的cn.mypro.spring包及其子包下的所有内容,检查这些包中是否存在组件,如果存在,就会自动创建这些组件的对象!

每次去加载这个Spring文件,都会扫描包下面有没有哪个类是需要被new的,然后就会自己去new了。

注意:不要写成base-package="cn.mypro"base-package="cn",会造成扫描范围过大。

2.添加注解

所以,在cn.mypro.spring包中,还可以创建组件类User,例如:

package cn.mypro.spring;
//【条件1:】这个类在这个包中
//【条件2:】组件注解
//两个条件缺一不可
import org.springframework.stereotype.Component;//注解:放在类前
@Component
public class User {}

只有添加了组件注解的类才是组件类!也就是说,如果User类在组件扫描的包中,却没有添加组件注解,Spring并不会创建这个类的对象!

然后创建SpringDemo类:

package cn.mypro.spring;import org.springframework.context.support.ClassPathXmlApplicationContext;public class SpringDemo {public static void main(String[] args) {//1.加载Spring的配置文件,获取Spring容器ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml");//2.从Spring容器中获取由Spring管理的对象//Spring中的Bean的id值:默认是根据类名将首字母改为小写作为Bean的id//这里的"user"是对应将User类改成首字母小写的User user1 = ac.getBean("user",User.class);//3.测试System.out.println(user);//cn.mypro.spring.User@401e7803//输出一个user的对象//4.关闭(释放资源)ac.close();}

Bean的id:默认和更改

Spring中默认是根据类名将首字母改为小写作为Bean的id,所以,如果要获取以上User类的对象,则应该使用user作为Bean的id,例如:

User user = ac.getBean("user", User.class);

3.注解中添加参数:

如果需要自行指定Bean的id,可以在@Compontent注解中添加参数!例如:

在User类中:

package cn.mypro.spring;
import org.springframework.stereotype.Component;@Component("uuu")
public class User {}

那么在SpringDemo类中:

public class SpringDemo {public static void main(String[] args) {//2.从Spring容器中获取由Spring管理的对象//这里就要写"uuu"了User user1 = ac.getBean("uuu",User.class);}

一般很少去改这个名字

4.其他注解介绍:

还可以使用@Controller@Service@Repository这几种注解,在Spring框架的作用范围之内,它们的作用与@Compontent完全等效的,使用方式也完全相同,在需要添加注解时,添加以上任意一个即可!

(注意,其他框架这几个注解不一定等效。)

这些注解的语义是不同的,在后续的课程中,还会在项目中添加其它有特殊定位的组件,例如控制器类型的组件应该添加@Controller注解,业务类型的组件就应该添加@Service注解,处理数据持久化的组件就应该添加@Repository注解,其它类型的组件就应该添加@Compontent注解!

7.2. 管理对象作用域与生命周期的注解

1.对象的作用域的注解:

类的声明之前添加@Scope注解可以配置该类被Spring框架管理时是否单例!该注解中可以添加参数"singleton""prototype"

当设置为@Scope("singleton")时,表示单例,这是默认的管理方法,默认是单例(且饿汉式加载)

还可以设置为@Scope("prototype"),表示非单例,例如:

@Scope("prototype")
@Component
public class User {public User() {System.out.println("User.User()");}//注意:用一个类是可以添加多个注解的,并且多个注解不区分先后顺序
}

使用@Lazy注解可以配置该类是否为懒加载模式,可以在这个注解中配置的值是布尔值!一般,如果该类不需要是懒汉式加载,则不添加该注解,如果需要是懒汉式加载,直接添加@Lazy注解即可,并不需要设置注解参数!同时,需要先保证该类是单例模式的,才讨论是否懒加载的问题

不需要懒加载——不添加@Lazy注解,因为默认是饿汉式加载;

懒加载——添加@Lazy即可,因为@Lazy和@Lazy(true)是一样的。

@Scope("singleton")
@Component
@Lazy
public class User {public User() {System.out.println("User.User()");}}

2.对象生命周期的注解:

由Spring所管理对象的声明周期,只有初始化方法和销毁方法

比如在User类中自己写出这两个方法:

 //生命周期方法(一定是没有参数列表的)public void onCreate() {//创建方法System.out.println("User.onCreate()");}public void onDestroy() {//销毁方法System.out.println("User.onDestroy()");}

自定义的生命周期方法之前添加@PostConstruct可以表示该方法是生命周期方法中的初始化方法,添加@PreDestroy注解就表示销毁方法,例如:

@PostConstruct
public void onCreate() {System.out.println("User.onCreate()");
}@PreDestroy
public void onDestroy() {System.out.println("User.onDestroy()");
}

这两个注解写完,有的版本可以导包,有的不能导包,

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

是因为这不是Spring的注解,是JavaEE的注解:

像之前Spring的注解的导包:

import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

这是Spring的注解。

那么JavaEE的注解从哪里来呢?

点击项目右键——点击属性(Properties)——查找Targeted Runtimes ,勾选对应的Apache Tomcat版本

(也就是2月4号第2点中的第5步)

之后这两个注解就可以导包了。有的没有Tomcat也能导包。

注意:

 /** 从Java EE5规范开始,Servlet中增加了两个影响Servlet生命周期的注解,* @PostConstruct和@PreDestroy,这两个注解被用来修饰一个非静态的void()方法。* * Construct:构造方法    Destroy:销毁* Post:在什么之后        Pre:在什么之前* * PostConstruct:在构造方法之后* PreDestroy:在销毁之前* * 写法有如下两种方式:@PostConstructpublic void someMethod(){}或者public @PostConstruct void someMethod(){}被@PostConstruct修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器执行一次。PostConstruct:在构造函数之后执行,init()方法(初始化方法)之前执行。PreDestroy:在destroy()方法执行之后执行*/

7.3. 自动装配的注解(重点)

之前提到:

<bean>节点中添加autowire实现自动装配是比较方便的,但是,在实际开发时,永远不会这样来处理

所以推荐的是:用自动装配的注解来解决这个问题:

和之前一样,在spring05项目中:

1.新建UserDao类

package cn.mypro.spring;import org.springframework.stereotype.Component;
/*自动装配的机制:Spring框架会在容器中查询匹配的对象,为需要自动装配的Bean的属性自动赋值!
例如:
<bean id="userDao" class="cn.mypro.spring.UserDao"></bean>
<bean id="userLoginServlet" class="cn.mypro.spring.UserLoginServlet" autowire="byName">
</bean>在运行时,Spring会创建`UserDao`类的对象(根据第一个`<bean>`节点中的全类名),然后,创建`UserLoginServlet`的对象时,由于对应的`<bean>`节点配置了`autowire`属性,则Spring还会查找`UserLoginServlet`类中有哪些属性,就在这个类中找到了`private UserDao userDao;`这个属性,同时,在Spring容器已经存在`UserDao`的对象,与`private UserDao userDao;`**属性是匹配的,就会调用该属性的SET方法为这个属性注入值**!整个过程是Spring框架自动完成的,开发人员只需要在配置时添加`autowire`属性的配置即可!这个就是Spring框架的**自动装配机制**!
*/
//根据在运行时,Spring会创建`UserDao`类的对象,所以在UserDao类前加@Component注解,其实4个注解在Spring框架的作用范围之内,都是完全相同的,写哪个都行。@Component
public class UserDao {public void login() {System.out.println("UserDao.login()");}
}

2.新建UserLoginServlet类,区别在于,private UserDao userDao;这个属性没有写Set方法,不需要写。

在需要被自动装配的属性之前添加@Autowired注解,就可以表示接下来的这1个属性将会被自动装配,例如:

这种做法,就是以后工作中最常用的做法

//然后,创建`UserLoginServlet`的对象
@Component
public class UserLoginServlet {//就只是这一个属性是需要被自动装配的@Autowiredprivate UserDao userDao;//根据:按照名字自动装配的规则,这个属性名字,要和<bean>id的名字相同//而在UserDao中,有@Component public class UserDao {...//如果不写,则默认id值为userDao//则属性名和id名相同private UserDao name;//这个属性不会自动装配//因为每个属性要被自动装配都要在前面写上一个注解,所以就很直观了。public void doPost() {System.out.println("UserLoginServlet.doPost()");userDao.login();}}

在使用自动装配时,也可以使用@Resource注解!也就是说,@Autowired@Resource这2个注解选取其中的1个来使用就可以了!


附:关于@Autowired和@Resource的区别(面试题)

共同点:使用这2种注解都可以实现自动装配!

区别

@Resource注解是javax包中的注解,它是优先byName来装配的,如果byName无法装配,则会自动尝试byType装配,在byType装配时,要求匹配类型的对象必须有且仅有1个,如果无法装配,则会报告错误;与上面讲的自动装配不同。

@Autowired注解是Spring框架中的注解,它是优先byType来装配的,但是,这个过程中,只会检索匹配类型的对象的数量,并不直接装配,如果找到的对象的数量是0个,则直接报错,如果找到的对象的数量是1个,则直接装配,如果找到的对象的数量(类型)超过1个(2个或更多个),则会尝试byName来装配,如果byName装配失败,则报错。(虽然进行试验不能直接看出来,但是这个答案的依据是来自Spring的官方文档,是正确的。)

面试时,要知道区别后应该怎么用?

在实际开发项目时,绝大部分情况下,需要装配的对象都是有且仅有1个的,并且命名都是规范的,所以,无论byTypebyName都是可以装配成功,就不必在乎装配方式和做法,在以上2个注解的选取方面,通常也没有明确的要求

补充

@Autowired为Spring提供的注解,需要导入包org.springframework.beans.factory.annotation.Autowired。
@Resource注解由J2EE提供,需要导入包javax.annotation.Resource。

关于:JavaSE(标准版)、JavaEE(企业版)与JavaME(微型版)

Java分三个版本: JavaSE(标准版)、JavaEE(企业版)、JavaME(微型版) ,其中JavaSE 是Java编程语言的基础, JavaEE是用于公司的PC端开发的,而JavaME是用于移动端开发的。

1、Java SE ( Java Platform , Standard Edition )。

Java SE以前称为J2SE。它允许开发和部署在桌面、服务器、嵌入式环境和实时环境中使用的Java 应用程序。

Java SE包含了支持Java Web服务开发的类,为Java Platform ,Enterprise Edition ( JavaEE )提供基础。

2、 Java EE( Java Platform , Enterprise Edition )。

这个版本以前称为J2EE。企业版本帮助开发和部署可移植、健壮、可伸缩且安全的服务器端Java 应用程序。

JavaEE是在JavaSE的基础上构建的,它提供Web服务、组件模型、管理和通信API ,可以用来实现企业级的面向服务体系结构( service-orientedarchitecture , SOA )和Web 2.0应用程序。

3.、Java ME ( Java Platform , Micro Edition )。

这个版本以前称为J2ME。Java ME为在移动设备和嵌入式设备(比如手机、PDA、电视机顶盒和打印机)上运行的应用程序提供一 个健壮且灵活的环境。

Java ME包括灵活的用户界面、健壮的安全模型、许多内置的网络协议以及对可以动态下载的连网和离线应用程序的丰富支持。


附:常见问题解决方案

1. 关于下载jar包的问题

如果下载的jar包是坏的,主要表现就是无法识别某些类,无法导包,例如ClassPathXmlApplicationContext无法导包,就一定是因为没有添加spring-context依赖,下载的相关jar包是损坏的,可以在Eclipse左侧的列表中看到jar包是否存在,如果不存在,则添加依赖,如果已存在,则根据显示的路径,删除对应的jar包,并对项目点击右键,选择Maven > Update Project,并在弹出的对话框中勾选Force Update后确定,更新后应该可以解决问题,当然,也可以不删除原有jar包,直接在pom.xml中更换所使用的依赖的版本即可!

2. 关于Spring的配置文件报错的问题

如果报错的提示在根节点<beans>的配置中,则可以无视这个问题,在网络畅通的情况下会自动解决,并且,即使报错,也不影响开发和运行项目!

如果一定要解决这个问题,应该先检查Eclipse的设置的XML > XML Catalog中第一个User Specified Entries有没有自定义设置,如果此前在中心学习使用的电脑是有可能配置了自定义设置的,在中心的自定义设置中指定了使用公司内网服务器,没有连接到公司内网时是不可用的,所以,需要删除这些自定义设置。

另外,也可以尝试切换网络,例如使用手机热点,并重启Eclipse,如果成功解决问题了,就可以把手机热点关闭,改为使用WiFi即可!

3. 关于Spring配置文件没有代码提示的问题

参考以上“关于Spring的配置文件报错的问题”解决方案。

4. 关于运行项目时很久才能运行甚至始终卡住的问题

参考以上“关于Spring的配置文件报错的问题”解决方案。

如果项目运行超过半分钟甚至更久都没有成功运行,则重启Eclipse。

5. 关于自定义设置混乱的问题

如果某些自定义设置不符合现在的使用需求,甚至设置错了却不知道怎么还原,可以先关闭Eclipse,再删除Workspace下的隐藏项目(在Windows系统中是以小数点作为第1个字符的文件夹),然后再打开Eclipse,所有的设置都会被还原为初始设置,另外,也可以更换Workspace。

当所有设置全部还原成默认后,需要自行指定Maven仓库,例如使用aliyun(阿里云)的Maven配置文件。(这是我们用了Eclipse之后唯一一个必须要改的设置)

6. 关于maybe not public or not valid的错误

例如:

Caused by: org.springframework.expression.spel.SpelEvaluationException: EL1008E: Property or field 'age' cannot be found on object of type 'cn.mypro.Person' - maybe not public or not valid?

分析:在提示的类cn.mypro.Person中没有age属性,或这个属性不可用,或没有对应的SET/GET方法!

7. 关于NoSuchBeanDefinitionException异常

例如:

Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'username' available

分析:一定是在程序运行过程中,尝试获取名为username的Bean,例如调用了getBean("username"),但是,在Spring容器中却没这个Bean,可能是根本没有配置这个Bean,或者配置时名称写错了。

8. 关闭Eclipse构建项目时的XML自动验证

如果在Progress中一直有载入条卡着,那么:Progress(进程)

在:Window—Validation—XML Validator中

关闭对应上面的Build的对号,取消。将右侧XML Validator中的Build一列取消勾选,则构建项目时不会再验证XML。

但是能不关闭,最好还是不关闭,错误能及时的暴露出来反而是一件好事

左侧的Manual:是编写代码时是否报错;

右侧Build:指的是运行时是否要检查。

9. 关于expected single matching bean but found 2的问题

问题:

二月 06, 2020 2:36:22 下午 org.springframework.context.support.AbstractApplicationContext refresh
警告: Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'userLoginServlet' defined in class path resource [spring.xml]: Unsatisfied dependency expressed through bean property 'userDao'; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'cn.mypro.spring.UserDao' available: expected single matching bean but found 2: userDao,subUserDao
Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'userLoginServlet' defined in class path resource [spring.xml]: Unsatisfied dependency expressed through bean property 'userDao'; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'cn.mypro.spring.UserDao' available: expected single matching bean but found 2: userDao,subUserDao***——>Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'cn.mypro.spring.UserDao' available: expected single matching bean but found 2: userDao,subUserDao

分析:使用了**根据类型(byType)**的自动装配方式,这种自动装配要求最多只有1个匹配类型的对象,但是,在Spring容器中却出现了2个这样的对象,Spring就不知道应该装配哪一个了,就会出现错误!

解决方案:使得Spring只管理其中1个类型的对象,或者,使用byName方式来装配。

**异常:**NoUniqueBeanDefinitionException:不唯一的Bean匹配异常

翻译:

Unique:唯一的

Definition:定义

qualifying:合适的、匹配的

available:可获得的、可用的

expected:期望

single:一个

matching:匹配的

sub:附属的

Spring框架(全)相关推荐

  1. spring框架解析(全)

    spring概述 spring容器启动 springIoc控制反转 spring依赖注入 spring事务 spring事件监听 springaop面向切面编程 –AOP用例搭建 –注解配置 Spri ...

  2. Spring 系列: Spring 框架简介

    Spring 是一个开源框架,是为了解决企业应用程序开发复杂性而创建的.框架的主要优势之一就是其分层架构,分层架构允许您选择使用哪一个组件,同时为 J2EE 应用程序开发提供集成的框架. 在这篇由三部 ...

  3. Spring框架介绍及使用(转载)

    原文链接 Spring框架-控制反转(IOC) 1 Spring框架概述 1.1 什么是Spring Spring是一个开源框架,Spring是于2003 年兴起的一个轻量级的Java 开发框架,由R ...

  4. Spring框架入门

    目录 Spring框架概述 1.Spring框架组成 2.使用spring有什么好处 3.为什么使用spring框架 4.Spring快速入门 4.1Spring IoC底层实现原理 4.2下载Spr ...

  5. Spring框架入门基础,不可多得的干货

    Spring ORM:Spring 框架插入了若干个 ORM 框架,从而提供了 ORM 的对象关系工具,其中包括 JDO.Hibernate 和 iBatis SQL Map.所有这些都遵从 Spri ...

  6. Spring框架简介

    Spring框架简介 Spring Framework 是一个开源的Java/Java EE全功能栈(full-stack)的应用程序框架,以Apache许可证形式发布,也有.NET平台上的移植版本. ...

  7. Spring框架使用规范和IOC的开发

    Spring框架是一个管理对象的容器,就像Web容器Tomcat可以管理servlet的生命周期,Spring容器可以管理对象的生命周期. Spring的组件化让类和类对象之间的关系变得不那么紧密,降 ...

  8. spring框架所有包解释

    2019独角兽企业重金招聘Python工程师标准>>> spring依赖的jar包如下: 下面是每个jar包的说明 spring.jar 是包含有完整发布模块的单个jar 包.但是不 ...

  9. spring框架搭建第一天

    文章目录 1. 从jdbc的标准代码了解程序的耦合性 简单理解耦合:程序之间的依赖关系 2. 基本的三层 dao层 service层 模拟的controller层(servlet) 存在的问题 3. ...

最新文章

  1. Qt最新版5.13在Windows环境静态编译安装和部署的完整过程(VS 2017/VS 2019)
  2. 为什么我们要做三份 Webpack 配置文件
  3. SAP 电商云 Spartacus UI 同 SAP Customer Data Cloud 集成运行时的 api
  4. python中回车怎么表示_如何在python中使用读取行仅拆分回车符?
  5. qt中使用QCompleter实现查找功能
  6. MySQL实战 | 01 当执行一条 select 语句时,MySQL 到底做了啥?
  7. mysql test 映射到实体_第80天:Python 操作 MySQL
  8. SQL语句- 条件查询
  9. 【原创】基于Qt5.14的一站式安卓开发环境搭建
  10. PC端模拟微信/QQ/钉钉运行环境 解决 请在微信客户端打开链接 解决 2021
  11. 操作系统的基本特征、区别及功能
  12. 解决 /lib64/libstdc++.so.6: version `GLIBCXX_3.4.20‘ not found (required by 问题
  13. 半监督分类算法简述,self-trainning,co-trainning
  14. 推荐一款全能测试开发神器!1分钟快速上手!
  15. has a default child route. When navigating to this named route 。。。。。报错
  16. 多益网络2015校园招聘第二次笔试题
  17. UBLOX板卡基础设置--F9P板卡配置(基准站和流动站)
  18. 小学生python游戏编程arcade----excel调用
  19. pom里配置阿里云仓库
  20. 【简单数学】【NOIP 20008】笨小猴

热门文章

  1. 用python编写神经网络_用Python实现神经网络(第5周)的代价函数
  2. Kalman滤波C语言和C++版本实现
  3. 随记:python-reduce()函数
  4. C++ particle code translation
  5. SQL34 批量插入数据
  6. 华为防火墙ipsec vp*实例配置
  7. java:包装类类型判等、比较大小
  8. 2017年04月19日
  9. 最新中国福彩分析大数据分析大师拥有双色球数据展示微信小程序源码支持双色球数据分析多个接口
  10. android studio mac jdk1.8,Mac下修改Android Studio的默认JDK版本