Spring MVC 学习笔记 Spring MVC简介 Spring MVC
是Spring框架中用于Web应用快速开发的一个模块,其中的MVC
是Model-View-Controller
的缩写。它是一个广泛应用于图形化用户交互开发中的设计模式,不仅常见于Web开发,也广泛应用于如Swing
和JavaFX
等桌面开发。作为当今业界最主流的Web开发框架,Spring MVC已经成为当前最热门的开发技能。
IDEA环境配置 https://www.cnblogs.com/zuti666/p/13987082.html
核心概念
DI(Dependency Injection)依赖注入
当需要使用service对象的时候,同样需要用到dao对象,所以在IoC容器中做了一个依赖绑定。
在容器中建立bean与bean之间的依赖关系的整个过程,成为依赖注入
目标: 充分解耦
使用IoC容器管理 bean(IoC)
在IoC容器内将有依赖关系的bean进行关系绑定(DI)
最终效果
使用对象时不仅可以直接从IoC容器中获取,并且获取到的bean已经绑定了所有的依赖关系
IoC入门案例 IoC入门案例思路分析
管理什么?(Service与Dao)
如何将被管理的对象告知IoC容器?(配置)
被管理的对象交给IoC容器,如何获取到IoC容器?(接口)
IoC容器得到后,如何从容器中获取bean?(接口方法)
使用Spring导入哪些坐标?(pom.xml)
案例1 BookDaoImpl
1 2 3 4 5 public class BookDaoImpl implements BookDao { public void save () { System.out.println("book dao save ..." ); } }
BookServiceImpl
1 2 3 4 5 6 7 8 9 public class BookServiceImpl implements BookService { private BookDao bookDao = new BookDaoImpl(); public void save () { System.out.println("book service save ..." ); bookDao.save(); } }
BookDao接口
1 2 3 public interface BookDao { void save () ; }
BookService接口
1 2 3 public interface BookService { void save () ; }
App1
1 2 3 4 5 6 7 8 9 10 public class app { public static void main (String[] args) { BookService bookService = new BookServiceImpl(); bookService.save(); } }
案例2 配置Spring
1 2 3 4 5 6 7 8 9 10 11 12 13 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns ="http://www.springframework.org/schema/beans" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd" > <bean id ="bookDao" class ="com.le1a.dao.impl.BookDaoImpl" /> <bean id ="bookService" class ="com.le1a.service.impl.BookServiceImpl" /> </beans >
app2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package com.le1a;import com.le1a.dao.BookDao;import com.le1a.service.BookService;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;public class app2 { public static void main (String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml" ); BookService bookService = (BookService) ctx.getBean("bookService" ); bookService.save(); } }
总结 1、导入Spring坐标 1 2 3 4 5 <dependency > <groupId > org.springframework</groupId > <artifactId > spring-context</artifactId > <version > ${spring.version}</version > </dependency >
2、定义Spring管理的类(接口) 1 2 3 public interface BookService { public void save () ; }
1 2 3 4 5 6 7 8 public class BookServiceImpl implements BookService { private BookDao bookDao = new BookDaoImpl(); public void save () { bookDao.save(); } }
3、创建Spring配置文件,配置对应类作为Spring管理的bean 1 2 3 4 5 6 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" xsi:schemaLocation ="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd" > <bean id ="bookService" class ="com.le1a.service.impl.BookServiceImpl" /> </beans >
注意!bean定义时id属性在同一个上下文中不能重复
4、初始化IoC容器(Spring核心容器/Spring容器),通过容器获取bean 1 2 3 4 5 6 7 8 9 public class app2 { public static void main (String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml" ); BookService bookService = (BookService) ctx.getBean("bookService" ); bookService.save(); } }
DI入门案例 DI入门案例思路分析
基于IoC管理bean
service中使用new形式创建Dao对象是否保留?(否)
Service中需要的Dao对象如何进入到Service中?(提供方法)
Service与Dao间的关系如何描述?(配置)
案例实现 修改BookServiceImpl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class BookServiceImpl implements BookService { private BookDao bookDao; public void save () { System.out.println("book service save ..." ); bookDao.save(); } public void setBookDao (BookDao bookDao) { this .bookDao = bookDao; } }
Spring配置文件中添加server与dao的关系
1 2 3 4 5 6 7 8 9 <bean id ="bookDao" class ="com.le1a.dao.impl.BookDaoImpl" /> <bean id ="bookService" class ="com.le1a.service.impl.BookServiceImpl" > <property name ="bookDao" ref ="bookDao" /> </bean >
app2运行结果
如果注释掉property
标签,则会抛出NullPointerException
异常
总结 1、删除使用new的形式创建对象的代码 1 2 3 4 5 6 7 8 9 10 public class BookServiceImpl implements BookService { private BookDao bookDao; public void save () { bookDao.save(); } }
2、提供依赖对象对应的setter方法 1 2 3 4 5 6 7 8 9 10 11 12 public class BookServiceImpl implements BookService { private BookDao bookDao; public void save () { bookDao.save(); } public void setBookDao (BookDao bookDao) { this .bookDao = bookDao; } }
3、配置service与dao之间的关系 1 2 3 4 5 6 7 8 9 10 11 12 <?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:util ="http://www.springframework.org/schema/util" xsi:schemaLocation ="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd <bean id=" bookDao " class ="com.le1a.dao.impl.BookDaoImpl" /> <bean id ="bookService" class ="com.le1a.service.impl.BookServiceImpl" > <property name ="bookDao" ref ="bookDao" /> </bean > </beans >
ref的bookDao指的是当前容器对应的bean的名称(也就是这里的id),name的bookDao是现在的属性的名称。
Bean基础配置
Bean别名配置
修改Spring配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns ="http://www.springframework.org/schema/beans" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd " > <bean id ="bookDao" name ="dao" class ="com.le1a.dao.impl.BookDaoImpl" /> <bean id ="bookService" name ="service service2" class ="com.le1a.service.impl.BookServiceImpl" > <property name ="bookDao" ref ="dao" /> </bean > </beans >
但是,当如果使用了未被定义的别名时,将会抛出异常,例如这里使用了service4,但是我们并没有定义这个别名
Bean作用范围
1 2 3 4 5 6 7 8 9 10 11 public class AppForScope { public static void main (String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml" ); BookDao bookDao1 = (BookDao) ctx.getBean("bookDao" ); BookDao bookDao2 = (BookDao) ctx.getBean("bookDao" ); System.out.println(bookDao1); System.out.println(bookDao2); } }
通过运行结果可以发现,Spring给我们创建的是一个单例Bean,两个bookDao是同一个地址
如果我们想要创建非单例的bean呢?
通过配置来完成,在Spring的配置文件中的bookDao配置中,再添加一个属性scope
,一共有两个属性,分别是singleton
和prototype
,默认的是singleton
,如果换成prototype
,Spring创建的Bean就会是两个不同的地址
为什么bean默认为单例呢?
对于Spring来说,它帮我们管理的bean要放入IoC容器中,如果创建出的bean不是单例的,那么bean的数量将会是无数个,用一次造一个,用一次造一个,所以使用单例bean会提高容器的效率。
适合交给容器进行管理的bean
不适合交给容器进行管理的对象
Bean实例化 构造方法(常用) bean实例化
bean本质上就是对象,创建bean使用构造方法完成
配置文件
AppForInstanceBook
1 2 3 4 5 6 7 8 public class AppForInstanceBook { public static void main (String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml" ); BookDao bookDao =(BookDao) ctx.getBean("bookDao" ); bookDao.save(); } }
BookDaoImpl
1 2 3 4 5 6 7 8 public class BookDaoImpl implements BookDao { public BookDaoImpl () { System.out.println("book dao constructor is running ..." ); } public void save () { System.out.println("book dao save ..." ); } }
说明了造对象都会调用无参构造方法来构造,无论构造方法是public还是private属性。
静态工厂(了解) 配置文件
1 2 <bean id ="orderDao" class ="com.le1a.factory.OrderDaoFactory" factory-method ="getOrderDao" />
静态工厂
1 2 3 4 5 public class OrderDaoFactory { public static OrderDao getOrderDao () { return new OrderDaoImpl(); } }
AppForInstanceOrder
1 2 3 4 5 6 7 8 9 10 11 12 public class AppForInstanceOrder { public static void main (String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml" ); OrderDao orderDao = (OrderDao) ctx.getBean("orderDao" ); orderDao.save(); } }
实例工厂(了解)与FactoryBean(实用) 配置文件
1 2 3 <bean id ="userFactory" class ="com.le1a.factory.UserDaoFactory" /> <bean id ="userDao" factory-bean ="userFactory" factory-method ="getUserDao" />
实例工厂
1 2 3 4 5 public class UserDaoFactory { public UserDao getUserDao () { return new UserDaoImpl(); } }
AppForInstanceUser
1 2 3 4 5 6 7 8 9 10 11 12 13 public class AppForInstanceUser { public static void main (String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml" ); UserDao userDao = (UserDao) ctx.getBean("userDao" ); userDao.save(); } }
这个方法名是不固定的每次都需要配置,所以改良一下,使用第四种方法。
UserDaoFactoryBean ⭐⭐⭐⭐⭐
1 2 3 4 5 6 7 8 9 10 11 12 public class UserDaoFactoryBean implements FactoryBean <UserDao > { @Override public UserDao getObject () throws Exception { return new UserDaoImpl(); } @Override public Class<?> getObjectType() { return UserDao.class; } }
配置文件
1 2 <bean id ="userDao" class ="com.le1a.factory.UserDaoFactoryBean" />
AppForInstanceUser
1 2 3 4 5 6 7 8 9 public class AppForInstanceUser { public static void main (String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml" ); UserDao userDao = (UserDao) ctx.getBean("userDao" ); userDao.save(); } }
可以发现通过FactoryBean
实例化的Bean也是单例的
如果要实现非单例的话,在UserDaoFactoryBean
中新增一个方法
1 2 3 4 @Override public boolean isSingleton () { return false ; }
Bean的生命周期
生命周期: 从创建到消亡的完整过程
bean生命周期: bean从创建到销毁的整体过程
bean生命周期的控制: 在bean创建后到销毁前做一些事情
Bean的生命周期控制(一):使用配置 BookDaoImpl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class BookDaoImpl implements BookDao { @Override public void save () { System.out.println("book dao save ..." ); } public void init () { System.out.println("init ..." ); } public void destory () { System.out.println("destory ..." ); } }
配置文件
必须得配置了这两个方法,才会被识别为初始化方法和销毁方法
1 2 3 4 5 6 7 8 9 10 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns ="http://www.springframework.org/schema/beans" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd" > <bean id ="bookDao" class ="com.le1a.dao.impl.BookDaoImpl" init-method ="init" destroy-method ="destory" /> <bean id ="bookService" class ="com.le1a.service.impl.BookServiceImpl" > <property name ="bookDao" ref ="bookDao" /> </bean > </beans >
AppForLifeCycle
1 2 3 4 5 6 7 public class AppForLifeCycle { public static void main (String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml" ); BookDao bookDao = (BookDao) ctx.getBean("bookDao" ); bookDao.save(); } }
BookServiceImpl
1 2 3 4 5 6 7 8 9 10 11 12 public class BookServiceImpl implements BookService { private BookDao bookDao; @Override public void save () { System.out.println("book service ..." ); } public void setBookDao (BookDao bookDao) { this .bookDao = bookDao; } }
在bean中实现初始化和销毁的方法,并且在配置文件中配置,然后就可以实现bean生命周期的控制。
执行AppForLifeCycle
发现初始化方法执行了,但销毁方法并没有被执行
原因是因为现在这个AppForLifeCycle
程序是运行在java虚拟机中的,虚拟机启动了,IoC容器加载配置也启动了,然后把Bean初始化了并且拿到了Bean,下一步程序运行完了,虚拟机就退出了,并没有给Bean销毁的机会。解决办法有如下两种:
Bean销毁方法(一): 使用close()
方法在虚拟机退出之前把容器给关闭
但是发现ApplicationContext
接口并没有实现close()
方法,在ClassPathXmlApplicationContext
接口的父类AbstractApplicationContext.class
中实现了close()
方法,所以只需要把ApplicationContext
改为ClassPathXmlApplicationContext
就可以了
如果想要在程序中正常的关闭容器,使用ClassPathXmlApplicationContext
类就可以关了
Bean销毁方法(二): 使用registerShutdownHook()
设置关闭钩子,表示在关闭虚拟机之前,先把容器关掉!
两种方式的区别:
close()方法相对暴力,如果close()放在前面,则会导致异常;而registerShutdownHook()
则可以放在任意位置
Bean的生命周期控制(二): 接口控制 如果bean的初始化和销操作都需要在配置文件中声明对应的方法名的话,太过于复杂。所以来看看Spring的方法来初始化
BookServiceImpl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class BookServiceImpl implements BookService , InitializingBean , DisposableBean { private BookDao bookDao; @Override public void save () { System.out.println("book service ..." ); } public void setBookDao (BookDao bookDao) { this .bookDao = bookDao; } @Override public void destroy () throws Exception { System.out.println("service destroy" ); } @Override public void afterPropertiesSet () throws Exception { System.out.println("service init" ); } }
Bean同样可以继承这两个接口,并实现初始化和销毁的方法,这样就可以不用在spring配置文件中添加init-method="init" destroy-method="destroy"
BookDaoImpl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class BookDaoImpl implements BookDao , InitializingBean , DisposableBean { @Override public void save () { System.out.println("book dao save ..." ); } @Override public void destroy () throws Exception { System.out.println("bean destroy ..." ); } @Override public void afterPropertiesSet () throws Exception { System.out.println("bean init ...." ); }
总结:
初始化容器
创建对象(内存分配)
执行构造方法
执行属性注入(set操作)
执行bean初始化方法
使用bean
执行业务操作
关闭/销毁容器
执行bean销毁方法
依赖注入方式
思考:向一个类中传递数据的方式有几种?
普通方法(set方法)
构造方法
思考:依赖注入描述了容器中建立bean与bean之间依赖关系的过程,如果bean运行需要的是数字或字符串呢?
引用类型
简单类型(基本数据类型和String)
依赖注入方式
setter注入——引用类型
在bean中定义引用类型属性并提供可访问的set方法
1 2 3 4 5 public class BookServiceImpl implements BookService , InitializingBean , DisposableBean { private BookDao bookDao; public void setBookDao (BookDao bookDao) { this .bookDao = bookDao; }
配置中使用property
标签ref
属性注入引用类型对象
1 2 3 4 5 <bean id ="bookDao" class ="com.le1a.dao.impl.BookDaoImpl" /> <bean id ="bookService" class ="com.le1a.service.impl.BookServiceImpl" > <property name ="bookDao" ref ="bookDao" /> </bean >
如果有多个bean的时候,同样的配置就好了
AppForDISet
1 2 3 4 5 6 7 8 public class AppForDISet { public static void main (String[] args) { ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml" ); BookService bookService = (BookService) ctx.getBean("bookService" ); bookService.save(); } }
配置文件
1 2 3 4 5 6 7 8 <bean id ="bookDao" class ="com.le1a.dao.impl.BookDaoImpl" /> <bean id ="userDao" class ="com.le1a.dao.impl.UserDaoImpl" /> <bean id ="bookService" class ="com.le1a.service.impl.BookServiceImpl" > <property name ="bookDao" ref ="bookDao" /> <property name ="userDao" ref ="userDao" /> </bean >
BookServiceImpl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class BookServiceImpl implements BookService { private BookDao bookDao; private UserDao userDao; public void setUserDao (UserDao userDao) { this .userDao = userDao; } public void setBookDao (BookDao bookDao) { this .bookDao = bookDao; } @Override public void save () { System.out.println("book service ..." ); bookDao.save(); userDao.save(); }
setter注入——简单类型 简单类型就直接在Bean里面配置set方法,并且在配置文件中配置bean的property
标签即可
运行结果如图
构造器注入——引用类型
构造器注入的话就把之前BookServiceImpl
的set
方法改为构造方法就好了
1 2 3 4 public BookServiceImpl (BookDao bookDao, UserDao userDao) { this .bookDao = bookDao; this .userDao = userDao; }
1 2 3 4 5 6 7 8 <bean id ="bookDao" class ="com.le1a.dao.impl.BookDaoImpl" /> <bean id ="userDao" class ="com.le1a.dao.impl.UserDaoImpl" /> <bean id ="bookService" class ="com.le1a.service.impl.BookServiceImpl" > <constructor-arg name ="bookDao" ref ="bookDao" /> <constructor-arg name ="userDao" ref ="userDao" /> </bean >
构造器注入——简单类型
BookDaoImpl 在Bean中使用构造器
1 2 3 4 5 6 7 8 9 10 11 12 13 public class BookDaoImpl implements BookDao { private int connectionNum; private String databaseName; public BookDaoImpl (int connectionNum, String databaseName) { this .connectionNum = connectionNum; this .databaseName = databaseName; } public void save () { System.out.println("book dao save ..." +databaseName+"," +connectionNum); } }
配置文件 中配置constructor-arg
标签
1 2 3 4 <bean id ="bookDao" class ="com.le1a.dao.impl.BookDaoImpl" > <constructor-arg name ="databaseName" value ="mysql" /> <constructor-arg name ="connectionNum" value ="666" /> </bean >
高耦合解决办法
这里会出现一个耦合度高的问题,因为配置文件中的name属性是指向的set方法/构造器
的形参,如果形参发生改变,那么这里的name属性同样需要改变,比较麻烦。
方法一 解决方案就是 不写name属性,写type类型,通过type类型来限制赋值,通过实验发现确实可以
但是如果两个参数是同样的类型,就会按照顺序赋值,这显然不是我们想要的
方法二 使用参数位置来解决参数匹配问题
1 2 3 4 5 6 7 8 9 10 11 12 <bean id ="bookDao" class ="com.le1a.dao.impl.BookDaoImpl" > <constructor-arg index ="0" value ="666" /> <constructor-arg index ="1" value ="mysql" /> </bean > <bean id ="userDao" class ="com.le1a.dao.impl.UserDaoImpl" /> <bean id ="bookService" class ="com.le1a.service.impl.BookServiceImpl" > <constructor-arg name ="bookDao" ref ="bookDao" /> <constructor-arg name ="userDao" ref ="userDao" /> </bean >
依赖注入方式选择
强制依赖使用构造器,使用setter注入有概率不进行注入导致null对象出现
可选依赖使用setter注入进行,灵活性强
Spring框架倡导使用构造器,第三方框架内部大多数采用构造器注入的形式对数据初始化,相对严谨
如果有必要可以两者同时使用,使用构造器注入完成强制依赖的注入,使用setter注入完成可选依赖的注入
实际开发过程中还要根据实际情况分析,如果受控对象没有提供setter方法就必须使用构造器注入
自己开发的模块推荐使用setter注入
Spring自动装配