Spring简介
Spring 是轻量级的框架,其基础版本只有 2 MB 左右的大小。
Spring 框架的核心特性是可以用于开发任何 Java 应用程序,但是在 Java EE 平台上构建 web 应用程序是需要扩展的。 Spring 框架的目标是使 J2EE 开发变得更容易使用,通过启用基于 POJO 编程模型来促进良好的编程实践。
Spring框架特点:
- ==代码解耦,简化开发== Spring 就是一个大工厂,可以将所有对象的创建和依赖关系的维护交给 Spring 管理。
- ==横向扩展,方便集成各种优秀框架== Spring 不排斥各种优秀的开源框架,其内部提供了对各种优秀框架(如 Struts2、Hibernate、MyBatis 等)的直接支持。
- ==降低 Java EE API 的使用难度== Spring 对 Java EE 开发中非常难用的一些 API(JDBC、JavaMail、远程调用等)都提供了封装,使这些 API 应用的难度大大降低。
- ==方便程序的测试== Spring 支持 JUnit4,可以通过注解方便地测试 Spring 程序。
- ==AOP 编程的支持== Spring 提供面向切面编程,可以方便地实现对程序进行权限拦截和运行监控等功能。
- ==声明式事务的支持== 只需要通过配置就可以完成对事务的管理,而无须手动编程。
Spring优势
- Spring 可以使开发人员使用 POJOs 开发企业级的应用程序。只使用 POJOs 的好处是你不需要一个 EJB 容器产品,比如一个应用程序服务器,但是你可以选择使用一个健壮的 servlet 容器,比如 Tomcat 或者一些商业产品。
- Spring 对 JavaEE 开发中非常难用的一些 API(JDBC、JavaMail、远程调用等),都提供了封装,使这些API应用难度大大降低。
- Spring 的 web 框架是一个设计良好的 web MVC 框架。MVC 模式导致应用程序的不同方面(输入逻辑,业务逻辑和UI逻辑)分离,同时提供这些元素之间的松散耦合。模型(Model)封装了应用程序数据,通常它们将由 POJO 类组成。视图(View)负责渲染模型数据,一般来说它生成客户端浏览器可以解释 HTML 输出。控制器(Controller)负责处理用户请求并构建适当的模型,并将其传递给视图进行渲染。
- 测试一个用 Spring 编写的应用程序很容易,因为环境相关的代码被移动到这个框架中。此外,通过使用 JavaBean-style POJOs,它在使用依赖注入注入测试数据时变得更容易。
- 轻量级的 IOC 容器往往是轻量级的,例如,特别是当与 EJB 容器相比的时候。这有利于在内存和 CPU 资源有限的计算机上开发和部署应用程序。
- Spring 提供了一致的事务管理接口,可向下扩展到(使用一个单一的数据库,例如)本地事务并扩展到全局事务(例如,使用 JTA)。
Spring特性
- 非侵入式:基于Spring开发的应用中的对象可以不依赖于Spring的API
- 控制反转:IOC------Inversion of Control,指的是将对象的创建权交给 Spring 去创建。使用 Spring 之前,对象的创建都是由我们自己在代码中new创建。而使用 Spring 之后。对象的创建都是给了 Spring 框架。
- 依赖注入:DI------Dependency Injection,是指依赖的对象不需要手动调用 setXXX 方法去设置,而是通过配置赋值。
- 面向切面编程:Aspect Oriented Programming------AOP,在 OOP(面向对象编程) 中模块化的关键单元是类,而在 AOP 中模块化的关键单元是方面。AOP 帮助你将横切关注点从它们所影响的对象中分离出来,然而依赖注入帮助你将你的应用程序对象从彼此中分离出来。
- 容器:Spring 是一个容器,因为它包含并且管理应用对象的生命周期
- 组件化:Spring 实现了使用简单的组件配置组合成一个复杂的应用。在 Spring 中可以使用XML和Java注解组合这些对象。
- 一站式:在 IOC 和 AOP 的基础上可以整合各种企业应用的开源框架和优秀的第三方类库(实际上 Spring 自身也提供了表现层的 SpringMVC 和持久层的 Spring JDBC)
Spring体系结构
==Core Continer(核心容器)==: 核心容器由 spring-core,spring-beans,spring-context(spring-context-support)和spring-expression(SpEL,Spring 表达式语言,Spring Expression Language)等模块组成,它们的细节如下:
-
spring-core 模块提供了框架的基本组成部分,包括 IoC 和依赖注入功能。
-
spring-beans 模块提供 BeanFactory,工厂模式的微妙实现,它移除了编码式单例的需要,并且可以把配置和依赖从实际编码逻辑中解耦。
-
context 模块建立在由 core和 beans 模块的基础上建立起来的,它以一种类似于 JNDI 注册的方式访问对象。Context 模块继承自 Bean 模块,并且添加了国际化(比如,使用资源束)、事件传播、资源加载和透明地创建上下文(比如,通过 Servelet 容器)等功能。Context 模块也支持 Java EE 的功能,比如 EJB、JMX 和远程调用等。ApplicationContext 接口是 Context 模块的焦点。spring-context-support 提供了对第三方集成到 Spring 上下文的支持,比如缓存(EhCache, Guava, JCache)、邮件(JavaMail)、调度(CommonJ, Quartz)、模板引擎(FreeMarker, JasperReports, Velocity)等。
-
spring-expression 模块提供了强大的表达式语言,用于在运行时查询和操作对象图。它是 JSP2.1 规范中定义的统一表达式语言的扩展,支持 set 和 get 属性值、属性赋值、方法调用、访问数组集合及索引的内容、逻辑算术运算、命名变量、通过名字从 Spring IoC 容器检索对象,还支持列表的投影、选择以及聚合等。
它们的完整依赖关系如下图所示: 其他部分都依赖于核心容器 ==Spring Data Access/Intergration(数据访问/集成)== 数据访问/集成层包括 JDBC(Java Data Base Connectivity),ORM(Object Relational Mapping),OXM(Object XML Mapping),JMS(Java Message Service) 和事务处理模块,它们的细节如下:
-
JDBC 模块提供了 JDBC 抽象层,它消除了冗长的 JDBC 编码和对数据库供应商特定错误代码的解析。
-
ORM 模块提供了对流行的对象关系映射 API 的集成,包括 JPA、JDO 和 Hibernate 等。通过此模块可以让这些 ORM 框架和 spring的其它功能整合,比如前面提及的事务管理。
-
OXM 模块提供了对 OXM 实现的支持,比如 JAXB、Castor、XML Beans、JiBX、XStream 等。
-
JMS 模块包含生产(produce)和消费(consume)消息的功能。从 Spring 4.1 开始,集成了 spring-messaging 模块。
-
事务模块为实现特殊接口类及所有的 POJO 支持编程式和声明式事务管理。(注:编程式事务需要自己写 beginTransaction()、commit()、rollback() 等事务管理方法,声明式事务是通过注解或配置由 spring 自动处理,编程式事务粒度更细)
==Web== Web 层由 Web,Web-MVC,Web-Socket 和 Web-Portlet 组成,它们的细节如下:
-
Web 模块提供面向 web 的基本功能和面向 web 的应用上下文,比如多部分(multipart)文件上传功能、使用 Servlet 监听器初始化 IoC 容器等。它还包括 HTTP 客户端以及 Spring 远程调用中与 web 相关的部分。
-
WebMVC 模块为 web 应用提供了模型视图控制(MVC)和 REST Web服务的实现。Spring 的 MVC 框架可以使领域模型代码和 web 表单完全地分离,且可以与 Spring 框架的其它所有功能进行集成。
-
WebSocket 模块为 WebSocket-based 提供了支持,而且在 web 应用程序中提供了客户端和服务器端之间通信的两种方式。
-
WebFlux 是 Spring Framework5.0 中引入的一种新的反应式Web框架。通过Reactor项目实现Reactive Streams规范,完全异步和非阻塞框架。本身不会加快程序执行速度,但在高并发情况下借助异步IO能够以少量而稳定的线程处理更高的吞吐,规避文件IO/网络IO阻塞带来的线程堆积。
==AOP== AOP 模块提供了面向切面的编程实现,允许你定义方法拦截器和切入点对代码进行解耦,从而使实现功能的代码彻底的解耦出来。
==Aspects== Aspects 模块提供了与 AspectJ 的集成,这是一个功能强大且成熟的面向切面编程(AOP)框架。
==Instrumentation== Instrumentation 模块在一定的应用服务器中提供了类 instrumentation 的支持和类加载器的实现。
==Messaging== Messaging 模块为 STOMP 提供了支持作为在应用程序中 WebSocket 子协议的使用。它也支持一个注解编程模型,它是为了选路和处理来自 WebSocket 客户端的 STOMP 信息。
==Test== 测试模块支持对具有 JUnit 或 TestNG 框架的 Spring 组件的测试。
Spring部署
Spring依赖IoC特性,若将其看作一个大工厂(==容器==)Bean管理就交由它完成,那么就需要有相应规则(==配置文件==)来指导容器进行Bean生产,依赖注入,事务管理,AOP编程等等。
==IoC的初步应用==
- 创建java项目
- 导入Spring核心工具包 最后一个是Apache的日志工具包。官网下载是使用spring的必备包,用来记录程序运行时的活动的日志记录。
- 创建配置文件
Beans.xml
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-3.0.xsd">
<bean id="stu2" class="model.Student">
<property name="name"><value>Spring FrameWork Runingtime</value></property>
<property name="age"><value>22</value></property>
<property name="address"><value>北京</value></property>
</bean>
</beans>
该配置文件是spring配置文件的最基本结构。通过命名空间进行属性注入等等一些配置。
- 创建IoC容器进行Bean管理
java
import org.springframework.context.support.ClassPathXmlApplicationContext;
import model.HelloWorld;
import model.Student;
public class Test{
public static void main(String[] args) {
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("Beans.xml");
Student student= (Student) context.getBean("stu2");
System.out.print(student);
}
}
代码中中并没有通过new
关键字实例化对象并赋值,而是通过Spring容器getBean
方法来实现bean的创建,这样就没有注入到代码中即使编译为class文件只需要修改配置文件就可以,降低了代码的耦合性。
==DI(依赖注入)== 上面代码中通过bean
标签创建了一个IoC容器,其中property
就是在进行依赖注入,即在Spring完成对象创建的同时,依赖Spring容器完成对属性的赋值。如果没有DI的话都是值都是null
。
IoC 容器
Spring 容器是 Spring 框架的核心。容器将创建对象,把它们连接在一起,配置它们,并管理他们的整个生命周期从创建到销毁。Spring 容器使用依赖注入(DI)来管理组成一个应用程序的组件。这些对象被称为 Spring Beans。
IoC通过阅读配置元数据提供的指令,容器知道对哪些对象进行实例化,配置和组装。配置元数据可以通过 XML,Java 注释或 Java 代码来表示。下图是 Spring 如何工作的高级视图。 Spring IoC 容器利用 Java 的 POJO 类和配置元数据来生成完全配置和可执行的系统或应用程序。
IOC 容器具有依赖注入功能的容器,它可以创建对象,IOC 容器负责实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。通常new一个实例,控制权由程序员控制,而"控制反转"是指new实例工作不由程序员来做而是交给Spring容器来做。在Spring中BeanFactory是IOC容器的实际代表者。
Spring 提供了以下两种不同类型的容器:
容器 | 描述 |
---|---|
BeanFactory 容器 | org.springframework.beans.factory.BeanFactory 接口来定义。BeanFactory 或者相关的接口,如 BeanFactoryAware,InitializingBean,DisposableBean,主要目的是向后兼容已经存在的和那些 Spring 整合在一起的第三方框架。 |
ApplicationContext 容器 | Application Context 是 BeanFactory 的子接口,也被称为 Spring 上下文。Application Context 是 spring 中较高级的容器。和 BeanFactory 类似,它可以加载配置文件中定义的 bean,将所有的 bean 集中在一起,当有请求的时候分配 bean。 |
BeanFactory 容器
BeanFactory 容器最常被使用的是 XmlBeanFactory 类。这个容器从一个 XML 文件中读取配置元数据,由这些元数据来生成一个被配置化的系统或者应用。
java
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;
public class MainApp {
public static void main(String[] args) {
XmlBeanFactory factory = new XmlBeanFactory
(new ClassPathResource("Beans.xml"));
HelloWorld obj = (HelloWorld) factory.getBean("helloWorld");
obj.getMessage();
}
}
/*利用框架提供的 XmlBeanFactory() API 去生成工厂 bean 以及利
用 ClassPathResource() API 去加载在路径 CLASSPATH 下可用的
bean 配置文件。XmlBeanFactory() API 负责创建并初始化所有的
对象,即在配置文件中提到的 bean。*/
/*
利用第一步生成的 bean 工厂对象的 getBean() 方法得到所需要的 bean。
这个方法通过配置文件中的 bean ID 来返回一个真正的对象,*/
ApplicationContext 容器
Application Context 是 BeanFactory 的子接口,也被称为 Spring 上下文。 Application Context 是 spring 中较高级的容器。和 BeanFactory 类似,它可以加载配置文件中定义的 bean,将所有的 bean 集中在一起,当有请求的时候分配 bean。 另外,它增加了企业所需要的功能,比如,从属性文件中解析文本信息和将事件传递给所指定的监听器。
该容器常使用的 ApplicationContext 接口:
- ==FileSystemXmlApplicationContext==:该容器从 XML 文件中加载已被定义的 bean。在这里,你需要提供给构造器 XML 文件的完整路径。
java
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
public class MainApp {
public static void main(String[] args) {
ApplicationContext context = new FileSystemXmlApplicationContext
("C:/HelloSpring/src/Beans.xml");
HelloWorld obj = (HelloWorld) context.getBean("helloWorld");
obj.getMessage();
}
}
- ==ClassPathXmlApplicationContext==:该容器从 XML 文件中加载已被定义的 bean。在这里,你不需要提供 XML 文件的完整路径,只需正确配置 CLASSPATH 环境变量即可,因为,容器会从 CLASSPATH 中搜索 bean 配置文件。
java
ApplicationContext context=new ClassPathXmlApplicationContext("beans.xml");
- ==WebXmlApplicationContext==:该容器会在一个 web 应用程序的范围内加载在 XML 文件中已被定义的 bean。
DI依赖注入
依赖注入是控制反转的一个具体的例子。当编写一个 Java 应用程序时,应用程序类(数据操作)应该尽可能的独立于其他的 Java 类来增加这些类可重用性;当进行单元测试时,可以使它们独立于其他类进行测试。依赖注入可以向构造函数传递参数的方式发生,依赖:类 A 依赖于类 B。注入:当需要时,类 B 将通过 IoC 被注入到类 A 中。
Spring容器加载配置文件后,通过反射
创建类的对象,并给属性赋值。通过反射实现属性赋值由三种方式:
- ==setter方法注入==bean对象中使用
property
标签调用set方法
xml
//属性是基本数据类型,直接用value属性或value标签赋值即可
<property name="name" value="Spring FrameWork Runingtime"/>
<property name="list"><value>Spring,FrameWork,Runingtime</value></property>
//name属性对应成员变量,value对应赋值。spring容器自动根据bean的类型定义自动转化
xml
//属性是特殊数据类型用ref属性引用<bean>标签生产的对象或<ref>标签bean属性引用或直接属性内部<bean>生产,对象可以是内置或自定义对象
<bean id="date" class="java.util.Date">
</bean>
<bean id="date1" class="model.DateTest">
<property name="dateFromat" ref="date"></property> //ref=id
</bean>
||
<bean id="date2" class="model.DateTest">
<property name="dateFromat">
<ref bean="date"></ref>
</property>
</bean>
//也可以属性内进行bean生产
<bean id="date2" class="model.DateTest">
<property name="dateFromat">
<bean class="java.util.Date"></bean>
</property>
</bean>
//演示的是Date类型,可以是其他自定义类型
<bean id="date3" class="model.DateTest">
<property name="student">
<bean class="model.Student">
<property name="name" value="张三"></property>
<property name="age" value="22"></property>
<property name="address" value="wuhan"></property>
</bean>
</property>
</bean>
//注入 null 和空字符串的值
<bean id="..." class="...">
<property name="email" value=""/>
</bean>
<bean id="..." class="...">
<property name="email"><null/></property>
</bean>
xml
//如果是基本数据结构List,Set,Map就需要借助对应<list>,<set>,<map>标签实现,
list,set值少也可以借助value属性或<value>标签实现
<bean id="list" class="model.ListTest">
<!-- <property name="list"><value>Java,Python,Php,Javacript</value></property> -->
<!-- <property name="list" value="Java,Python,Php,Javacript"></property> -->
<property name="list">
<list>
<value>Java</value>
<value>Python</value>
<value>JavaScript</value>
</list>
</property>
</bean>
<bean id="set" class="model.SetTest">
<!-- <property name="set"><value>Java,Python,Php,Javacript</value></property> -->
<!-- <property name="set" value="Java,Python,Php,Javacript"></property> -->
<property name="set">
<set>
<value>Java</value>
<value>Python</value>
<value>JavaScript</value>
</set>
</property>
</bean>
<bean id="map" class="model.MapTest">
<property name="map">
<map>
<entry> //map标签的套用标签
<key>
<value>public</value>
</key>
<value>语文,英语,数学</value>
</entry>
<entry>
<key>
<value>professonal</value>
</key>
<value>Java,Python,JavaScript</value>
</entry>
</map>
</property>
</bean>
- ==构造方法注入==使用
constructor-arg
标签传递构造参数
xml
type 属性显式的指定了构造函数参数的类型
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg type="int" value="2001"/>
<constructor-arg type="java.lang.String" value="Zara"/>
</bean>
index 属性来显式的指定构造函数参数的索引
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg index="0" value="2001"/>
<constructor-arg index="1" value="Zara"/>
</bean>
向一个对象传递一个引用,需要使用 标签的 ref 属性
java
//在编写java bean是一般包含无参构造和有参构造,构造方法注入局势利用了有参构造
public class Book {
private String name;
private Double price;
private List chapter;
private Map<String,String> chapterMap;
public Book() {
}
public Book(String arg1,Double arg2,List arg3,Map arg4) {
this.name=arg1;
this.price=arg2;
this.chapter=arg3;
this.chapterMap=arg4;
}
public String toString() {
return this.name+
","+this.price+
","+this.chapter+
","+this.chapterMap;
}
}
xml
//利用构造方法为属性赋值,也分为基本数据类型和复杂数据类型,用法同上
<bean id="book" class="model.Book">
<constructor-arg index="0" value="完美世界"></constructor-arg>
<constructor-arg index="1"><value>48.5</value></constructor-arg>
<constructor-arg index="2">
<list>
<value>石村篇</value>
<value>大战荒域</value>
</list>
</constructor-arg>
<constructor-arg index="3">
<map>
<entry>
<key>
<value>第一章</value>
</key>
<value>石村篇</value>
</entry>
<entry>
<key>
<value>第二章</value>
</key>
<value>大战荒域</value>
</entry>
</map>
</constructor-arg>
</bean>
/*index属性是构造参数的位置,如果位置和对象的一致可以不写。基本数据类型用value标签或value属性,特殊用
对应的标签即可,对象用<ref bean="">或<bean class="">
*/
// type 属性显式的指定了构造函数参数的类型
- 接口注入
Spring配置文件介绍
①xmlns="www.springframework.org/schema/bean... Bean的定义;
②xmlns:xsi="www.w3.org/2001/XMLSch...
Spring Bean
Spring IoC容器使用依赖注入(DI)来管理组成应用程序的组件。这些对象被称为Spring Beans。每一个对象称为bean。在XML的定义。
bean的属性及子元素
bean定义包含容器所需的元数据的信息:
属性 | 描述 |
---|---|
name | 此属性指定唯一bean标识符。在基于XML的配置元数据时,您可以使用id和/或name属性来指定bean标识符 |
class | 此属性是强制性的,并指定bean类被用来创建bean |
scope | 用来配置spring bean的作用域,取值有singleton(单例模式)和prototype(多例模式) |
lazy-init | 设为true,延迟加载,该bean不会在ApplicationContext启动时提前被实例化,而是第一次向容器通过getBean索取bean时实例化,只对singleton的bean起作用 |
autowire | 用来配置spring对象属性的默认的装配方式。no: 默认值 不启用自动装配;byType :根据类型自动装配 ;byName: 根据名称自动装配,一般bean的name如果不声明 默认值取的就是id;构造器实现 |
dependency-check | 依赖检查 |
depends-on | 表示一个bean的实例化依靠另一个bean先实例化 |
autowire-candidate | 设为false,容器在查找自动装配对象时,将不考虑该bean,即它不会被考虑作为其他bean自动装配的候选者,但是该bean本身可以使用自动装配来注入其他bean |
primary | 该bean优先被注入 |
init-method | 初始化bean时调用的方法 |
destory-method | 容器销毁之前所调用的方法 |
factory-method | 当调用factory-method所指向的方法时,才开始实例化bean |
factory-bean | 调用静态工厂方法的方式创建bean |
bean的子元素
子元素 | 描述 |
---|---|
meta | 元数据,当需要使用里面的信息时可以通过key获取 |
lookup-method | 获取器注入,是把一个方法声明为返回某种类型的bean但实际要返回的bean是在配置文件里面配置的 |
replaced-method | 可以在运行时调用新的方法替换现有的方法,还能动态的更新原有方法的逻辑 |
constructor-arg | 对bean自动寻找对应的构造函数,并在初始化的时候将设置的参数传入进去 |
property | 对bean管理的对象通过setter方法赋值 |
qualifier | 通过Qualifier指定注入bean的名称 |
bean的作用域
当在 Spring 中定义一个 bean 时,你必须声明该 bean 的作用域的选项。例如,为了强制 Spring 在每次需要时都产生一个新的 bean 实例,你应该声明 bean 的作用域的属性为 prototype。同理,如果你想让 Spring 在每次需要时都返回同一个bean实例,你应该声明 bean 的作用域的属性为 singleton。
xml
<bean id="book" class="model.Book"></bean>
java
ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext("beans.xml");
Book book =(Book) context.getBean("book");
System.out.print(book);
通过getBean
方法获取IoC容器的bean对象,默认是同一个引用,每个Book对象的地址都相同 ,因为bean
标签的默认作用域是单例,如果有多处使用会产生线程问题,通过scope
属性改变作用域。 scope
=singleton
(单例模式)|| prototype
(多例模式) 单例模式在初始化容器时就创建,可以通过lazy-init=true
实现懒加载。一般工厂类使用单例模式,其他使用多例模式,在调用bean时创建。
Spring 框架支持以下五个作用域,分别为 singleton、prototype、request、session 和 global session,5种作用域说明如下:
bean的生命周期
当一个 bean 被实例化时,它可能需要执行一些初始化使它转换成可用状态。同样,当 bean 不再需要,并且从容器中移除时,可能需要做一些清除工作。bean生命周期
为了定义安装和拆卸一个 bean,我们只要声明带有 ==init-method==和==destroy-method== 参数的 。init-method 属性指定一个方法,在实例化 bean 时,立即调用该方法。同样,destroy-method 指定一个方法,只有从容器中移除 bean 之后,才能调用该方法。
Bean的生命周期可以表达为:Bean的定义------Bean的初始化------Bean的使用------Bean的销毁
xml
<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-3.0.xsd">
<!-- 创建spring bean -->
<bean id="..." class="...">
</bean>
<!-- 懒加载 -->
<bean id="..." class="..." lazy-init="true">
</bean>
<!-- 初始化方法,在构造方法之后-->
<bean id="..." class="..." init-method="...">
</bean>
<!-- 销毁方法 -->
<bean id="..." class="..." destroy-method="...">
</bean>
</beans>
init-method和destroy-method 的方法体在IoC容器管理的Java Bean中。
==Spring------Bean 后置处理器== Bean 后置处理器允许在调用初始化方法前后对 Bean 进行额外的处理。
BeanPostProcessor 接口定义回调方法,你可以实现该方法来提供自己的实例化逻辑,依赖解析逻辑等。你也可以在 Spring 容器通过插入一个或多个 BeanPostProcessor 的实现来完成实例化,配置和初始化一个bean之后实现一些自定义逻辑回调方法。
你可以配置多个 BeanPostProcessor 接口,通过设置 BeanPostProcessor 实现的 Ordered 接口提供的 order 属性来控制这些 BeanPostProcessor 接口的执行顺序。
ApplicationContext 会自动检测由 BeanPostProcessor 接口的实现定义的 bean,注册这些 bean 为后置处理器,然后通过在容器中创建 bean,在适当的时候调用它。
在你自定义的 BeanPostProcessor 接口实现类中,要实现以下的两个抽象方法 BeanPostProcessor.postProcessBeforeInitialization(Object, String) 和 BeanPostProcessor.postProcessAfterInitialization(Object, String)
bean继承
在定义一个 Bean 时,指定类抽象属性==abstract==,被明确地标记为抽象的,自身不能被实例化,子类==parent==指定父类实现继承。
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-3.0.xsd">
<bean id="father" class="model.Father" abstract="true">
<property name="message1" value="Hello World!"/>
</bean>
<bean id="son" class="model.Son" parent="father">
<property name="message1" value="Hello!"/>
<property name="message2" value="Spring !"/>
</bean>
</beans>
自动装配
自动装配:是指IoC容器在实例化bean时,从容器中查找匹配的实例赋值给当前的bean属性。
xml
//byName表示,初始化bean时根据bean的属性名查找IoC容器中匹配的标识符自动注入
<bean id="dat3" class="model.DateTest" autowire="byName">
</bean>
/*
java bean(DateTest.java):
private Data date;
spring bean:
<bean id="date" class="java.util.Date">
</bean>
<bean id="date1" class="model.DateTest" autowire="byName">
</bean>
在初始化第二个bean时会将spring bean的date注入到 date1的date变量,但是name对应的类型不匹配时报错
*/
//byType表示,初始化bean时根据bean的类型查找IoC容器中匹配的标识符自动注入
java bean(DateTest.java):
private Data date;
<bean id="date" class="java.util.Date">
</bean>
<bean id="date2" class="model.DateTest" autowire="byType">
</bean>
/*
在初始化第二个bean时在IoC容器中查找Date类型的对象,将其注入到date变量。但是有多个类型时会报错
*/
//当然还有构造方法和默认值no不启动自动装配
事件处理
Spring的核心是ApplicationContext,它管理bean的整个生命周期。ApplicationContext中加载bean时发布某些类型的事件。例如,一个ContextStartedEvent是当所述上下文被启动并发布,上下文被停止ContextStoppedEvent。
事件处理中ApplicationContext 通过了ApplicationEvent类和ApplicationListener接口提供。所以,如果一个bean实现了ApplicationListener,然后每anApplicationEvent被发布到ApplicationContext的时候通知bean。
Spring事件处理是单线程的,所以如果一个事件被发布,直至及除非所有的接收器得到的消息,该进程被阻塞并且流程将不会继续。因此,应注意在设计应用程序时,如果事件处理被使用。
要监听上下文事件,一个bean应该实现ApplicationListener接口,只有一个方法onApplicationEvent()。用于实现spring事件监听
java
//开始事件
/*
StartEvent.java
*/
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextStartedEvent;
public class StartEvent
implements ApplicationListener<ContextStartedEvent>{
public void onApplicationEvent(ContextStartedEvent event) {
System.out.println("ContextStartedEvent Received");
}
}
//结束事件
/*
StopEvent.java
*/
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextStoppedEvent;
public class StopEvent
implements ApplicationListener<ContextStoppedEvent>{
public void onApplicationEvent(ContextStoppedEvent event) {
System.out.println("ContextStoppedEvent Received");
}
}
//监听
ApplicationContext context =
new ClassPathXmlApplicationContext("Beans.xml");
// start event.
context.start();
/*
被监听事件
*/
// stop event.
context.stop();
xml
//在配置文件中声明一下就可以了
<bean id="StartEvent"
class="com.manongjc.StartEvent"/>
<bean id="StopEvent"
class="com.manongjc.StopEvent"/>
Spring基于注解开发
前面都是基于配置文件applicationContext.xml进行bean生产和管理,也可以采用XML来描述一个bean接线,实现注解的相关类,方法或字段声明将bean配置到组件类本身。注释注入在XML注入之前进行,因此后者的配置将覆盖前者的通过两种方式连接的属性。
注释接线默认情况下不开启在Spring容器中。所以,我们才可以使用基于注解的接线,要使用注解需要先在Spring配置文件中启用它。
xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<context:annotation-config/>
<!-- 注解扫描 -->
</beans>
当context:annotation-config配置后,就可以开始注解声明了,Spring自动连线的值到属性,方法和构造函数。
简单实例:
java
//配置文件继上
//bean对象
public class Book {
private String name;
private double price;
public void setName(String name) {
this.name=name;
}
public String getName() {
return this.name;
}
public void setPrice(Double price) {
this.price=price;
}
public String toString() {
return this.name+","+this.price;
}
}
//声明IoC容器类管理bean对象
@Configuration
public class BookConfig {
@Bean
public Book1 book() {
return new Book();
}
}
//bean生产
import model.Book;
import model.BookConfig;
public class AppOne {
public static void main(String[] args) {
ApplicationContext context= new AnnotationConfigApplicationContext(Book1Config.class);
Book1 book1=context.getBean(Book1.class);
book1.setName("完美世界");
System.out.print(book1.getName());
}
}
上面的简单实例就是实现注解开发。其中用到了 ==@Configuration==作用是声明一个Spring IoC容器进行bean的定义在这个类中进行bean生产。==@Bean==作用是bean生产,交由IoC容器管理。
xml
<beans ...>
...
<bean id="book" class="model.Book" />
</beans>
java
@Configuration
public class BookConfig {
@Bean
public Book1 book() {
return new Book();
}
}
这两段代码作用是一致。
基于Java注解
基于java的注解开发配置文件除开启注解外不做任何声明和定义,初始化及配置都基于java注解实现。导入时beans工厂的配置类:ApplicationContext context= new AnnotationConfigApplicationContext(BeansConfig.class)
在该工厂中通过@Bean等注解进行bean生产和装配。 核心:==容器配置类BeansConfig==
java
@Configuration //声明一个beans工厂
public class BeansConfig {
@Bean //bean生产
public Book1 book() {
return new Book();
}
}
案例:
java
Book.java:
public class Book {...}
BeansConfig.java:
@Configuration
public class BookConfig {
@Bean
public Book book() {
return new Book();
}
}
App.java (主函数):
public class App {
public static void main(String[] args) {
ApplicationContext context= new AnnotationConfigApplicationContext(BeansConfig.class);
Book book1=context.getBean(Book.class); // byType
//Book book1=(Book) context.getBean("book") //byName
book1.setName("完美世界");
System.out.print(book1.getName());
}
}
getBean()
方法有两种方法式,通过@bean注解的方法名(相当于id)和导入的类获取。
xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<!-- 开启注解扫描 -->
<context:annotation-config/>
<!-- 定义扫描范围 -->
<context:component-scan base-package="model"></context:component-scan>
</beans>
<!--配置文件中没有任何声明和定义处理开启注解扫描-->
IoC注解:
注解 | 描述 |
---|---|
@Configuration | 声明一个IoC容器,进行Beans生产,依赖注入,事务管理,AOP编程 |
@Component | 进行Bean生产,将bean交由IoC容器管理 |
@Bean | bean生产,将该bean对象交由IoC管理 |
@Value | 属性注入 |
@Import | 允许加载@Bean从另一个配置类定义 |
@Configuration
该注解基于java,声明一个IoC容器,进行Beans生产,依赖注入,事务管理,AOP编程。
java
@Configuration //申明IoC容器
public class AppConfig {
@Bean(initMethod = "init", destroyMethod = "destory" ) //生产bean方法明为id
public Book book() {
return new Book();
}
}
用此注解可以代替xml配置文件,由于默认是加载配置文件,故需要在配置类中开启注解扫描实现完全注解开发 ==@ComponentScan(basePackages={"com.model"})== 代替xml中开启注解扫描,测试类中加载配置类而不是xml了,实现完全注解开发。
@Bean注解
bean注解就相当于<bean>
标签,位于@Configuration声明的配置类容器的bean上方。其内部定义了bean属性、子元素、生命周期函数和作用域等。
声明生命周期函数
java
public class Book {
public void init() {
// initialization
}
public void destory() {
// destruction
}
}
@Configuration
public class AppConfig {
@Bean(initMethod = "init", destroyMethod = "destory" )
public Book book() {
return new Book();
}
}
//和下面的配置一致
xml
<!-- 初始化方法,在构造方法之后-->
<bean id="..." class="..." init-method="...">
</bean>
<!-- 销毁方法 -->
<bean id="..." class="..." destroy-method="...">
</bean>
指定Bean的作用域
java
@Configuration
public class BookConfig {
@Bean
@Scope("prototype")
public Book book() {
return new Book();
}
}
@Value注解
java
@Component
public class Book1 {
@Value(value = "zhansan")
private String name;
@Value(value = "48.45")
private Double price;
public void setName(String name) {
this.name = name;
}
public void setPrice(Double price) {
this.price = price;
}
@Override
public String toString() {
return "Book{" +
"name='" + name + '\'' +
", price=" + price +
'}';
}
}
@Import注解
java
@Configuration
public class ConfigA {
@Bean
public A a() {
return new A();
}
}
//在ConfigB配置类中引入ConfigA的bean
@Configuration
@Import(ConfigA.class)
public class ConfigB {
@Bean
public B a() {
return new A();
}
}
//只需要实例化ConfigB即可
public static void main(String[] args) {
ApplicationContext context =
new AnnotationConfigApplicationContext(ConfigB.class);
A a = context.getBean(A.class);
B b = context.getBean(B.class);
}
基于配置文件注解
以配置文件主、Java注解为辅分工合作完成注解开发,仍然导入配置文件ApplicationContext context=new ClassPathXmlApplicationContext("beans.xml")
==@Component== 该注解基于配置文件,生产bean并交由IoC容器管理。@Component(value="id")
相当于bean
标签,value为id属性。如果没有配置,则通过小写类名获取beanBook book =(Book) context.getBean("book")
。
java
@Component("id") //生产bean交由IoC管理,不用导入注解配置类(beansConfig)中
public class Book {
private String name;
private double price;
public void setName(String name) {
this.name=name;
}
public String getName() {
return this.name;
}
public void setPrice(Double price) {
this.price=price;
}
public String toString() {
return this.name+","+this.price;
}
}
ApplicationContext context=new ClassPathXmlApplicationContext("beans.xml");
Book book =(Book) context.getBean("id");
==@Service、@Controller和@Repository具有同样的功能==,他们主要时语义上的区别:
- @Controller声明将控制器类交给IoC管理如Servlet。
- @Service声明将业务管理类交由IoC管理,Servlet接口的实现类。
- @Repository声明持久化类交给IoC管理,DAO接口。
- @Component声明常规类。
bean对象使用这些注解直接在成员变量定义处赋值:
java
@Component("book")
public class Book {
private String name = "完美世界"; //直接在定义处赋值(静态赋值)
private double price = "48";
public void setName(String name) {
this.name=name;
}
public String getName() {
return this.name;
}
public void setPrice(Double price) {
this.price=price;
}
public String toString() {
return this.name+","+this.price;
}
}
==@Scope==注解 用于改变bean的作用域,两种[singleton,prototype]。
java
@Component("book")
@Scope(value="singleton")
public class Book {
private String name = "完美世界"; //直接在定义处赋值
private double price = "48";
public void setName(String name) {
this.name=name;
}
public String getName() {
return this.name;
}
public void setPrice(Double price) {
this.price=price;
}
public String toString() {
return this.name+","+this.price;
}
}
==@Lazy注解== 懒加载,singleton作用域下,使其在bean生产时加载bean而不是初始化配置文件时。
java
@Component("book")
@Scope(value="singleton")
@Lazy(true) //默认为false
public class Book {...}
==@PostConstruct 和 @PreDestroy 注解== 相当于 init-method 和destroy-method 管理bean的生命周期。
java
@Component("book")
...
public class Book {
...
@PostConstruct
public void init(){
System.out.println("Bean is init.");
}
@PreDestroy
public void destroy(){
System.out.println("Bean is destroy");
}
//这样注解就相当于在<bean>标签中的 init-method="init" 和destroy-method="destory"
//初始化方法子构造方法之后,销毁方法子容器销毁之前
==@Autowired 注解== 自动装配,从IoC容器中寻找匹配的对象自动注入到@Autowired 注解的对象。 该注解默认byType
如果没有找到对应的类型就会报错,若想没有找到就返回null设置required=false属性可以。
java
//用在成员变量之前
@Autowired(required=false)
private Book book;
//用在setter方法前
@Autowired(required=false)
public void setBook(Book book){
this.book=book;
}
//用在方法前
@Autowired(required=false)
==@Qualifier 注解== 当你创建多个具有相同类型的 bean 时,并且想要用一个属性只为它们其中的一个进行装配,在这种情况下,你可以使用 @Qualifier 注解和 @Autowired 注解通过指定哪一个真正的 bean 将会被装配来消除混乱。
java
...
@Component("book")
public class Book {...}
...
@Component("book1")
public class Book {...}
@Autowired
@Qualifier("book1")
private Book book;
@Autowired
public setBook(@Qualifier("book1") Book book){
this.book=book;
}
//@Qualifier注解引用了名称(byName)也可以看作为对ref的引用。
==@Resource 注解== 在字段中或者 setter 方法中使用 @Resource 注释。@Resource 注释使用一个 'name' 属性,该属性以一个 bean 名称的形式被注入,即它遵循 byName 自动连接语义。
如果没有明确地指定一个 'name',默认名称源于字段名或者 setter 方法。
如果byName没有找到,再通过byType查找,否则报错。
java
@Resource(name= "book")
public void setBook( Book book ){
this.book = book;
}
==@value==注入普通数据类型即属性。
java
@Component
@Scope("singleton")
public class Book2 {
@Value(value = "zhansang")
private String name;
@Value(value = "45.23")
public Double price;
public void setName(String name) {
this.name = name;
}
public void setPrice(Double price) {
this.price = price;
}
@Override
public String toString() {
return "Book2{" +
"name='" + name + '\'' +
", price=" + price +
'}';
}
@PostConstruct
public void before(){
System.out.println("init ....");
}
@PreDestroy
public void after(){
System.out.println("destroy ...");
}
}
==@Required 注解== @Required 注解应用于 bean 属性的 setter 方法,它表明受影响的 bean 属性在配置时必须放在 XML 配置文件中,否则容器就会抛出一个 BeanInitializationException 异常。
Spring AOP
Spring 框架的一个关键组件是面向切面的编程(AOP)框架。面向切面的编程需要把程序逻辑分解成不同的部分称为切面 。跨一个应用程序的多个点的功能被称为切入点 ,这些切面在概念上独立于应用程序的业务逻辑。在软件开发过程中有各种各样的很好的切面的例子,如日志记录、审计、声明式事务、安全性和缓存等。
在 OOP 中,关键单元模块度是类,而在 AOP 中单元模块度是切面。依赖注入帮助你对应用程序对象相互解耦合,AOP 可以帮助你从它们所影响的对象中对横切关注点解耦。
Spring AOP 模块提供拦截器来拦截一个应用程序,或者说==代理==例如,当执行一个方法时,将该方法由Spring代理执行,那么通过框架就可以在方法执行之前或之后添加额外的功能。 AOP底层就是JDK动态代理(JDK动态代理,CGlib动态代理)代理可以看千锋教育的SSM框架。
面向切面编程简单来说就是再不改变原来代码的基础上添加新的功能。
基本概念:
Spring AOP部署
下载额外工具包并引入:
xml配置文件使用aop命名空间,引入aop标签:
xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">
</beans>
声明一个代理,并强化功能:
xml
<aop:config>
<!--声明一个切入点-->
<aop:pointcut expression="execution(* aop_test.AopPointCut.insert())" id="myPoint"/>
<!--声明一个切面-->
<aop:aspect id="myAspect" ref="aspect1">
<!--通知-->
<aop:before method="before" pointcut-ref="myPoint"/>
</aop:aspect>
</aop:config>
基于配置文件的通知
通知是强化的方法,通过代理再代理对象的前后添加强化的功能。 通过<aop:aspect>
中使用<aop:{通知类型名}>
元素声明任意五种类型的通知:
xml
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
<aop:pointcut id="businessService"
expression="execution(* com.xyz.myapp.service.*.*(..))"/>
<!-- a before advice definition -->
<aop:before pointcut-ref="businessService"
method="doRequiredTask"/>
<!-- an after advice definition -->
<aop:after pointcut-ref="businessService"
method="doRequiredTask"/>
<!-- an after-returning advice definition -->
<!--The doRequiredTask method must have parameter named retVal -->
<aop:after-returning pointcut-ref="businessService"
returning="retVal"
method="doRequiredTask"/>
<!-- an after-throwing advice definition -->
<!--The doRequiredTask method must have parameter named ex -->
<aop:after-throwing pointcut-ref="businessService"
throwing="ex"
method="doRequiredTask"/>
<!-- an around advice definition -->
<aop:around pointcut-ref="businessService"
method="doRequiredTask"/>
...
</aop:aspect>
</aop:config>
<bean id="aBean" class="...">
...
</bean>
==切入点表达式== 对类的哪个方法加强,进行声明和配置: execution([权限修饰符] [返回类型] [类的全路径] [方法名称] ([参数列表]))
<aop:pointcut expression="execution(* aop_test.AopPointCut.insert())" id="myPoint"/>
中的execution括号中的第一个*表示返回类型(所有);第二个参数是方法的相对路径的相对路径,如果方法括号中(..)
表示参数对所有方法都有效。 ==声明代理==
xml
<aop:config>
</aop:config>
==代理内部声明切入点==
xml
<aop:pointcut id="businessService"
expression="execution(* com.xyz.myapp.service.*.*(..))"/>
==声明切面==
xml
<aop:aspect id="myAspect" ref="aBean">
...
</aop:aspect>
==通知定义==
xml
<!--前置通知-->
<aop:before pointcut-ref="businessService"
method="doRequiredTask"/>
<!--后置通知-->
<aop:after pointcut-ref="businessService"
method="doRequiredTask"/>
<!--返回后置通知-->
<aop:after-returning pointcut-ref="businessService"
returning="retVal"
method="doRequiredTask"/>
<!--抛出异常后通知-->
<aop:after-throwing pointcut-ref="businessService"
throwing="ex"
method="doRequiredTask"/>
<!--环绕通知-->
<aop:around pointcut-ref="businessService"
method="doRequiredTask"/>
简单aop实例 切入点和切面容易混淆,简单来说面向对象的基本单位是对象,面向切面的基本单位是方法,切入点就是被加强的方法,切面就是加强方法的集合。
java
//创建切入点(要加强的方法)
public class PointCutTest {
public void aspectApp(){
System.out.println("主程序运行...");
}
}
//创建切面(加强方法集合)
public class AspectTest {
public void before(){
System.out.println("aop starting ...");
}
public void after(){
System.out.println("aop ending ....");
}
}
xml中配置切面和切入点
xml
<--将切面和切入点交由IoC容器管理-->
<bean id="aspectTest" class="aspect.AspectTest"></bean>
<bean id="pointCutTest" class="aspect.PointCutTest"></bean>
<!--配置切面和切入点-->
<aop:config>
<!--切入点(要加强的方法)-->
<aop:pointcut id="cupTest" expression="execution(public void aspect.PointCutTest.*(..))"/>
<!--切面(加强方法合集)-->
<aop:aspect ref="aspectTest"> <!--ref引入IoC管理的切面-->
<aop:before method="before" pointcut-ref="cupTest"></aop:before>
<aop:after method="after" pointcut-ref="cupTest"></aop:after>
<!--method是切面中的加强方法,poincut-ref引入切入点-->
</aop:aspect>
</aop:config>
<!--整个反射过程IoC容器管理-->
java
//测试主程序
public class AopMainApp {
public static void main(String[] args) {
ApplicationContext context= new ClassPathXmlApplicationContext("classpath:aop.xml");
PointCutTest pointCutTest =(PointCutTest) context.getBean("pointCutTest"); //DL依赖查询创建切入点(加强方法)
pointCutTest.aspectApp();
}
}
结果:
基于注解的通知
基于注解的aop配置会更方便,在配置文件开启注解aop注解扫描:
xml
开启组件扫描和组件扫描路径
...
//基于注解配置的aop代理
<aop:aspectj-autoproxy/>
注解配置一定要导入aspectjweaver.jar
工具包,它是aop基于java扩展的@AspectJ
风格的注解,用于注解实现面向切面编程。
xml
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.8</version>
<scope>runtime</scope>
</dependency>
开发时一定到删除scope,不然会报错:
基于注解一定要导入如下包:
spring-aop:AOP核心功能,例如代理工厂等。 aspectjweaver:支持aop相关注解等。 aspectjrt:支持切入点表达式等。 aopalliance:是AOP联盟的API包,里面包含了针对面向切面的接口。通常Spring等其它具备动态织入功能的框架依赖此包。
如果实现完全注解开发需要用注解开启aop代理扫描: ==@EnableAspectJAutoProxy(proxyTargetClass=true)== (和@ComponentScan同级)这样就不需要配置文件实现完全注解开发。
==@Pointcut==
java
import org.aspectj.lang.annotation.Pointcut;
@Pointcut("execution(* com.xyz.myapp.service.*.*(..))") // expression
private void businessService() {} // signature
==@Aspect==
java
@Component
@Aspect
public class AspectTest {
@Before("execution(public void PointTest.strengthMethod(..))")
public void beforeMetod(){
System.out.println("one ...");
}
@After("execution(public void PointTest.strengthMethod(..))")
public void afterMethod(){
System.out.println("two ...");
}
}
==@{事务名}==
java
@Before("businessService()") //businessService()相当于execution(* com.xyz.myapp.service.*.*(..))归功于@Pointcut
public void doBeforeTask(){
...
}
@After("businessService()")
public void doAfterTask(){
...
}
@AfterReturning(pointcut = "businessService()", returning="retVal")
public void doAfterReturnningTask(Object retVal){
// you can intercept retVal here.
...
}
@AfterThrowing(pointcut = "businessService()", throwing="ex")
public void doAfterThrowingTask(Exception ex){
// you can intercept thrown exception here.
...
}
@Around("businessService()")
public Object arround(ProceedingJoinPoint point){
...
Object obj=point.proceed();
...
return obj;
}
环绕通知的方法格式一定:
java
@Around("businessService()")
public Object arround(ProceedingJoinPoint point){
...
Object obj=point.proceed();
...
return obj;
}
java
@Component("beans")
@Aspect //声明切面 和<aop:aspect>功能一样
public calss Test{
@Before("execution(* controller.*.*(..))")
}
==@Order(数字)== 确定多个增强方法的优先级。
java
@Aspect
@Order(1)
public class Proxy(){...}
案例: Beans.xml:
xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<bean id="stu2" class="model.Student">
<property name="name"><value>Spring FrameWork Runingtime</value></property>
<property name="age"><value>22</value></property>
</bean>
<bean id="m1" class="aoptest.PutApp"></bean>
<bean id="m2" class="aoptest.AddOne"></bean>
<!-- <aop:aspectj-autoproxy proxy-target-class="true"/> -->
<aop:config>
<aop:pointcut expression="execution(* aoptest.PutApp.put())" id="pointOne"/>
<aop:aspect id="aspectOne" ref="m2">
<aop:before method="before" pointcut-ref="pointOne"/>
<aop:after method="after" pointcut-ref="pointOne"/>
</aop:aspect>
</aop:config>
</beans>
项目结构: 额外工具包:
AddOne:
java
public class AddOne {
public void before() {
System.out.println("before...");
}
public void after() {
System.out.println("after...");
}
}
PutApp:
java
public class PutApp {
public void put() {
System.out.println("主程序!");
}
}
测试程序App:
java
import aoptest.PutApp;
public class AppOne {
public static void main(String[] args) {
ApplicationContext context=new ClassPathXmlApplicationContext("Beans.xml");
PutApp put=(PutApp) context.getBean("m1");
put.put();
}
}
结果: 注解案例:
xml
<!--开启注解驱动-->
<context:annotation-config></context:annotation-config>
<context:component-scan base-package="aspect"></context:component-scan>
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
java
//切入点,被加强方法
@Component("pointTest")
public class Point2 {
//该注解声明再方法上就是一个切入点
@Pointcut
public void strengthMethod(){
System.out.println("加强方法...");
}
}
//切面,加强方法
@Component
@Aspect
public class AspectTest {
//通知的方法引入切入点
@Before("execution(public void Point2.strengthMethod(..))")
public void one(){
System.out.println("one ...");
}
@After("execution(public void Point2.strengthMethod(..))")
public void two(){
System.out.println("two ...");
}
}
//测试主类
public class AopMain {
public static void main(String[] args) {
ApplicationContext context= new ClassPathXmlApplicationContext("classpath:aop.xml");
PointTest point=(PointTest) context.getBean("pointTest");
point.strengthMethod();
}
}
结果:
需要注意的是:
@PointCut
声明的就是切入点,@Aspect
声明的是切面,通知如@Before
引入切入点配置的切入点要正确。如果在@PointCut
声明了类,则在@Before
只需要声明方法。
Spring JDBC (Spring Data Access模块)
普通的 JDBC 数据库时,需要写很多的代码来处理异常,包括打开和关闭数据库连接等。Spring JDBC 框架负责所有的低层细节,从开始打开连接,准备和执行 SQL 语句,处理异常,处理事务,到最后关闭连接。
Spring JDBC 提供几种方法和数据库中相应的不同的类与接口。其中JdbcTemplate 类是管理所有数据库通信和异常处理的中央框架类。
JdbcTemplate 类执行 SQL 查询、更新语句和存储过程调用,执行迭代结果集和提取返回参数值。它也捕获 JDBC 异常并转换它们到 org.springframework.dao 包中定义的通用类、更多的信息、异常层次结构。
JdbcTemplate 类的实例是线程安全配置的。所以你可以配置 JdbcTemplate 的单个实例,然后将这个共享的引用安全地注入到多个 DAOs 中。
配置数据源和JdbcTemplate
工具包:
如果在数据库操作中有事务管理,还需要spring的 spring-tx-5.2.5.RELEASE.jar
事务管理的包。如果整合的其他框架如mybatis则要使用spring的orm工具包spring-orm-5.2.5.RELEASE.jar
。需要连接池的还要德鲁伊连接池。
jdbc操作数据库的和兴对象:Driver,DiverManager,Connecttion,Statement,PreparedStatement,ResultSet。其中前三个部分是配置数据源,Statement是进行数据库会话,最后一接收数据处理的结果。
Spring jdbc在此基础上做了封装,Spring jdbc的核心是jdbcTemplate,DDL操作都通过该对象实现,在IoC容器配置数据源DriverManagerDataSource
,通过jdbcTemplate
数据库会话和返回结果。
==配置数据源和工厂模板==
xml
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/db1"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
除了在xml配置直接写入配置信息外也可从外部properties
文件导入:
ini
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/db?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
jdbc.username=root
jdbc.password=root
在xml配置文件导入外部配置文件并注入:
xml
<!--导入外部properies文件-->
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource" id="dataSource">
<!--SEpl表达式语言-->
<property name="driverClassName" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
</beans>
通过<context:property-placeholder>
导入外部配置,${}
表达式语言注入。
JdbcTemplate接口操作数据库
jdbcTemplate工厂提供了如下接口: execute
:可以执行所有SQL语句,一般用于执行DDL语句。 update
:用于执行INSERT、UPDATE、DELETE等DML语句。 queryXxx,query
:用于DQL数据查询语句。 batchUpdate
:批量执行DML语句。
jdbc对Resulset的解析过程非常麻烦,但是spring jdbc提供了以下对象用于确定返回的集合(List)的类型,不需要进行循化遍历:
java
//执行查询语句,返回一个指定类型的数据(返回一个对象)
public <T> T queryForObject(String sql, Class<T> requiredType)
/*
int count= jdbcTemplate.queryForObject(sql, Integer.class);
*/
//执行查询语句,将一条记录放到一个Map中
public Map<String, Object> queryForMap(String sql)
/*
Map<String, Object> map = jdbcTemplate.queryForMap(sql, 6);
*/
//执行查询语句,返回一个List集合,List中存放的是Map类型的数据
public List<Map<String, Object>> queryForList(String sql)
/*
List<Map<String, Object>> list = jdbcTemplate.queryForList(sql, 8);
for (Map<String, Object> map : list) {
System.out.println(map);
}
*/
//执行查询语句,返回一个List集合,List中存放的是RowMapper指定类型的数据
public <T> List<T> query(String sql, RowMapper<T> rowMapper)
public <T> List<T> query(sql, new BeanPropertyRowMapper<>() )
/*
public class BeanPropertyRowMapper<T> implements RowMapper<T>
BeanPropertyRowMapper类实现了RowMapper接口
*/
RowMapper
和BeanPropertyRowMapper
RowMapper
和BeanPropertyRowMapper
类都是用于解析自定义的java bean对象,该对象是spring jdbc提供用来映射java bean对象和数据库表的数据项(一行数据),并返回包含所有数据项的list集合。
但是java bean对象的成员变量和数据项之间的对应关系需要声明,方法是实现RowMapper
或BeanPropertyRowMapper
对象。(和mybatis有区别,其声明更简单只需要resultType=bean即可,字段与成员变量的关系不需要声明)具体如下:
java
public static class StudentMapper implements RowMapper<User>{
@Override
public User mapRow(ResultSet resultSet, int i) throws SQLException {
User user=new User();
user.setUsername(resultSet.getString("username"));
user.setPassword(resultSet.getString("password"));
return user;
}
}
RowMapper
的实现类重写mapRow方法,将ResultSet的结果,与User的成员变量关联,最终返回一个User对象,再将实现类作为传递给query
方法,最终返回元素是User的List集合List<User>
:
java
JdbcTemplate jdbcTemplate=(JdbcTemplate) context.getBean("jdbcTemplate");
String sql="select * from user";
//实例化实现类作为参数
List<User> userList=jdbcTemplate.query(sql,new StudentMapper());
System.out.println(userList);
完整的实现案例: xml配置文件参考前面的教程。
java
//User.java
package data;
public class User {
private String username;
private String password;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "user{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}
//测试类
public class JdbcMain {
public static void main(String[] args) {
ApplicationContext context= new ClassPathXmlApplicationContext("jdbc.xml");
JdbcTemplate jdbcTemplate=(JdbcTemplate) context.getBean("jdbcTemplate");
String sql="select * from user";
List<User> userList=jdbcTemplate.query(sql,new StudentMapper());
System.out.println(userList);
}
public static class StudentMapper implements RowMapper<User>{
@Override
public User mapRow(ResultSet resultSet, int i) throws SQLException {
User user=new User();
user.setUsername(resultSet.getString("username"));
user.setPassword(resultSet.getString("password"));
return user;
}
}
}
RowMapper的功能是将对象的成员变量,与数据库表的数据项关联,query方法的返回结果是一个List集合
传参依然通过?
占位:
java
@Repository
public class UserDao {
@Autowired
private JdbcTemplate jdbcTemplate;
public User get(int id){
String sql="select id,name,deptid from user where id=?";
RowMapper<User> rowMapper=new BeanPropertyRowMapper<User>(User.class);
return jdbcTemplate.queryForObject(sql, rowMapper,id);
}
}
自动装配注入属性
JdbcTemplate
有IoC容器管理通过自动装配获取对象:
java
@Autowired
public JdbcTemplate jdbcTemplate;
将查询的数据通过自动装配,映射为ORM模型。
java
//dao层
public class UserMapper implements RowMapper<User> {
@Override
public User mapRow(ResultSet resultSet, int i) throws SQLException {
User user=new User();
user.setPassword(resultSet.getString("username"));
user.setPassword(resultSet.getString("password"));
return user;
}
}
//service层
public class UserService {
@Autowired
private JdbcTemplate jdbcTemplate;
public List<User> selectUsers(){
String sql="select * from user";
List<User> userList=jdbcTemplate.query(sql,new UserMapper());
return userList;
}
}
Spring Transactions
事务概念 事务是数据库操作的基本单元,逻辑上的操作要么都成功,要么都失败回溯。 事务管理 一个数据库事务是一个被视为单一的工作单元的操作序列。这些操作应该要么完整地执行,要么完全不执行。事务管理是一个重要组成部分,RDBMS 面向企业应用程序,以确保数据完整性和一致性。事务的概念可以描述为具有以下四个关键属性说成是 ACID:
-
原子性:事务应该当作一个单独单元的操作,这意味着整个序列操作要么是成功,要么是失败的。
-
一致性:这表示数据库的引用完整性的一致性,表中唯一的主键等。
-
隔离性:可能同时处理很多有相同的数据集的事务,每个事务应该与其他事务隔离,以防止数据损坏。
-
持久性:一个事务一旦完成全部操作后,这个事务的结果必须是永久性的,不能因系统故障而从数据库中删除。
一个真正的 RDBMS 数据库系统将为每个事务保证所有的四个属性。使用 SQL 发布到数据库中的事务的简单视图如下:
-
使用 begin transaction 命令开始事务。
-
使用 SQL 查询语句执行各种删除、更新或插入操作。
-
如果所有的操作都成功,则执行提交操作,否则回滚所有操作。
==编程式事务管理== 编程式事务管理方法是在源代码下管理事务。有极大地灵活性,但是它很难维护。成功逻辑和失败逻辑由程序决定。 ==声明式事务管理== 声明式事务管理方法是通过配置,而不是源代码硬编程来管理事务。可以将事务管理从事务代码中分离出来(使用AOP原理)。只使用注释或基于配置的 XML 来管理事务。逻辑由spring框架管理。
工具包: mysql-connector-java.jar和org.springframework.jdbc.jar 和 org.springframework.transaction.jar
。
xml配置
xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<!--tx是事务的缩写-->
<!--创建数据源-->
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/db1"/>
<property name="username" value="root"/>
<property name="password" value="baby5429"/>
</bean>
<!--创建交互模板-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--创建事务管理-->
<bean id="transaction" class="org.springframework.jdbc.DataSourceTransactionManager">
<!--注入要管理的数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置事务-->
<tx:advice id="...">
<tx:attributes>
<!--为方法添加事务-->
<tx:method name="" .../> //name对应方法名,其他参数就是上面表格中的
</tx:attributes>
</tx:advice>
<!--通过aop将事务添加到方法上-->
<aop:config>
<aop:pointcut ...>
<aop:advisor advice-ref="" poincut-ref=""> //这里不再是普通的切面了是事务切面。
</aop:config>
</beans>
上面是xml配置的方法式,当然也可以通过注解:
xml中开启事务注解功能:
xml
<!--创建数据源-->
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/db1"/>
<property name="username" value="root"/>
<property name="password" value="baby5429"/>
</bean>
<!--创建交互模板-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--创建事务管理-->
<bean id="transaction" class="org.springframework.jdbc.DataSourceTransactionManager">
<!--注入要管理的数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--上面配置数据库连接和对哪个数据库开启事务管理不可以省-->
<tx:annotation-driven transaction-manager="transaction"></tx:annotation-driven>
<!--transaction-manager 对应创建的事务-->
==@Transaction== 该注解在类和方法上都可以,只是包裹的范围不同。声明该类或方法为对数据库的操作以事务为基本单位,全程由spring管理。
java
@Service
@Transaction
public class Dao(){...}
==@Transaction的参数配置==
- propagation:事务传播行为,多事务方法进行调用,这一过程如何管理。
参数 | 描述 |
---|---|
REQUIRED | 如果调用的方法没有事务,就新建事务,否则就共用一个事务 |
REQUIRED_NEW | 不论调用的方法是否有事务,都会新建事务 |
SUPPORTS | 如果当前有事务在运行,调用的方法就在该事务中运行,否则它不可以运行在事务中 |
NOT_SUPPORTED | 当前方法不能运行在事务中,如果有运行的事务将其挂起 |
MANDATORY | 当前方法必须运行在事务中否则抛出异常 |
NEVER | 当前方法不能运行在事务中否则抛出异常 |
NESTED | 如果有事务在运行,调用的方法就嵌套运行否者启动新事物 |
-
ioslation:事务隔离级别 多事务之间的操作是否产生影响。主要解决脏读(事务未提交完毕被读取了),不可重复读(已读取的事务被修改了),虚读等问题。
-
timeout:超时时间 事务在一定时间内提交否者回滚。
-
readOnly:是否只读 默认值为false,管控是是否只读。
-
noRollbackFor: 设置出现那些异常不进行回滚。
-
rollbackFor 设置出现那些异常进行回滚
通过配置类实现完全注解开发: ==@EnableTransactionManagement==开启事务,注意其他注解也相应开启。
==@Bean==创建连接池和数据源以及JdbcTemplate对象和事务管理器。
==@Transaction==声明事务。
Spring MVC
Spring web MVC 框架提供了模型-视图-控制的体系结构和可以用来开发灵活、松散耦合的 web 应用程序的组件。MVC 模式导致了应用程序的不同方面(输入逻辑、业务逻辑和 UI 逻辑)的分离,同时提供了在这些元素之间的松散耦合。
-
模型封装了应用程序数据,并且通常它们由 POJO 组成。
-
视图主要用于呈现模型数据,并且通常它生成客户端的浏览器可以解释的 HTML 输出。
-
控制器主要用于处理用户请求,并且构建合适的模型并将其传递到视图呈现。
Spring的Web模型-视图-控制器(MVC)框架是围绕将请求分发到不同处理程序的DispatcherServlet类设计的,辅以可配置的处理程序映射、视图解析、本地化、时区、主题解析(theme resolution)以及对文件上传的支持。
Spring MVC框架部署
不同于非框架开发基于Servlet,每个Servlet对应一个数据处理。Spring MVC框架是围绕 DispatcherServlet 设计的,所有http请求发送到DispatcherServlet再有映射信息传递到对应的处理逻辑,避免了编写众多的Servlet。 SpringMVC 是构建在 Servlet 基础之上的,它对外提供了一个名为 DispatchServlet 的类,这个类相当于是 SpringMVC 和 Servlet API 的一个交界点。它也是 SpringMVC 当中对于请求处理的一个分发器。(它将 Servlet 传递过来的请求根据 URL 分发给对应的 Controller)
在 web.xml 文件里配置 url-pattern ,也可以起到分发请求的作用(或者@WebServlet)。 但是,如果有大量的 URL 都需要在 web.xml 进行配置时,整个 web.xml 就变成了一个灾难。 因此 Web 框架都选择避免 web.xml,在此之上构建自己的请求分发机制(通过一个sertvlet判断分发请求)。
于是在部署是只需要在web.xml中部署DispatcherServlet ,如下:
xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<servlet>
<servlet-name>FastServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup> <!--该servlet必须最先加载-->
</servlet>
<servlet-mapping>
<servlet-name>FastServlet</servlet-name>
<url-pattern>/*</url-pattern> <!--对所有路径作用-->
</servlet-mapping>
</web-app>
在修改 Servlet 为 Spring 提供的 DispatcherServlet 之外,还有两点需注意:
- 加入的 load-on-startup 参数,作用:告知 Container 在启动的时候就加载这个 Servlet(而非收到请求时才加载)
- 使用了 /* 的 URL,表示所有的请求都将由 DispatcherServlet 处理。这样也会造成静态的页面无法访问(因为静态页面没有通过DispatcherServlet管理)需要进行静态资源的放行(放行是spring管理的写在spring的配置文件中)另外有jsp页面将/*改为/对jsp放行(jsp也是一个servlet不需要DispatcherServlet管理)。
- Spring MVC 初始化时将在应用程序的 WEB-INF 目录下查找Spring MVC的配置文件,也可以将 Spring MVC 的配置文件存放在应用程序目录中的任何地方,但需要使用 servlet 的 init-param 元素加载配置文件,通过 contextConfigLocation 参数来指定 Spring MVC 配置文件的位置。
xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://JAVA.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<servlet>
<servlet-name>SpringMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>SpringMVC</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
添加工具包(mvc框架的核心是web和webmvc的jar包): 使用spring MVC的配置文件(spring-mvc.xml):
xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">
<!--开启组件扫描-->
<context:annotation-config></context:annotation-config>
<context:component-scan base-package="springdemo"/>
<!--开启mvc注解扫描-->
<mvc:annotation-driven></mvc:annotation-driven>
</beans>
Spring MVC的第一个程序
spring配置文件中对静态资源放行:
xml
<mvc:resources mapping="/html/** " location="/html/" />
css/js/图片等资源同理
spring MVC框架默认使用注解,需要先开启mvc注解扫描,相对的@WebServlet比web.xml更方便,该框架也是如此。主要的注解有:==@Controller== 将该类注册为DispatcherServlet 的实现类,并在该类中进行逻辑处理。==@RequestMapping(value = "/hello", method = RequestMethod.GET)==配置url路径和方法。==@ResponseBody==响应数据。
java
//对象的配置后写一个controller
package springdemo.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/book")
public class BookCtrl {
@RequestMapping("/add")
@ResponseBody
public String add(){
return "add";
}
@RequestMapping("/list")
public void list(){
System.out.println("list");
}
}
除了通过注解也可以在Spring mvc的配置文件中配置:
xml
<!-- LoginController控制器类,映射到"/login" -->
<bean name="/login"
class="springmvcdemo.controller.LoginController"/>
<!-- LoginController控制器类,映射到"/register" -->
<bean name="/register"
class="springmvcdemo.controller.RegisterController"/>
通过上面的配置后访问/book/add会在页面打印add,访问/book/list会在控制台打印list: ==@Controller注解== @Controller 注解用于声明某类的实例是一个控制器。
java
import org.springframework.stereotype.Controller;
@Controller
public class IndexController {
// 处理请求的方法
}
==@RequestMapping注解== 一个控制器内有多个处理请求的方法,每个方法负责不同的请求操作,而 @RequestMapping 就负责将请求映射到对应的控制器方法上(相当于一个个的HtttpServlet)。
在基于注解的控制器类中可以为每个请求编写对应的处理方法。使用 @RequestMapping 注解将请求与处理方法一 一对应即可。
@RequestMapping 注解可用于类或方法上。用于类上,表示类中的所有响应请求的方法都以该地址作为父路径。
-
value 属性 value 属性是地址映射,是 @RequestMapping 注解的默认属性,因此如果只有 value 属性时,可以省略该属性名。
-
path属性 path 属性和 value 属性都用来作为映射使用。即 @RequestMapping(value="toUser") 和 @RequestMapping(path="toUser") 都能访问 toUser() 方法。path 属性支持通配符匹配。
-
name属性 name属性相当于方法的注释,使方法更易理解。
-
method属性 method 属性用于表示该方法支持哪些 HTTP 请求。如果省略 method 属性,则说明该方法支持全部的 HTTP 请求。
-
params属性 params 属性用于指定请求中规定的参数,表示请求中必须包含 type 参数时才能执行该请求。即 http://localhost:8080/hello?type=xxx 能够正常访问 toUser() 方法。
-
header属性 header 属性表示请求中必须包含某些指定的 header 值。@RequestMapping(value = "toUser",headers = "Referer=www.xxx.com") 表示请求的 header 中必须包含了指定的"Referer"请求头,以及值为"www.xxx.com"时,才能执行该请求。
-
consumers属性 consumers 属性用于指定处理请求的提交内容类型(Content-Type),例如:application/json、text/html。如 @RequestMapping(value = "toUser",consumes = "application/json")。
-
produces属性 produces 属性用于指定返回的内容类型,返回的内容类型必须是 request 请求头(Accept)中所包含的类型。如 @RequestMapping(value = "toUser",produces = "application/json")。 除此之外,produces 属性还可以指定返回值的编码。如 @RequestMapping(value = "toUser",produces = "application/json,charset=utf-8"),表示返回 utf-8 编码。
使用 @RequestMapping 来完成映射,具体包括 4 个方面的信息项:请求 URL、请求参数、请求方法和请求头。
Spring MVC与servlet的对比
Servlet处理逻辑,HtppServlet继承了父类对方法的判断逻辑,一个路径对应一个servlet。通过判断分发到对应的doGet或其他方法。只需要编写处理逻辑即可。
java
import javax.servlet.*;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
public class MyServlet extends HttpServlet {
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//使用PrintWriter.write()方法向前台页面输出内容
resp.setContentType("text/html;charset=UTF-8");
PrintWriter writer = resp.getWriter();
writer.write("Hello World");
writer.close();
}
public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doGet(req, resp);
}
}
而DispatcherServlet是对Spring的Servlet的,所有路径都发送到此处,都该类解析路径和方法判断并分发,分发到其子类。
java
@Controller //注册为DispatcherServlet的子类
@RequestMapping("/book") //配置路径和方法默认get,value = "/book", method="get"
public class BookCtrl {
@RequestMapping("/add")
@ResponseBody
public String add(){
return "add";
}
@RequestMapping("/list")
public void list(){
System.out.println("list");
}
}
Spring处理请求流程
SpringMVC 的执行流程如下。
- 用户点击某个请求路径,发起一个 HTTP request 请求,该请求会被提交到 DispatcherServlet(前端控制器);
- 由 DispatcherServlet 请求一个或多个 HandlerMapping(处理器映射器),并返回一个执行链(HandlerExecutionChain)。
- DispatcherServlet 将执行链返回的 Handler 信息发送给 HandlerAdapter(处理器适配器);
- HandlerAdapter 根据 Handler 信息找到并执行相应的 Handler(常称为 Controller);
- Handler 执行完毕后会返回给 HandlerAdapter 一个 ModelAndView 对象(Spring MVC的底层对象,包括 Model 数据模型和 View 视图信息);
- HandlerAdapter 接收到 ModelAndView 对象后,将其返回给 DispatcherServlet ;
- DispatcherServlet 接收到 ModelAndView 对象后,会请求 ViewResolver(视图解析器)对视图进行解析;
- ViewResolver 根据 View 信息匹配到相应的视图结果,并返回给 DispatcherServlet;
- DispatcherServlet 接收到具体的 View 视图后,进行视图渲染,将 Model 中的模型数据填充到 View 视图中的 request 域,生成最终的 View(视图);
- 视图负责将结果显示到浏览器(客户端)。
SpringMVC核心组件
Spring MVC 涉及到的组件有 DispatcherServlet(前端控制器)、HandlerMapping(处理器映射器)、HandlerAdapter(处理器适配器)、Handler(处理器)、ViewResolver(视图解析器)和 View(视图)。
- DispatcherServlet DispatcherServlet 是前端控制器,从图 1 可以看出,Spring MVC 的所有请求都要经过 DispatcherServlet 来统一分发。DispatcherServlet 相当于一个转发器或中央处理器,控制整个流程的执行,对各个组件进行统一调度,以降低组件之间的耦合性,有利于组件之间的拓展。
- HandlerMapping HandlerMapping 是处理器映射器,其作用是根据请求的 URL 路径,通过注解或者 XML 配置,寻找匹配的处理器(Handler)信息。
- HandlerAdapter HandlerAdapter 是处理器适配器,其作用是根据映射器找到的处理器(Handler)信息,按照特定规则执行相关的处理器(Handler)。
- Handler Handler 是处理器,和 Java Servlet 扮演的角色一致。其作用是执行相关的请求处理逻辑,并返回相应的数据和视图信息,将其封装至 ModelAndView 对象中。
- View Resolver View Resolver 是视图解析器,其作用是进行解析操作,通过 ModelAndView 对象中的 View 信息将逻辑视图名解析成真正的视图 View(如通过一个 JSP 路径返回一个真正的 JSP 页面)。
- View View 是视图,其本身是一个接口,实现类支持不同的 View 类型(JSP、FreeMarker、Excel 等)。
Spring MVC传递参数
Spring MVC Controller 接收请求参数的方式有很多种:
- ==通过实体 Bean 接收请求参数==
实体 Bean 接收请求参数适用于 get 和 post 提交请求方式。需要注意,Bean 的属性名称必须与请求参数名称相同。
java
@RequestMapping("/login")
public String login(User user, Model model) {
if ("bianchengbang".equals(user.getName())
&& "123456".equals(user.getPwd())) {
model.addAttribute("message", "登录成功");
return "main"; // 登录成功,跳转到 main.jsp
} else {
model.addAttribute("message", "用户名或密码错误");
return "login";
}
}
- ==通过处理方法的形参接收请求参数==
通过处理方法的形参接收请求参数就是直接把表单参数写在控制器类相应方法的形参中,即形参名称与请求参数名称完全相同。
java
@RequestMapping("/login")
public String login(String name, String pwd, Model model) {
if ("bianchengbang".equals(user.getName())
&& "123456".equals(user.getPwd())) {
model.addAttribute("message", "登录成功");
return "main"; // 登录成功,跳转到 main.jsp
} else {
model.addAttribute("message", "用户名或密码错误");
return "login";
}
}
- == 通过 HttpServletRequest 接收请求参数==
java
@RequestMapping("/login")
public String login(HttpServletRequest request, Model model) {
String name = request.getParameter("name");
String pwd = request.getParameter("pwd");
if ("bianchengbang".equals(name)
&& "123456".equals(pwd)) {
model.addAttribute("message", "登录成功");
return "main"; // 登录成功,跳转到 main.jsp
} else {
model.addAttribute("message", "用户名或密码错误");
return "login"; //return相当于resopnse.getWriter.write();
}
}
- ==通过 @PathVariable 接收 URL 中的请求参数==
java
@RequestMapping("/login/{name}/{pwd}")
public String login(@PathVariable String name, @PathVariable String pwd, Model model) {
if ("bianchengbang".equals(name)
&& "123456".equals(pwd)) {
model.addAttribute("message", "登录成功");
return "main"; // 登录成功,跳转到 main.jsp
} else {
model.addAttribute("message", "用户名或密码错误");
return "login";
}
}
- ==通过 @RequestParam 接收请求参数==
在方法入参处使用 @RequestParam 注解指定其对应的请求参数。@RequestParam 有以下三个参数: value:参数名;required:是否必须,默认为 true,表示请求中必须包含对应的参数名,若不存在将抛出异常;defaultValue:参数默认值。
java
@RequestMapping("/login")
public String login(@RequestParam String name, @RequestParam String pwd, Model model) {
if ("bianchengbang".equals(name)
&& "123456".equals(pwd)) {
model.addAttribute("message", "登录成功");
return "main"; // 登录成功,跳转到 main.jsp
} else {
model.addAttribute("message", "用户名或密码错误");
return "login";
}
}
- ==通过 @ModelAttribute 接收请求参数== @ModelAttribute 注解用于将多个请求参数封装到一个实体对象中,从而简化数据绑定流程,而且自动暴露为模型数据,在视图页面展示时使用。
java
@RequestMapping("/login")
public String login(@ModelAttribute("user") User user, Model model) {
if ("bianchengbang".equals(name)
&& "123456".equals(pwd)) {
model.addAttribute("message", "登录成功");
return "main"; // 登录成功,跳转到 main.jsp
} else {
model.addAttribute("message", "用户名或密码错误");
return "login";
}
}
Spring MVC文件上传
==MultipartResolver接口==
在 Spring MVC 中实现文件上传十分容易,它为文件上传提供了直接支持,即 MultpartiResolver 接口。MultipartResolver 用于处理上传请求,将上传请求包装成可以直接获取文件的数据,从而方便操作。
MultpartiResolver 接口有以下两个实现类:
-
StandardServletMultipartResolver:使用了 Servlet 3.0 标准的上传方式。
-
CommonsMultipartResolver:使用了 Apache 的 commons-fileupload 来完成具体的上传操作。 上传文件部署
-
导入工具包 文件上传使用 Apache Commons FileUpload 组件,需要导入 commons-io-2.4.jar 和 commons-fileupload-1.2.2.jar 两个 jar 文件。
-
配置 MultipartResolver
xml
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="5000000" />
<property name="defaultEncoding" value="UTF-8" />
</bean>
defaultEncoding:请求的编码格式,默认为 ISO-8859-1,此处设置为 UTF-8(注:defaultEncoding 必须和 JSP 中的 pageEncoding 一致,以便正确读取表单的内容)。 maxUploadSize:上传文件大小上限,单位为字节。
- 编写文件上传表单页面
负责文件上传表单的编码类型必须是"multipart/form-data"类型。基于表单的文件上传需要使用 enctype 属性,并将它的值设置为 multipart/form-data,同时将表单的提交方式设置为 post。
表单的 enctype 属性指定的是表单数据的编码方式,该属性有以下 3 个值。
- application/x-www-form-urlencoded:这是默认的编码方式,它只处理表单域里的 value 属性值。
- multipart/form-data:该编码方式以二进制流的方式来处理表单数据,并将文件域指定文件的内容封装到请求参数里。
- text/plain:该编码方式只有当表单的 action 属性为"mailto:"URL 的形式时才使用,主要适用于直接通过表单发送邮件的方式。
html
<form action="${pageContext.request.contextPath }/fileupload"
method="post" enctype="multipart/form-data">
选择文件:<input type="file" name="myfile"><br>
文件描述:<input type="text" name="description"><br>
<input type="submit" value="提交">
</form>
- 创建POJO类
POJO 类中声明一个 MultipartFile 类型的属性封装被上传的文件信息
java
public class FileDomain {
private String description;
private MultipartFile myfile;
/** 省略setter和getter参数*/
}
- 编写控制器 控制器接收fileUpload文件
java
@Controller
public class FileUploadController {
// 得到一个用来记录日志的对象,这样在打印信息时能够标记打印的是哪个类的信息
private static final Log logger = LogFactory.getLog(FileUploadController.class);
@RequestMapping("getFileUpload")
public String getFileUpload() {
return "fileUpload";
}
/**
* 单文件上传
*/
@RequestMapping("/fileupload")
public String oneFileUpload(@ModelAttribute FileDomain fileDomain, HttpServletRequest request) {
/*
* 文件上传到服务器的位置"/uploadfiles",该位置是指 workspace\.metadata\.plugins\org.eclipse
* .wst.server.core\tmp0\wtpwebapps, 发布后使用
*/
String realpath = request.getServletContext().getRealPath("uploadfiles");
String fileName = fileDomain.getMyfile().getOriginalFilename();
File targetFile = new File(realpath, fileName);
if (!targetFile.exists()) {
targetFile.mkdirs();
}
// 上传
try {
fileDomain.getMyfile().transferTo(targetFile);
logger.info("成功");
} catch (Exception e) {
e.printStackTrace();
}
return "showFile";
}
}
Spring Test单元测试
新单元测试中提供@RunWith
注解,该注解是一个运行器,用来指定运行的环境。@RunWith
注解在一个类上表示是一个测试类,@Test
注释在一个方法发上表示为测试方法。
==有配置文件的单元测试==
配置文件的单元测试
有配置文件,在测试时必须要先加载配置文件,但每次用ClassPathXmlApplicationContext
加载配置文件很繁琐,org.springframework.test.context
提供了@ContextConfiguration
注解用来快速导入配置文件。
java
//导入单个文件
@ContextConfiguration("classpath:bean1.xml")
//导入多个文件
@ContextConfiguration(locations = { "classpath*:/spring1.xml", "classpath*:/spring2.xml" })
使用该注解需要导入spring-test依赖,另外还需要再运行器中指明时spring环境:
java
@RunWith(SpringRunner.class)
@RunWith(SpringJUnit4ClassRunner.class)
SpringRunner 继承了SpringJUnit4ClassRunner,没有扩展任何功能;只是名字简短而已。
测试步骤 :声明运行器并配置运行环境------@ContextConfiguration
导入配置文件初始化IoC容器------@Test
声明测试方法
java
@RunWith(SpringRunner.class)
@ContextConfiguration("classpath:bean1.xml")
public class AnoationApp {
@Autowired
private Book book;
@Autowired
private Book1 book1;
@Test
public void method1(){
System.out.println(book);
}
@Test
public void method2(){
System.out.println(book1);
}
}
配置类的单元测试
配置同样通过@ContextConfiguration
注解导入,该注解默认时xml导入需要声明属性。如上面所示locations
时声明xml,只是xml的位置;classes
声明配置类,值是配置类的位置。
java
//配置类定义
@Configuration
@ComponentScan(basePackages = "pojo")
public class ApplicationConfig {
@Bean
public Cup method1(){
return new Cup();
}
}
//注解注册bean
@Component
public class Cup1 {
@Value("blue cup")
private String name;
@Value("15")
private int width;
public void setName(String name) {
this.name = name;
}
public void setHeight(int height) {
this.width = height;
}
@Override
public String toString() {
return "Cup{" +
"name='" + name + '\'' +
", height=" + width +
'}';
}
}
//单元测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = ApplicationConfig.class)
public class App2 {
@Autowired
private Cup cup;
@Autowired
private Cup1 cup1;
@Test
public void method(){
System.out.println(cup);
System.out.println("-----------");
System.out.println(cup1);
}
}