本笔记为本人在B站学习时整理而成,为了更好的学习,将其整理成笔记,以防忘记相关知识点。
Spring概述
概述
Spring:
出现在2002年左右,降低企业级开发难度。帮助进行模块之间、类与类之间的管理,帮助开发人员创建对象,管理对象之间的关系。
2003年传入国内,被大量使用。
2017出现新的流行框架SpringBoot,核心思想与Spring相同。
核心技术:
IoC、AOP,能使模块之间、类之间解耦合。
依赖:
class A使用class B的属性或方法,称之为class A依赖class B。
官网:
https://spring.io/
优点
(1)轻量:Spring的所需要的jar包都非常小,一般1M以下,几百kb。核心功能所需要的jar包总共3M左右。Spring框架运行占有资源少,运行效率高,不依赖其他jar。
(2) 针对接口编程,解耦合
(3) AOP 编程的支持
(4) 方便集成各种优秀框架
(2)(3)(4)的优点将会在接下来学习中体会。
Spring 体系结构
Spring 由 20 多个模块组成,它们可以分为数据访问/集成(Data Access/Integration)、 Web、面向切面编程(AOP, Aspects)、提供JVM的代理 (Instrumentation)、消息发送(Messaging)、 核心容器(Core Container)和测试(Test)。
spring全家桶:
spring , springmvc ,spring boot , spring cloud
框架怎么学: 框架是一个软件,其它人写好的软件。
1)知道框架能做什么, mybatis能访问数据库, 对表中的数据执行增删改查。
2)框架的语法, 框架要完成一个功能,需要一定的步骤支持的,
3)框架的内部实现, 框架内部怎么做。 原理是什么。
4)通过学习,可以实现一个框架。
IoC控制反转
控制反转(IoC,Inversion of Control),是一个概念,是一种思想。指将传统上由程序代 码直接操控的对象调用权交给容器,通过容器来实现对象的装配和管理。控制反转就是对对 象控制权的转移,从程序代码本身反转到了外部容器。通过容器实现对象的创建,属性赋值, 依赖的管理。
Ioc 的实现:
➢ 依赖查找:DL( Dependency Lookup ),容器提供回调接口和上下文环境给组件。
➢ 依赖注入:DI(Dependency Injection),程序代码不做定位查询,这些工作由容器自行完成。
Spring 框架使用依赖注入(DI)实现 IoC。
Spring 容器是一个超级大工厂,负责创建、管理所有的 Java 对象,这些 Java 对象被称为 Bean。
Spring 容器管理着容器中 Bean 之间的依赖关系,Spring 使用“依赖注入”的方式 来管理 Bean 之间的依赖关系。使用 IoC 实现对象之间的解耦合。
控制反转的理解
控制反转, 是一个理论,概念,思想。
控制: 创建对象,对象的属性赋值,对象之间的关系管理。
正转:由开发人员在代码中,使用new 构造方法创建对象, 开发人员主动管理对象。
把原来的开发人员管理,创建对象的权限转移给代码之外的容器实现。 由容器代替开发人员管理对象,创建对象,给属性赋值。
为什么要使用 ioc : 目的就是减少对代码的改动, 也能实现不同的功能。 实现解耦合。
底层实现是反射机制。
Spring的第一个程序
实现步骤:
(1)添加module,新建maven工程,选择maven-archetype-quickstart
骨架模板
(2)填写工程坐标:
GroupId:com.kwxy
ArtifactId:ch01-hello-spring
Version:1.0-SNAPSHOT
(3)Module name设置为:ch01-hello-spring,点击finish,等待maven项目的构建,控制台显示BUILD SUCCESS字样,则构建成功
(4) 将src/main
和src/test
下的java
目录右键Mark Directory as-->Sources Root
(5)在src/main
下新建resources
目录,右键Mark Directory as-->Resources Root
(6)删除默认创建的APP类文件 ,这些文件分别在main和test目录中
(7)删除pom.xml文件中无关的配置,将编译和运行jdk版本改为1.8
(8)在build标签
中添加’指定资源位置’的配置,配置大致如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.kwxy</groupId>
<artifactId>ch01-hello-spring</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<!--单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/java</directory><!--所在的目录-->
<includes><!--包括目录下的.properties,.xml 文件都会扫描到-->
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
</project>
(9)添加Spring依赖(加完最好右键pom.xml->Maven->Reimport
)
<!--Spring的依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.4.RELEASE</version>
</dependency>
<!--
添加spring-context依赖后,会自动关联以下jar
spring-aop
spring-beans
spring-core
spring-expression
-->
(10)创建业务接口和实现类
接口:
package com.kwxy.service;
public interface SomeService {
void doSome();
}
实现类:
package com.kwxy.service.impl;
import com.kwxy.service.SomeService;
public class SomeServiceImpl implements SomeService {
/**
* spring默认调用无参数构造方法创建对象。
* 如果没有无参数构造方法,报错:No default constructor found
*/
public SomeServiceImpl() {
System.out.println("SomeServiceImpl的无参数构造方法");
}
@Override
public void doSome() {
System.out.println("执行了SomeServiceImpl的doSome()方法");
}
}
(11)创建Spring配置文件
如同在Servlet中我们需要在web.xml中注册我们希望服务器自动创建管理的servlet对象一样,在Spring中也需要有类似的配置,来自动创建刚才的SomeServiceImpl对象。
在 src/main/resources/目录现创建一个 xml 文件,文件名可以随意,但 Spring 建议的名称为 applicationContext.xml
。
IDEA已经为我们设计好了Spring配置文件的模板:右击resources–>new–>XML configuration file–>Spring Config
模板如下:
<?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>
注意
,Spring 配置文件中使用的约束文件为 xsd 文件。作用与Mybatis的sql映射文件的dtd约束文件类似,但xsd约束作用更强:
1)定义一个 xml 文档中都有什么元素
2)定义一个 xml 文档中都有什么属性
3)定义一个 xml 文档中元素可以有哪些子元素,以及元素的顺序
4)定义一个 xml 文档中元素和属性的数据类型
<beans/>
是配置文件的根标签。在Spring中,java对象称之为bean。在这个标签下进行java对象的注册:
<bean id="someService" class="com.kwxy.service.SomeServiceImpl" scope="prototype"/>
1.声明java对象交给Spring创建和管理,这个步骤称之为声明bean
。
等同于:
SomeService someService = new com.kwxy.service.SomeServiceImpl();
然后将创建的对象是放入到Spring的容器(Map<id,对象>):
private final Map<String, Object> factoryBeanObjectCache = new ConcurrentHashMap(16);
factoryBeanObjectCache.put(“service”,someService);
一个bean标签声明一个对象。
2.<bean/>
标签的属性:
class:类的全限定名称,不能是接口(Spring使用反射创建对象);
class可以是非自定义的对象,例如”java.util.Date“,依然可以被Spring创建对象
。
id:自定义的对象名称,要求是唯一值。 表示在Spring中的对象名称,通过这个名称可以从Spring中找到对象,获取对象。
scope:指定bean对象的作用域(对象的存在范围和可见性)。
scope的可取值为:
1)单例
:singleton
, 默认值,表示叫这个名称的对象在spring容器中只有一个
。
2)原型
:prototype
, 表示每次使用getBean()都创建一个新的对象
。
(12)新建测试类,在测试类中创建测试方法
public class MyTest {
@Test
public void test01(){
//定义Spring的配置文件, 配置文件是在类路径的根目录之下
String config = "applicationContext.xml";
//创建Spring的容器对象.根据Spring配置文件的位置,使用接口的不同实现类
//1.如果Spring的配置文件是在类路径(classpath),使用ClassPathXmlApplicationContext
//2.如果Spring的配置文件是放在项目的根之下(与src、target同级目录),使用FileSystemXmlApplicationContext
//创建Spring容器,会读取配置文件中的bean标签,并创建对应的对象。
//ClassPathXmlApplicationContext:表示从类路径中加载spring的配置文件
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
//从容器中获取对象 使用getBean("<bean>的id")
SomeService service = (SomeService) ac.getBean("someService");
//调用业务方法
service.doSome();
}
/**
* 获取spring容器中 java 对象的信息
*/
@Test
public void test02(){
String config="applicationContext.xml";
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
//使用spring提供的方法, 获取容器中定义的对象的数量
int nums = ac.getBeanDefinitionCount();
System.out.println("容器中定义的对象数量:"+nums);
//容器中每个定义的对象的名称
String names [] = ac.getBeanDefinitionNames();
for(String name:names){
System.out.println(name);
}
}
}
Bean的装配
默认装配方式
当我们创建ApplicationContext对象时,Spring会读取配置文件的<bean/>
并执行对应类的无参构造器。在这些类的无参构造器中加一条输出语句可以验证以上结论。
ApplicationContext 容器,会在容器对象初始化时,将其中的所有对象一次性全部装配好。
以后代码中若要使用到这些对象,只需从内存中直接获取即可。执行效率较高。但占用内存。
容器中Bean的作用域
当通过 Spring 容器创建一个 Bean 实例时,不仅可以完成 Bean 的实例化,还可以通过 scope 属性,为 Bean 指定特定的作用域。Spring 支持多种作用域。
(1)singleton:单例模式。即在整个 Spring 容器中,使用 singleton 定义的 Bean 将是单例的,叫这个名称的对象只有一个实例。默认为单例的。
(2)prototype:原型模式。即每次使用 getBean 方法获取的同一个的实例都是一个新的实例。
(3)request:对于每次 HTTP 请求,都将会产生一个不同的 Bean 实例。
(4)session:对于每个不同的 HTTP session,都将产生一个不同的 Bean 实例。
注意:
对于 scope 的值 request、session 只有在 Web 应用中使用 Spring 时,该作用域才有效。
对于 scope 为 singleton 的单例模式,该 Bean 是在容器被创建时即被装配好了;
对于 scope 为 prototype的原型模式,Bean 实例是在代码中使用该 Bean 实例时才进行装配的。
我们只是自动创建了对象,没有给对象的属性赋值。后面介绍两种给属性赋值的方法。
基于XML的DI
通过在xml配置文件对对象的属性进行赋值。
设值注入(掌握)
又称为set注入,通过调用类中属性的setter给属性赋值。
简单类型(java中的基本类型和String类型)
在<bean/>
标签下添加:<property name="属性名" value="简单类型的属性值" />
每一个property标签,完成一个属性的赋值。
注意:
Spring执行property标签的原理,是执行name属性值对应的set方法。而并不关心set方法的具体实现和属性是否真的存在。
引用类型
当指定 bean 的某属性值为另一 bean 的实例时,通过 ref 指定它们间的引用关系。ref 的 值必须为某 bean 的 id 值。
例如:创建一个学校类:
package com.kwxy.domain;
public class School {
private String name;
private String address;
public void setName(String name) {
this.name = name;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "School{" +
"name='" + name + '\'' +
", address='" + address + '\'' +
'}';
}
}
在学生类中添加School类型的属性:
package com.kwxy.domain;
public class Student {
private String name;
private int age;
//引用类型
private School school;
public Student() {
System.out.println("Student的无参数构造方法");
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public void setSchool(School school) {
System.out.println("setSchool:"+school);
this.school = school;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", school=" + school +
'}';
}
}
Spring配置文件中添加如下bean:
<!--声明School-->
<bean id="mySchool" class="com.kwxy.domain.School">
<property name="name" value="北京大学" />
<property name="address" value="北京的海淀区" />
</bean>
<!--ref作为属性-->
<bean id="myStudent" class="com.kwxy.domain.Student" >
<property name="name" value="李四" />
<property name="age" value="20" />
<property name="school" ref="mySchool" /> <!--setSchool(mySchool) -->
</bean>
<!--ref作为子标签-->
<bean id="myStudent2" class="com.kwxy.domain.Student">
<property name="name" value="张三" />
<property name="age" value="22" />
<property name="school">
<ref bean="mySchool"/>
</property>
</bean>
构造注入(理解)
执行类的有参构造,在构造对象的同时给属性赋值。
1.给Student类添加有参构造器:
//定义有参数构造方法
public Student(String name, int age, School school) {
System.out.println("Student有参数构造方法");
this.name = name;
this.age = age;
this.school = school;
}
2.对xml配置文件进行相关配置:
<!--使用name属性-->
<bean id="myStu" class="com.kwxy.domain.Student" >
<constructor-arg name="age" value="22"/>
<constructor-arg name="name" value="张三" />
<constructor-arg name="school" ref="mySch" />
</bean>
<!--声明School-->
<bean id="mySch" class="com.kwxy.domain.School">
<property name="name" value="江苏师范大学" />
<property name="address" value="科文学院" />
</bean>
<constructor-arg />
标签中用于指定参数的属性有:
➢ name:指定参数名称,指的是构造方法中的形参。
➢ index:指明该参数对应着构造器的第几个参数,从 0 开始。不过,该属性不要也行,但要注意,若参数类型相同,或之间有包含关系,则需要保证赋值顺序要与构造器中的参数顺序一致。
value:构造方法的形参类型是简单类型的,使用value
ref:构造方法的形参类型是引用类型的,使用ref
引用类型的自动注入
对于引用类型属性的注入,也可不在配置文件中显示的注入。可以通过为<bean/>
标签 设置 autowire 属性值,为引用类型属性进行隐式自动注入(默认是不自动注入引用类型属性)。
根据自动注入判断标准的不同,可以分为两种:
(1)byName:根据名称自动注入
(2)byType: 根据类型自动注入
byName 方式自动注入
当配置文件中被调用者 bean 的 id 值与代码中调用者 bean 类的属性名相同时,可使用 byName 方式,让容器自动将被调用者 bean 注入给调用者 bean。
容器是通过调用者的 bean 类的属性名与配置文件的被调用者 bean 的 id 进行比较而实现自动注入的。
例如,在Student类中,School类型的属性名是”school“,那么在xml配置文件中:
<bean id="mySchool" class="com.kwxy.domain.School" autowire="byName">
<property name="name" value="北京大学" />
<property name="address" value="北京的海淀区" />
</bean>
<bean id="myStudent" class="com.kwxy.domain.Student" >
<property name="name" value="李四" />
<property name="age" value="20" />
<!--<property name="school" ref="mySchool" />-->
</bean>
byType 方式自动注入
使用 byType 方式自动注入,要求:配置文件中被调用者 bean 的 class 属性指定的类, 要与代码中调用者 bean 类的某引用类型属性类型同源。
即要么相同,要么有 is-a 关系(子 类,或是实现类)。但这样的同源的被调用 bean 只能有一个。多于一个,容器就不知该匹配哪一个了。
<!--
byType(按类型注入) : java类中引用类型的数据类型和spring容器中(配置文件)<bean>的class属性
是同源关系的,这样的bean能够赋值给引用类型
同源就是一类的意思:
1.java类中引用类型的数据类型和bean的class的值是一样的。
2.java类中引用类型的数据类型和bean的class的值父子类关系的。
3.java类中引用类型的数据类型和bean的class的值接口和实现类关系的
语法:
<bean id="xx" class="yyy" autowire="byType">
简单类型属性赋值
</bean>
注意:在byType中, 在xml配置文件中声明bean只能有一个符合条件的,
多余一个是错误的
-->
<!--byType自动注入-->
<bean id="myStudent" class="com.kwxy.domain.Student" autowire="byType">
<property name="name" value="晶哥" />
<property name="age" value="22"/>
</bean>
<!--声明School-->
<!--<bean id="mySchool" class="com.kwxy.domain.School">
<property name="name" value="清华大学" />
<property name="address" value="北京的海淀区" />
</bean>-->
<!--声明School的子类对象-->
<bean id="xiaoXueSchool" class="com.kwxy.domain.XiaoXueSchool">
<property name="name" value="中兴小学" />
<property name="address" value="北京的大兴区"/>
</bean>
为应用指定多个 Spring 配置文件
在实际应用里,随着应用规模的增加,系统中 Bean 数量也大量增加,导致配置文件变 得非常庞大、臃肿。
为了避免这种情况的产生,提高配置文件的可读性与可维护性,可以将 Spring 配置文件分解成多个配置文件。
包含关系的配置文件: 多个配置文件中有一个总文件,总配置文件将各其它子文件通过<import/>
引入。
在 Java 代码中只需要使用总配置文件对容器进行初始化即可。
例如:有spring-school.xml、spring-student.xml配置文件和一个total.xml总配置文件。
在total.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">
<!--
包含关系的配置文件:
spring-total表示主配置文件 : 包含其他的配置文件的,主配置文件一般是不定义对象的。
语法:<import resource="其他配置文件的路径" />
关键字:"classpath:" 表示类路径(class文件所在的目录),
在spring的配置文件中要指定其他文件的位置, 需要使用classpath,告诉spring到哪去加载读取文件。
-->
<!--加载的是文件列表-->
<!--
<import resource="classpath:ba06/spring-school.xml" />
<import resource="classpath:ba06/spring-student.xml" />
-->
<!--
在包含关系的配置文件中,可以通配符(*:表示任意字符),使用通配符的话,配置文件必须放在一个目录中
即这个配置文件要放在resources下的一个目录中。这个目录自己去创建,比如下面的ba06。
注意: 主的配置文件名称不能包含在通配符的范围内(不能叫做spring-total.xml)
-->
<import resource="classpath:ba06/spring-*.xml" />
</beans>
注意: 主的配置文件名称不能包含在通配符的范围内。
基于注解的DI
通过以下四个步骤实现DI:
(1)创建Maven项目,在pom.xml 加入 AOP 依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.2.4.RELEASE</version>
</dependency>
如果你在pom.xml中添加了spring-context,那此依赖自动包含spring-aop,无需再次添加。
(2)需要更换配置文件头,加入 spring-context.xsd 约束
约 束 在 %SPRING_HOME%\docs\spring-framework-reference\html\xsd-configuration.html 文件中。
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="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
http://www.springframework.org/schema/context/spring-context.xsd">
</beans>
如果使用IDEA进行编辑,这一步骤可以省略,在执行第四步时根据提示自动添加即可。
(3)在类中添加注解
package com.kwxy.domain;
import org.springframework.stereotype.Component;
/**
* @Component: 创建对象的, 等同于<bean>的功能
* 属性:value 就是对象的名称,也就是bean的id值,
* value的值是唯一的,创建的对象在整个spring容器中就一个
* 位置:在类的上面
*
* @Component(value = "myStudent")等同于
* <bean id="myStudent" class="com.kwxy.domain.Student" />
*
* spring中和@Component功能一致,创建对象的注解还有:
* 1.@Repository(用在持久层类的上面) : 放在dao的实现类上面,
* 表示创建dao对象,dao对象是能访问数据库的。
* 2.@Service(用在业务层类的上面):放在service的实现类上面,
* 创建service对象,service对象是做业务处理,可以有事务等功能的。
* 3.@Controller(用在控制器的上面):放在控制器(处理器)类的上面,创建控制器对象的,
* 控制器对象,能够接受用户提交的参数,显示请求的处理结果。
* 以上三个注解的使用语法和@Component一样的。 都能创建对象,但是这三个注解还有额外的功能。
* @Repository,@Service,@Controller是给项目的对象分层的。
* 默认是单例对象
*/
@Component("myStudent")
public class Student {
private String name;
private int age;
public Student(String name, int age, School school) {
this.name = name;
this.age = age;
this.school = school;
}
//引用类型
private School school;
public Student() {
System.out.println("Student的无参数构造方法");
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public void setSchool(School school) {
System.out.println("setSchool:"+school);
this.school = school;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", school=" + school +
'}';
}
}
(4)声明组件扫描器。在Spring配置文件的<beans/>
标签下:
<!--声明组件扫描器(component-scan),组件就是java对象
base-package:指定注解在你的项目中的包名。
component-scan工作方式: spring会扫描遍历base-package指定的包,
把包中和子包中的所有类,找到类中的注解,按照注解的功能创建对象,或给属性赋值。
加入了component-scan标签,配置文件的变化:
1.加入一个新的约束文件spring-context.xsd
2.给这个新的约束文件起个命名空间的名称
-->
<context:component-scan base-package="com.kwxy.domain" />
<!--指定多个包的三种方式-->
<!--第一种方式:使用多次组件扫描器,指定不同的包-->
<context:component-scan base-package="com.kwxy.domain"/>
<context:component-scan base-package="com.kwxy.domain2"/>
<!--第二种方式:使用分隔符(;或,)分隔多个包名-->
<context:component-scan base-package="com.kwxy.domain;com.kwxy.domain2" />
<!--第三种方式:指定父包-->
<context:component-scan base-package="com.kwxy" />
定义 Bean 的注解@Component(掌握)
@Component: 创建类的对象,等同于<bean />
,默认是单例对象
属性: value 表示对象的名称(bean的id)
位置: 在类定义的上面,表示创建此类的对象。
例如:@Component(value = "myStudent")
等价于<bean id="myStudent" class="com.kwxy.domain.Student"/>
另外,Spring 还提供了 3 个创建对象的注解:
➢ @Repository 用于对 DAO 实现类进行注解
➢ @Service 用于对 Service 实现类进行注解
➢ @Controller 用于对 Controller 实现类进行注解 这三个注解与@Component 都可以创建对象
但这三个注解还有其他的含义,@Service 创建业务层对象,业务层对象可以加入事务功能,@Controller 注解创建的对象可以作为处理器接收用户的请求。
@Repository,@Service,@Controller 是对@Component 注解的细化,标注不同层的对象。即持久层对象,业务层对象,控制层对象。
@Component 不指定 value 属性,bean 的 id 是类名的首字母小写。
若想使用这些注解形式的DI,必须在spring配置文件中声明组件扫描器
:
<context:component-scan base-package="注解类所在包名" />
简单类型属性注入@Value(掌握)
@Component("myStudent")
public class Student {
/**
* @Value: 简单类型的属性赋值
* 属性: value 是String类型的,表示简单类型的属性值
* 位置: 1.在属性定义的上面,无需set方法,推荐使用。
* 2.在set方法的上面
*/
@Value("李晶")
private String name;
private int age;
public void setName(String name) {
this.name = name;
}
@Value("23")
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
byType 自动注入@Autowired(掌握)
需要在引用属性上使用注解@Autowired,该注解默认使用按类型自动装配Bean的方式。
使用该注解完成属性注入时,类中无需 setter。当然,若属性有 setter,则也可将其加 到 setter 上。
@Autowired:spring框架提供的注解,实现引用类型的赋值,默认使用byType自动注入;
当有多个类型匹配时,使用要注入的对象变量名称作为 bean 的 id,在 spring 容器查找,找到了也可以注入成功。找不到就报错。
即Autowired默认先按byType,如果发现找到多个bean,则,又按照byName方式比对,如果还有多个name相同,则报出异常。
当不确定注入哪个bean的时候,可以联合使用注解@Autowired 与@Qualifier指定注入某个bean的id。
例如School类:
@Component("mySchool")
public class School {
@Value("师范大学")
private String name;
@Value("徐州科文")
private String address;
public void setName(String name) {
this.name = name;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "School{" +
"name='" + name + '\'' +
", address='" + address + '\'' +
'}';
}
}
Student类:
@Component("myStudent")
public class Student {
/**
* @Value: 简单类型的属性赋值
* 属性: value 是String类型的,表示简单类型的属性值
* 位置: 1.在属性定义的上面,无需set方法,推荐使用。
* 2.在set方法的上面
*/
@Value("李晶")
private String name;
@Value("23")
private int age;
/**
* 引用类型
* @Autowired: spring框架提供的注解,实现引用类型的赋值。
* spring中通过注解给引用类型赋值,使用的是自动注入原理 ,支持byName, byType
* @Autowired:默认使用的是byType自动注入。
*
* 位置:1)在属性定义的上面,无需set方法, 推荐使用
* 2)在set方法的上面
*/
@Autowired
private School school;
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public void setSchool(School school) {
System.out.println("setSchool:"+school);
this.school = school;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", school=" + school +
'}';
}
}
为了防止空指针异常
的产生,推荐@AutoWired注解加在带参构造器上。具体原因参见:阿丙博客
byName 自动注入@Autowired 与@Qualifier(掌握)
需要在引用属性上联合使用注解@Autowired 与@Qualifier。@Qualifier 的 value 属性用 于指定要匹配的 Bean 的 id 值。类中无需 set 方法,也可加到 set 方法上。
例如:Student要自动注入School类,只需在school属性前添加注解:
@Autowired
@Qualifier("mySchool")//与School类的@component注解value值相同
private School school;
@Autowired 还有一个属性 required,默认值为 true,表示当匹配失败后,会终止程序运行。若将其值设置为 false,则匹配失败,将被忽略,未匹配的属性值为 null。
JDK 注解@Resource 自动注入(掌握)
Spring提供了对jdk中@Resource注解的支持。
@Resource注解既可以按名称匹配Bean, 也可以按类型匹配 Bean。
默认是按名称注入。使用该注解,要求 JDK 必须是 6 及以上版本。
@Resource 可在属性上,也可在 set 方法上。
本质就是用@Resource一个注解代替@Autowired和@Qualifier两个注解。
(1)byType 注入引用类型属性
@Resource 注解若不带任何参数,采用默认按名称
的方式注入,按名称不能注入bean, 则会按照类型进行 Bean 的匹配注入。
如果Student要自动注入School类,只需在school属性前添加注解:
@Resource
private School school;
(2)byName 注入引用类型属性
@Resource 注解指定其 name 属性,则 name 的值即为按照名称进行匹配的 Bean 的 id。
如果Student要自动注入School类,只需在school属性前添加注解:
@Resource(name = "mySchool")
private School school;
如果JDK是11版本的,学到@Resource注解的时候,加上以下依赖,否则会报错。因为高版本中移除了改模块。
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.2</version>
</dependency>
当一个接口有多个实现类时
当一个接口有多个实现时,直接使用@Autowired可能会报异常,原因Autowired默认按照byType注入,找到了多个符合条件的bean,但是不知道注入哪一个,就会报异常。
方式1:
联合使用注解@Autowired 与@Qualifier。@Qualifier 的 value 属性用 于指定要匹配的 Bean 的 id 值。
方式2:
@Resource 注解指定其 name 属性,则 name 的值即为按照名称进行匹配的 Bean 的 id。
注解与 XML 的对比
注解
优点是:
(1)方便
(2)直观
(3)高效(代码少,没有配置文件的书写那么复杂)
其弊端也显而易见:以硬编码的方式写入到 Java 代码中,修改是需要重新编译代码的
XML方式
优点是:
(1)配置和代码是分离的
(2)在 xml 中做修改,无需编译代码,只需重启服务器即可将新的配置加载
xml 的缺点是:编写麻烦,效率低,大型项目过于复杂。
xml与注解方式还有一个区别在于,xml方式可以通过bean标签创建一个类的多个对象,但注解方式并不能,因为一个类不能存在两个同名的注解。
AOP面向切面编程
AOP概述
AOP(Aspect Orient Programming),面向切面编程。面向切面编程是从动态角度考虑程序运行过程。
AOP 底层,就是采用动态代理模式实现的。
采用了两种代理:JDK 的动态代理,与 CGLIB 的动态代理
,AOP可以看作动态代理的规范化与标准化
。
面向切面编程
,就是将交叉业务逻辑封装成切面,利用 AOP 容器的功能将切面织入到 主业务逻辑中。
所谓交叉业务逻辑是指,通用的、与主业务逻辑无关的代码,如安全检查、 事务、日志、缓存等。
若不使用 AOP,则会出现代码纠缠,即交叉业务逻辑与主业务逻辑混合在一起。这样, 会使主业务逻辑变的混杂不清。
例如,转账,在真正转账业务逻辑前后,需要权限控制、日志记录、加载事务、结束事 务等交叉业务逻辑,而这些业务逻辑与主业务逻辑间并无直接关系。
但,它们的代码量所占比重能达到总代码量的一半甚至还多。它们的存在,不仅产生了大量的“冗余”代码,还大大干扰了主业务逻辑—转账。
AOP的相关术语
(1) 切面(Aspect)
切面泛指交叉业务逻辑。上例中的事务处理、日志处理就可以理解为切面。常用的切面 是通知(Advice)。实际就是对主业务逻辑的一种增强。
(2) 连接点(JoinPoint)
连接点指可以被切面织入的具体方法。通常业务接口中的方法均为连接点。
(3) 切入点(Pointcut)
切入点指声明的一个或多个连接点的集合。通过切入点指定一组方法。 被标记为 final 的方法是不能作为连接点与切入点的。因为最终的是不能被修改的,不能被增强的。
(4) 目标对象(Target)
目标对象指将要被增强的对象。即包含主业务逻辑的类的对象。上例中的 StudentServiceImpl 的对象若被增强,则该类称为目标类,该类对象称为目标对象。当然,不被增强,也就无所谓目标不目标了。
(5) 通知(Advice)
通知是切面的一种实现,可以完成简单织入功能(织入功能就是在这里完成的)。Advice 也叫增强。上例中的 MyInvocationHandler 就可以理解为是一种通知。换个角度来说,通知定义了增强代码切入到目标代码的时间点,是目标方法执行之前执行,还是之后执行等。通知类型不同,切入时间不同。
切入点定义切入的位置,通知定义切入的时间。
AOP的实现
AOP的技术实现框架:
Spring:Spring的AOP实现较为笨重,一般用在事务处理。
aspectJ:一个开源,专门做AOP的框架,隶属于Eclipse基金会。
Spring框架集成了aspectJ的功能。
aspectJ框架实现AOP有两种方式:
1)使用xml配置文件,一般用于配置全局事务;
2)使用注解。一般在项目开发中使用这种方式。
AspectJ 对 AOP 的实现
AspectJ 的通知类型
切面的执行时间, 这个执行时间在规范中叫做Advice(通知,增强)
AspectJ 中常用的通知有五种类型,体现在五个不同的添加在切面的注解:
(1)前置通知 @Before
(2)后置通知 @AfterReturning
(3)环绕通知 @Around
(4)异常通知 @AfterThrowing
(5)最终通知 @After
AspectJ 的切入点表达式
表示切面执行的位置,使用的是切入点表达式
AspectJ 定义了专门的表达式用于指定切入点。表达式的原型是:
execution (
[modifiers-pattern] 访问权限类型
ret-type-pattern 返回值类型
[declaring-type-pattern] 全限定性类名
name-pattern(param-pattern) 方法名(参数类型和参数个数)
[throws-pattern] 抛出异常类型
)
切入点表达式要匹配的对象就是目标方法的方法名。所以,execution 表达式中明显就是方法的签名
。注意,表达式中加[ ]的部分表示可省略部分,各部分间用空格分开。
在其中可以使用以下符号:
*
:0至多个任意字符
..
:用在方法参数中,表示任意多个参数;用在包名后,表示当前包及其子包
+
:用在类名后,表示当前类及其子类;用在接口名后,表示当前接口及其实现类
例如:
execution(* *..service.*.*(..))
上面表达的意思是返回值任意, 指定所有包下的 serivce 子包下所有类(接口)中所有方法为切入点
技巧:
execution(访问权限 方法返回值 方法声明(参数) 异常类型) 方法返回值和方法声明是必需的。
其实类似于声明一个方法: public void 方法名(参数) throws 异常
AspectJ 的开发环境
(1)引入AspectJ依赖:
<!--aspectj的依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.4.RELEASE</version>
</dependency>
(2)引入约束(第4、7、8行)
<?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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
</beans>
在IDEA中开发,可以省略这一步。在添加aop标签时会自动引入约束文件。
AspectJ 基于注解的 AOP 实现
AspectJ 基于注解的 AOP 实现步骤
(1)定义业务接口与实现类
package com.kwxy.service;
public interface SomeService {
void doSome(String name, int age);
}
package com.kwxy.service.impl;
import com.kwxy.service.SomeService;
public class SomeServiceImpl implements SomeService {
@Override
public void doSome(String name, int age) {
System.out.println("SomeSeviceImpl的业务方法doSome");
}
}
(2)定义切面类
package com.kwxy.aspect;
import org.aspectj.lang.annotation.Before;
import java.util.Date;
@Aspect
public class MyAspect {
/**
* @Before: 前置通知注解
* 属性:value ,是切入点表达式,表示切面的功能执行的位置。
* 位置:在方法的上面
* 特点:
* 1.在目标方法之前先执行的
* 2.不会改变目标方法的执行结果
* 3.不会影响目标方法的执行。
*/
@Before(value = "execution(public void com.kwxy.service.impl.SomeServiceImpl.doSome(..))")
public void myBefore(){
//就是你切面要执行的功能代码
System.out.println("前置通知, 切面功能:在目标方法之前输出执行时间:"+ new Date());
}
}
(3)声明目标对象与切面类对象,注册AspectJ的自动代理
<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
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 http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--声明目标对象-->
<bean id="someService" class="com.kwxy.service.impl.SomeServiceImpl"/>
<!--声明切面类对象-->
<bean id="myAspect" class="com.kwxy.aspect.MyAspect"/>
<!--声明自动代理生成器:使用aspectj框架内部的功能,创建目标对象的代理对象。
创建代理对象是在内存中实现的, 修改目标对象的内存中的结构。 创建为代理对象
所以目标对象就是被修改后的代理对象.
aspectj-autoproxy:会把spring容器中的所有的目标对象,一次性都生成代理对象。
-->
<aop:aspectj-autoproxy/>
</beans>
<aop:aspectj-autoproxy/>
通过扫描找到@Aspect 定义的切面类,再由切面类根据切入点找到目标类的目标方法,再由通知类型找到切入的时间点。
(4)创建测试方法测试,获取代理对象根据目标对象的id。
@Test
public void test01(){
//定义Spring的配置文件, 配置文件是在类路径的根目录之下
String config = "applicationContext.xml";
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
//从容器中获取对象 使用getBean("<bean>的id")
SomeService proxy = (SomeService) ac.getBean("someService");
System.out.println(proxy.getClass().getName());
//调用业务方法
proxy.doSome("李晶",23);
}
/*输出结果
com.sun.proxy.$Proxy8
前置通知, 切面功能:在目标方法之前输出执行时间:Wed Jul 22 19:33:30 CST 2020
SomeSeviceImpl的业务方法doSome
*/
AspectJ 通知注解
通知注解:
在切面类中修饰方法的注解,这些注解体现了通知类型。例如上面例子中@Before就是一个通知注解。通知注解修饰的方法称之为通知方法。
一共有五种通知类型,就对应了五种通知注解。下面一一介绍。
@Before前置通知-方法有JoinPoint参数
在目标方法执行之前执行。被注解为前置通知的方法,可以包含一个 JoinPoint 类型参 数。该类型的对象本身就是切入点表达式。通过该参数,可获取切入点表达式、方法签名、 目标对象等。
@Before(value = "execution(public void com.kwxy.service.impl.SomeServiceImpl.doSome(..))")
public void myBefore(JoinPoint jp) {
//在方法中,实现功能的增强,例如日志的代码
//获取方法的定义
System.out.println("连接点的方法定义:" + jp.getSignature());
System.out.println("连接点的方法名称:" + jp.getSignature().getName());
//获取方法执行时的参数
Object args[] = jp.getArgs();
for (Object arg : args) {
System.out.println(arg);
}
}
/*
测试方法输出结果:
com.sun.proxy.$Proxy8
连接点的方法定义:void com.kwxy.service.SomeService.doSome(String,int)
连接点的方法名称:doSome
李晶
23
*/
不光前置通知的方法,可以包含一个 JoinPoint 类型参数,所有的通知方法均可包含该参数。
@AfterReturning 后置通知-注解有 returning 属性
在目标方法执行之后执行。由于是目标方法之后执行,所以可以获取到目标方法的返回值。该注解的 returning 属性就是用于指定接收方法返回值的变量名的。
所以,被注解为后 置通知的方法,除了可以包含 JoinPoint 参数外,还可以包含用于接收返回值的变量。该变 量最好为 Object 类型,因为目标方法的返回值可能是任何类型。
例如:在业务接口中定义一个有返回值的抽象方法:
String doOther(String name,int age);
再在业务类中实现:
@Override
public String doOther(String name, int age) {
System.out.println("SomeSeviceImpl的业务方法doOther");
return "abcd";
}
然后在切面类中定义一个切面:
@AfterReturning(value = "execution(public String com.kwxy.service.impl.SomeServiceImpl.doOther(..))", returning = "result")
public void myAfterReturning(JoinPoint jp, Object result) {
//修改目标方法的返回值
if (result != null) {
String st = (String) result;
result = st.toUpperCase();
}
System.out.println("后置通知,在目标方法之后执行的。能够获取到目标方法的执行结果:" + result);
}
测试方法:
@Test
public void test03(){
//定义Spring的配置文件, 配置文件是在类路径的根目录之下
String config = "applicationContext.xml";
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
//从容器中获取对象 使用getBean("<bean>的id")
SomeService proxy = (SomeService) ac.getBean("someService");
System.out.println(proxy.getClass().getName());
//调用业务方法
String res = proxy.doOther("李晶", 23);
System.out.println("测试方法中的结果:" + res);
}
/*
测试方法输出结果:
com.sun.proxy.$Proxy8
SomeSeviceImpl的业务方法doOther
后置通知,在目标方法之后执行的。能够获取到目标方法的执行结果:ABCD
测试方法中的结果:abcd
*/
@Around 环绕通知-增强方法有 ProceedingJoinPoint 参数
在目标方法执行之前之后执行。
被注解为环绕增强的方法要有返回,Object 类型。并 且方法可以包含一个 ProceedingJoinPoint 类型的参数。
接口 ProceedingJoinPoint 继承于JoinPoint,因此可以根据它获取方法的信息。
其有一个 proceed()方法,用于执行目标方法。若目标方法有返回值,则该方法的返回值就是目标方法的返回值。
最后,环绕增强方法将其返回值返回。该增强方法实际是拦截了目标方法的执行。
接口增加方法:
String doFirst(String name,int age);
在实现类中实现该方法:
@Override
public String doFirst(String name, int age) {
System.out.println("SomeSeviceImpl的业务方法doFirst");
return "doFirst";
}
增加切面:
@Around(value = "execution(* *..SomeServiceImpl.doFirst(..))")
public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
//ProceedingJoinPoint能获取连接点方法的定义,参数等信息
String name = "";
Object args[] = pjp.getArgs();
if (args.length > 1) {
name = (String) args[0];
}
Object result = null;
System.out.println("环绕通知:在目标方法之前加入日志");
//控制目标方法是否执行
if ("zs".equals(name)) {
//执行目标方法
result = pjp.proceed(); //doFirst result = method.invoke(target, args);
}
System.out.println("环绕通知:在目标方法之后加入事务处理");
//返回目标方法的执行结果(可以是修改后的结果)
//修改目标方法的执行结果
if (result != null) {
result = "Hello AspectJ";
}
return result;
}
环绕通知能够控制目标方法是否执行,创建测试方法:
在上述代码中,如果name是zs,则执行,否则不执行。
@Test
public void test03(){
//定义Spring的配置文件, 配置文件是在类路径的根目录之下
String config = "applicationContext.xml";
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
//从容器中获取对象 使用getBean("<bean>的id")
SomeService proxy = (SomeService) ac.getBean("someService");
System.out.println(proxy.getClass().getName());
//调用业务方法
String res = proxy.doFirst("李晶", 23);
System.out.println("测试方法中的结果:" + res);
}
/*
测试结果:
com.sun.proxy.$Proxy8
环绕通知:在目标方法之前加入日志
环绕通知:在目标方法之后加入事务处理
测试方法中的结果:null
*/
将name改为zs后,运行结果为:
/*
com.sun.proxy.$Proxy8
环绕通知:在目标方法之前加入日志
SomeSeviceImpl的业务方法doFirst
环绕通知:在目标方法之后加入事务处理
测试方法中的结果:Hello AspectJ
*/
@AfterThrowing 异常通知-注解中有 throwing 属性
在目标方法抛出异常后执行。该注解的 throwing 属性用于指定所发生的异常类对象。
当然,被注解为异常通知的方法可以包含一个参数 Throwable,参数名称为 throwing 指定的名称,表示发生的异常对象。
在效果上相当于一个try…catch语句。目标方法的方法体在try语句块中,而切面方法的方法体放在了catch子句中。
@After最终通知
无论目标方法是否抛出异常,该增强均会被执行。
在执行效果上,相当于将切面方法的方法体放在了try…catch…finally…语句的finally子句中。
@Pointcut 定义切入点
当较多的通知增强方法使用相同的 execution 切入点表达式时,编写、维护均较为麻烦。 AspectJ 提供了@Pointcut 注解,用于定义 execution 切入点表达式。
其用法是,将@Pointcut 注解在一个方法之上,以后所有的 execution 的 value 属性值均 可使用该方法名作为切入点。
代表的就是@Pointcut 定义的切入点。这个使用@Pointcut 注解的方法一般使用 private 的标识方法,即没有实际作用的方法。 方法体内部也无需添加代码。
例如:
@After(value = "mypt()")
public void myAfter(){
System.out.println("最终通知,总是会被执行的,可以做程序最后要做的工作,例如资源回收,内存释放");
}
/**
* @Pointcut: 定义和管理切入点
* 属性:value 切入点表达式
* 位置:在自定义的方法上面。
* 作用:@Pointcut定义在方法的上面, 这个方法的名称就是切入点的别名
* 其他的通知注解的value属性可以使用方法名称,表示切入点。
*/
@Pointcut(value = "execution(* *..SomeServiceImpl.doThird(..))")
private void mypt(){
//无需代码
}
设置AspectJ 实现 AOP的方式
在Spring配置文件中,通过<aop:aspectj-autoproxy/>
的proxy-target-class属性设置选择通过JDK动态代理还是cglib动态代理实现AOP。
<!--声明自动代理生成器:使用aspectj把spring容器中目标类对象生成代理
proxy-target-class="true"表示使用cglib动态代理
目标类有接口,默认使用jdk动态代理。
目标类没有接口,默认时候cglib动态代理
目标类有接口,也可以使用cglib动态代理,需要设置proxy-target-class="true"
-->
<!-- <aop:aspectj-autoproxy proxy-target-class="true" />-->
<aop:aspectj-autoproxy/>
Spring集成myBatis
概述
Spring集成myBatis,其本质工作就是:将使用mybatis框架时用到的一些需要自己创建的对象,交由Spring统一管理。
把mybatis框架和spring集成在一起,像一个框架一样使用。
用的技术是:ioc 。
为什么是ioc:能把mybatis和spring集成在一起,像一个框架, 是因为ioc能创建对象。
可以把mybatis框架中的对象交给spring统一创建, 开发人员从spring中获取对象。
开发人员就不用同时面对两个或多个框架了, 就面对一个spring。
说明:
/*
mybatis使用步骤,对象
1.定义dao接口 ,StudentDao
2.定义mapper文件 StudentDao.xml
3.定义mybatis的主配置文件 mybatis-config.xml
4.创建dao的代理对象, StudentDao dao = SqlSession.getMapper(StudentDao.class);
List<Student> students = dao.selectStudents();
要使用dao对象,需要使用getMapper()方法。
怎么能使用getMapper()方法,需要哪些条件?
1.获取SqlSession对象, 需要使用SqlSessionFactory的openSession()方法。
2.创建SqlSessionFactory对象。 通过读取mybatis的主配置文件,能创建SqlSessionFactory对象
需要SqlSessionFactory对象, 使用Factory能获取SqlSession ,有了SqlSession就能有dao , 目的就是获取dao对象
Factory创建需要读取主配置文件
我们会使用独立的连接池类替换mybatis默认自己带的, 把连接池类也交给spring创建。
主配置文件:
1.数据库信息
<environment id="mydev">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!--数据库的驱动类名-->
<property name="driver" value="com.mysql.jdbc.Driver"/>
<!--连接数据库的url字符串-->
<property name="url" value="jdbc:mysql://localhost:3306/ssm"/>
<!--访问数据库的用户名-->
<property name="username" value="root"/>
<!--密码-->
<property name="password" value="root"/>
</dataSource>
2. mapper文件的位置
<mappers>
<mapper resource="com/kwxy/dao/StudentDao.xml"/>
</mappers>
*/
通过以上的说明,我们需要让spring创建以下对象:
(1)数据源dataSource。就是保存数据库连接信息的对象。在实际业务开发中,我们放弃使用Mybatis自带的数据库连接池,而采用阿里的Druid,更加高效;
(2)生成sqlSession对象的sqlSessionFactory;
(3)Dao接口的实现类对象。
需要学习就是上面三个对象的创建语法,使用xml的bean标签。
Spring集成myBatis创建项目的流程
(1)新建 mysql数据库,准备数据。( student表)
建表语句
DROP TABLE IF EXISTS `student`;
CREATE TABLE `student` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(80) DEFAULT NULL,
`email` varchar(100) DEFAULT NULL,
`age` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1003 DEFAULT CHARSET=utf8;
添加数据
INSERT INTO `student` VALUES ('1001', '李晶', 'lijing@163.com', '23');
INSERT INTO `student` VALUES ('1002', '李慧', 'lihui@qq.com', '25');
mysql> select *from student;
+------+--------+----------------+------+
| id | name | email | age |
+------+--------+----------------+------+
| 1001 | 李晶 | lijing@163.com | 23 |
| 1002 | 李慧 | lihui@qq.com | 25 |
+------+--------+----------------+------+
(2)新建maven的module
(3)加入依赖
1)spring依赖
2)mybatis的依赖
3)mybatis-spring依赖, 这个jar是从mybatis官网下载的, mybatis提供在spring中创建对象的类。
4)mysql的驱动
5)druid,数据库连接池的依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.kwyx</groupId>
<artifactId>spring_mybatis</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<!--单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!--Spring的依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.4.RELEASE</version>
</dependency>
<!--spring的事务-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.2.4.RELEASE</version>
</dependency>
<!--spring访问数据库-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.4.RELEASE</version>
</dependency>
<!--aspectj的依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.4.RELEASE</version>
</dependency>
<!--mybatis依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.1</version>
</dependency>
<!--mybatis整合spring的依赖:创建mybatis对象-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.1</version>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.9</version>
</dependency>
<!--数据库连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.12</version>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/java</directory><!--所在的目录-->
<includes><!--包括目录下的.properties,.xml 文件都会扫描到-->
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
</project>
(4)新建实体类Student
package com.kwyx.domain;
public class Student {
private Integer id;
private String name;
private String email;
private Integer age;
public Student() {
}
public Student(Integer id, String name, String email, Integer age) {
this.id = id;
this.name = name;
this.email = email;
this.age = age;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", email='" + email + '\'' +
", age=" + age +
'}';
}
}
记得在pom.xml中添加资源插件!
(5)新建Dao接口和sql映射文件
package com.kwyx.dao;
import com.kwyx.domain.Student;
import java.util.List;
public interface StudentDao {
List<Student> selectAll();
int insertStudent(Student student);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.kwxy.dao.StudentDao">
<select id="selectAll" resultType="com.kwxy.domain.Student">
select id,name,email,age from student
</select>
<insert id="insertStudent">
insert into student values (#{id},#{name},#{email},#{age})
</insert>
</mapper>
(6)新建mybatis主配置文件mybatis-config.xml
由于使用阿里的数据库连接池,所以不需要<environments/>
标签
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--settings:控制mybatis全局行为-->
<settings>
<!--设置mybatis输出日志-->
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<!--设置别名-->
<typeAliases>
<!--name:实体类所在的包名-->
<package name="com.kwxy.domain"/>
</typeAliases>
<!-- sql mapper(sql映射文件)的位置-->
<mappers>
<!--
name:是包名, 这个包中的所有mapper.xml一次都能加载
-->
<package name="com.kwxy.dao"/>
</mappers>
</configuration>
(7)新建Service接口和实现类, 在实现类中有Dao的属性
在实际项目中,我们在对数据库前需要一些其他的业务代码,例如逻辑判断、身份认证等,这些放在Service中
package com.kwyx.service;
import com.kwyx.domain.Student;
import java.util.List;
public interface StudentService {
List<Student> selectAll();
int insertStudent(Student student);
}
package com.kwyx.service.impl;
import com.kwyx.service.StudentService;
import com.kwyx.dao.StudentDao;
import com.kwyx.domain.Student;
import java.util.List;
public class StudentServiceImpl implements StudentService {
private StudentDao studentDao;
//使用ioc,设值注入,在配置文件中给dao赋值
public void setStudentDao(StudentDao studentDao) {
this.studentDao = studentDao;
}
@Override
public List<Student> selectAll() {
return studentDao.selectAll();
}
@Override
public int insertStudent(Student student) {
return studentDao.insertStudent(student);
}
}
(8)创建数据库连接配置文件jdbc.properties
jdbc.url=jdbc:mysql://localhost:3306/ssm
jdbc.user=root
jdbc.password=root
jdbc.maxActive=20
不需要配置数据库驱动
(9)新建Spring的配置文件applicationContext.xml(重要)
1)声明Druid的数据源DruidDataSource对象
2)声明SqlSessionFactoryBean,创建SqlSessionFactory对象
3)声明MyBatis的扫描器MapperScannerConfigurer,创建Dao接口的实现类对象
4)声明自定义的Service ,把3)中的Dao对象注入赋值给Service的属性
<?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:context="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">
<!--读取jdbc配置文件
location:指定属性配置文件的路径
"classpath:":关键字表示类文件,也就是class文件所在的目录
-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--声明数据源DataSource-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<!--读取属性配置文件的key的值,使用 ${key}-->
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.user}"/>
<property name="password" value="${jdbc.password}"/>
<property name="maxActive" value="${jdbc.maxActive}"/>
</bean>
<!--
DruidDataSource myDataSource = new DruidDataSource();
myDataSource.setUrl();
myDataSource.setUsername();
myDataSource.setPassword();
myDataSource.init();
-->
<!--声明SqlSessionFactoryBean,创建SqlSessionFactory对象-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--数据源-->
<property name="dataSource" ref="dataSource"/>
<!--指定mybatis的主配置文件-->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
</bean>
<!--声明MyBatis的扫描器,创建Dao接口的实现类对象-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--指定SqlSessionFactory对象,能获取SqlSession-->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<!--指定Dao接口的包名,框架会把这个包中的所有接口一次创建出Dao对象-->
<property name="basePackage" value="com.kwxy.dao"/>
</bean>
<!--
从spring中获取SqlSessionFacotory,因为spring是一个容器(Map)
SqlSessionFactory factory = map.get("sqlSessionFactory");
SqlSession session = factory.openSession();
for(接口:com.kwxy.dao)
{
Dao对象 = session.getMapper(接口)
//把创建好的对象放入到spring容器中
spring的Map.put( 接口名的首字母小写, Dao对象 )
}
创建好的dao对象放入到spring的容器中的。 dao对象的默认名称是 接口名首字母小写
-->
<!--声明Service-->
<bean id="studentService" class="com.kwxy.service.impl.StudentServiceImpl">
<property name="studentDao" ref="studentDao"/>
</bean>
</beans>
(10)新建测试类, 从spring容器中获取Service,调用Service的方法,完成数据库的操作
public class MyTest {
@Test
public void test01() {
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
StudentService studentService = (StudentService) ac.getBean("studentService");
List<Student> students = studentService.selectAll();
for (Student student : students) {
System.out.println("学生 :" + student);
}
}
/**结果
* JDBC Connection [com.mysql.jdbc.JDBC4Connection@662b4c69] will not be managed by Spring
* ==> Preparing: select id,name,email,age from student
* ==> Parameters:
* <== Columns: id, name, email, age
* <== Row: 1001, 李晶, lijing@163.com, 23
* <== Row: 1002, 李慧, lihui@qq.com, 25
* <== Total: 2
* Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@53aad5d5]
* 学生 :Student{id=1001, name='李晶', email='lijing@163.com', age=23}
* 学生 :Student{id=1002, name='李慧', email='lihui@qq.com', age=25}
*/
@Test
public void test02() {
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
StudentService studentService = (StudentService) ac.getBean("studentService");
Student student = new Student(3, "张飞", "zhangfei@163.com", 24);
int nums = studentService.insertStudent(student);
System.out.println("添加了 " + nums + " 位学生");
}
/**结果
* JDBC Connection [com.mysql.jdbc.JDBC4Connection@662b4c69] will not be managed by Spring
* ==> Preparing: insert into student values (?,?,?,?)
* ==> Parameters: 3(Integer), 张飞(String), zhangfei@163.com(String), 24(Integer)
* <== Updates: 1
* Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@76f2bbc1]
* 添加了 1 位学生
*/
}
Spring与事务
理论知识
(1)什么是事务
讲mysql的时候,提出了事务。 事务是指一组sql语句的集合, 集合中有多条sql语句,
可能是insert , update ,select ,delete, 我们希望这些多个sql语句都能成功,
或者都失败, 这些sql语句的执行是一致的,作为一个整体执行。
(2)在什么时候想到使用事务
当我的操作,涉及得到多个表,或者是多个sql语句的insert,update,delete。
需要保证这些语句都是成功才能完成我的功能,或者都失败,保证操作是符合要求的。
在java代码中写程序,控制事务,此时事务应该放在哪里呢?
service类的业务方法上,因为业务方法会调用多个dao方法,执行多个sql语句。
(3)通常使用JDBC访问数据库, 还是mybatis访问数据库怎么处理事务
jdbc访问数据库,处理事务 Connection conn ; conn.commit(); conn.rollback();
mybatis访问数据库,处理事务, SqlSession.commit(); SqlSession.rollback();
hibernate访问数据库,处理事务, Session.commit(); Session.rollback();
(4)上面事务的处理方式,有什么不足
不同的数据库访问技术,处理事务的对象,方法不同,需要了解不同数据库访问技术使用事务的原理
要掌握多种数据库中事务的处理逻辑,什么时候提交事务,什么时候回顾事务
处理事务需要多种方法
(5)怎么解决不足
spring提供一种处理事务的统一模型, 能使用统一步骤,方式完成多种不同数据库访问技术的事务处理。
使用spring的事务处理机制,可以完成mybatis访问数据库的事务处理。
使用spring的事务处理机制,可以完成hibernate访问数据库的事务处理。
spring处理事务的模型,使用的步骤都是固定的。把事务使用的信息提供给spring就可以了。
事务管理器接口
事务管理器是 PlatformTransactionManager 接口对象。其主要用于完成事务的提交、回滚,及获取事务的状态信息。
PlatformTransactionManager 接口有两个常用的实现类:
➢ DataSourceTransactionManager:使用 JDBC 或 MyBatis 进行数据库操作时使用。
➢ HibernateTransactionManager:使用 Hibernate 进行持久化数据时使用。
Spring 的回滚方式
Spring 事务的默认回滚方式是:发生运行时异常和 error 时回滚,发生受查(编译)异常时提交。不过,对于受查异常,程序员也可以手工设置其回滚方式。
运行时异常
,是 RuntimeException 类或其子类,即只有在运行时才出现的异常。
如, NullPointerException、ArrayIndexOutOfBoundsException、IllegalArgumentException 等均属于运 行时异常。
这些异常由 JVM 抛出,在编译时不要求必须处理(捕获或抛出)。
但,只要代码 编写足够仔细,程序足够健壮,运行时异常是可以避免的。
受查异常
,也叫编译时异常,即在代码编写时要求必须捕获或抛出的异常,若不处理, 则无法通过编译。
如 SQLException,ClassNotFoundException,IOException 等都属于受查异常。 RuntimeException 及其子类以外的异常,均属于受查异常。
事务定义接口
事务定义接口TransactionDefinition中定义了事务描述相关的三类常量:事务隔离级别、 事务传播行为、事务默认超时时限,及对它们的操作。
(1)定义了五个事务隔离级别常量
这些常量均是以 ISOLATION_开头。即形如 ISOLATION_XXX。
➢ DEFAULT:采用 DB 默认的事务隔离级别。MySql 的默认为 REPEATABLE_READ; Oracle 默 认为 READ_COMMITTED。
➢ READ_UNCOMMITTED:读未提交。未解决任何并发问题。
➢ READ_COMMITTED:读已提交。解决脏读,存在不可重复读与幻读。
➢ REPEATABLE_READ:可重复读。解决脏读、不可重复读,存在幻读 。
➢ SERIALIZABLE:串行化。不存在并发问题。
(2)定义了七个事务传播行为常量
所谓事务传播行为是指,处于不同事务中的方法在相互调用时,执行期间事务的维护情 况。如,A 事务中的方法 doSome()调用 B 事务中的方法 doOther(),在调用执行期间事务的 维护情况,就称为事务传播行为。事务传播行为是加在方法上的。 事务传播行为常量都是以 PROPAGATION_ 开头,形如 PROPAGATION_XXX。
PROPAGATION_REQUIRED
PROPAGATION_REQUIRES_NEW
PROPAGATION_SUPPORTS
PROPAGATION_MANDATORY
PROPAGATION_NESTED
PROPAGATION_NEVER
PROPAGATION_NOT_SUPPORTED
1)PROPAGATION_REQUIRED:
指定的方法必须在事务内执行。若当前存在事务,就加入到当前事务中;若当前没有事 务,则创建一个新事务。这种传播行为是最常见的选择,也是 Spring 默认的事务传播行为。 如该传播行为加在 doOther()方法上。若 doSome()方法在调用 doOther()方法时就是在事 务内运行的,则 doOther()方法的执行也加入到该事务内执行。若 doSome()方法在调用 doOther()方法时没有在事务内执行,则 doOther()方法会创建一个事务,并在其中执行。
2)PROPAGATION_SUPPORTS
指定的方法支持当前事务,但若当前没有事务,也可以以非事务方式执行。
3)PROPAGATION_REQUIRES_NEW
总是新建一个事务,若当前存在事务,就将当前事务挂起,直到新事务执行完毕。
使用 Spring 的事务注解管理事务
(1)开启注解驱动,告诉Spring框架现在使用注解处理事务,在spring配置文件applicationContext中添加如下代码:
<!--开启注解驱动,开启事务支持,以tx开头,可以看出是用于事务的-->
<tx:annotation-driven transaction-manager="transactionManager"/>
(2)声明事务管理器
<!--声明事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
可视为固定写法,其中property标签的ref是配置文件中数据源对象的id属性值。
(3)业务层 public 方法加入事务注解
/**
*
* rollbackFor:表示发生指定的异常一定回滚.
* 处理逻辑是:
* 1) spring框架会首先检查方法抛出的异常是不是在rollbackFor的属性值中
* 如果异常在rollbackFor列表中,不管是什么类型的异常,一定回滚。
* 2) 如果你的抛出的异常不在rollbackFor列表中,spring会判断异常是不是RuntimeException,
* 如果是一定回滚。
*
*/
/* @Transactional(
propagation = Propagation.REQUIRED,
isolation = Isolation.DEFAULT,
readOnly = false,
rollbackFor = {
NullPointerException.class
}
)*/
//使用的是事务控制的默认值, 默认的传播行为是REQUIRED,默认的隔离级别DEFAULT
//默认抛出运行时异常,回滚事务。
@Transactional
@Override
public int insertStudent(Student student) {
return studentDao.insertStudent(student);
}
@Transactional 的所有可选属性
如下所示:
➢ propagation:用于设置事务传播属性。该属性类型为 Propagation 枚举,默认值为Propagation.REQUIRED。
➢ isolation:用于设置事务的隔离级别。该属性类型为 Isolation 枚举,默认值为 Isolation.DEFAULT。
➢ readOnly:用于设置该方法对数据库的操作是否是只读的。该属性为 boolean,默认值 为 false。
➢ timeout:用于设置本操作与数据库连接的超时时限。单位为秒,类型为 int,默认值为 -1,即没有时限。在实际业务开发中一般不设置
。
➢ rollbackFor:指定需要回滚的异常类。类型为 Class[],默认值为空数组。当然,若只有 一个异常类时,可以不使用数组。
➢ rollbackForClassName:指定需要回滚的异常类类名。类型为 String[],默认值为空数组。 当然,若只有一个异常类时,可以不使用数组。
➢ noRollbackFor:指定不需要回滚的异常类。类型为 Class[],默认值为空数组。当然,若 只有一个异常类时,可以不使用数组。
➢ noRollbackForClassName:指定不需要回滚的异常类类名。类型为 String[],默认值为空 数组。当然,若只有一个异常类时,可以不使用数组。
需要注意的是,@Transactional 若用在方法上,只能用于 public 方法上。
对于其他非 public 方法,如果加上了注解@Transactional,虽然 Spring 不会报错,
但不会将指定事务织入到该方法中。因为 Spring 会忽略掉所有非 public 方法上的@Transaction注解。
若@Transaction 注解在类上,则表示该类上所有的方法均将在执行时织入事务。
使用 AspectJ 的 AOP 配置管理事务
一般大型项目使用。在不更改源代码的条件下管理事务。代码和事务的配置完全是分离的,不需要在代码上加注解,全部是在xml中配置。
(1)添加Maven依赖
<!--aspectj的依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.4.RELEASE</version>
</dependency>
(2)声明事务管理器
<!--声明事务管理器对象-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
(3)配置事务通知
为事务通知设置相关属性。用于指定要将事务以什么方式织入给哪些方法。
<!--声明业务方法它的事务属性(隔离级别,传播行为,超时时间)
id:自定义名称,表示 <tx:advice> 和 </tx:advice>之间的配置内容的
transaction-manager:事务管理器对象的id
-->
<tx:advice id="buyAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!--tx:method:给具体的方法配置事务属性,method可以有多个,分别给不同的方法设置事务属性
name:方法名称,1)完整的方法名称,不带有包和类。
2)方法可以使用通配符,* 表示任意字符
propagation:传播行为,枚举值
isolation:隔离级别
rollback-for:你指定的异常类名,全限定类名。 发生异常一定回滚
-->
<tx:method name="buy" propagation="REQUIRED" isolation="DEFAULT"
rollback-for="java.lang.NullPointerException"/>
<!--使用通配符,指定很多的方法-->
<tx:method name="add*" propagation="REQUIRES_NEW" />
<!--指定修改方法-->
<tx:method name="modify*" />
<!--删除方法-->
<tx:method name="remove*" />
<!--查询方法,query,search,find-->
<tx:method name="*" propagation="SUPPORTS" read-only="true" />
</tx:attributes>
</tx:advice>
(4)配置aop,配置增强器
指定哪些哪类要创建代理
<!--配置aop-->
<aop:config>
<!--配置切入点表达式:指定哪些包中类,要使用事务
id:切入点表达式的名称,唯一值
expression:切入点表达式,指定哪些类要使用事务,aspectj会创建代理对象
-->
<aop:pointcut id="servicePt" expression="execution(* *..service..*.*(..))"/>
<!--配置增强器:关联adivce和pointcut
advice-ref:通知,上面tx:advice哪里的配置
pointcut-ref:切入点表达式的id
-->
<aop:advisor advice-ref="studentService" pointcut-ref="servicePt" />
</aop:config>
Spring与Web
这一章主要介绍了一个核心知识点:解决不同Servlet中重复创建ApplicationContext对象,造成内存浪费的问题,即重复创建Spring容器。
解决这个问题的一个思路是,创建一个ServletContextListener,在ServletContext初始化的时候创建ApplicationContext对象,并将它保存在ServletContext中。
这样,在每个servlet中,只要调用当前servlet的ServletContext对象getAttribute方法就可以获取这个webapp中共享的一个ApplicationContext对象。
spring-web框架已经帮我们创建好了这样一个监听器。我们只需要在web.xml注册这个监听器就可以使用了。
(1)maven创建web模块,加入servlet,jsp依赖,拷贝之前spring和mybatis中所用的依赖
<!--以下是新增依赖,其它依赖到之前做的项目中拷贝-->
<!-- servlet依赖 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!-- jsp依赖 -->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.2.1-b03</version>
<scope>provided</scope>
</dependency>
<!--spring-web依赖:有监听器-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.2.4.RELEASE</version>
</dependency>
(2)需要为监听器提供Spring的配置文件路径信息
<!--注册spring框架提供的监听器
在监听器启动的时候,会寻找/WEB-INF/applicationContext.xml,为什么找这个文件?
在监听器的初始方法中,会创建spring的容器对象, 在创建容器对象时,需要读取配置文件
监听器默认是找/WEB-INF/applicationContext.xml。
-->
<!--自定义spring配置文件的位置和名称-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
(3)在Sevlet中获取ApplicationContext对象
WebApplicationContext ctx = null;
String key = WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE;
Object attr = getServletContext().getAttribute(key);
if( attr != null){
ctx = (WebApplicationContext)attr;
}
webApplicationContext是ApplicationContext的子类,是在web项目中使用的Spring容器对象。
为了不使用框架给出的难记的key值获取webApplicationContext,这个框架还提供了一个工具类。
使用工具类获取webApplicationContext:
//获取ServletContext中的容器对象,spring提供了一个方法,获取容器对象
WebApplicationContext ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext());