目录
前言
SSM框架
一、Spring
1.1 Spring介绍
1.2 Spring优点
1.3 Spring项目
1.4 Spring模块
二、 SpringMVC重构登录注册业务
2.1导入Spring和SpringMVC依赖
2.2 配置web.xml
2.3 重构登录的Controller
2.4 重构注册的Controller
三、MVC项目结构
3.1标准的MVC项目结构:
3.2 项目结构构建
四、Spring框架IOC
4.1 Spring容器管理对象
4.2依赖注入方式
4.2.1构造器注入(配置注入)
4.2.2 Set注入
4.2.3 自动注入(注解版)
4.3注册后台逻辑代码
五、MyBatis框架
5.1 外部配置文件+c3p0+QueryRunner
5.2 MyBatis基础知识
5.2 配置MyBatis
5.2.1在POM.Xml文件中导入MyBatis依赖
5.2.2在Spring容器中配置数据源和属性
5.2.3创建MyBatis的Mapper文件
5.2.4创建MyBatis会话工厂和扫描器
前言
servlet的一大缺点就是只能识别doGet 和doPost等7种请求,具体业务上的请求无法识别,通过在前端加一个隐藏组件(表单提交的时候传给后台参数),给后台传当前需要调用什么方法,或者通过url给后端传一个参数action或者method来告诉servlet调用什么方法,以此来区分相同业务块的不同请求;
Servlet的方式属于弱规范,由于程序员的命名错误或者其他原因就会导致404找不到的错误,能不能直接通过路径直接就告诉web服务器我们要处理请求的具体方法是哪个servlet的哪个方法,因为url路径是存在层级关系的,对应于类和方法的层级关系
于是市面上就出现了springMVC这个框架,对servlet进行升级
SSM框架

以前是SSH ->spring+struts+hibernate
SpringMVC和Spring是无缝连接的,一家公司的产品,性能更好,因此SpringMVC替代了struts
SpringMVC解决了前言中所说的sevlet的痛点问题,早年用的是配置版,现在是用的注解版(通过java的注解方式屏蔽掉了很多共同的代码逻辑)
MyBatis:进行OR映射
一、Spring
1.1 Spring介绍
Spring是一个基于控制反转Ioc和面向切面编程Aop的轻量级开源框架!它是由一个叫做Rod Johnson的音乐学博士在2002年提出并创建的,他提出了著名的轮子理论,就是:不要重复发明轮子。Spring之所以叫做Spring,就是它期望给软件行业带来一个春天!让我们的开发变得更加简单更加快速。它使得现有的技术变的更加容易使用,它本身是一个大杂烩,整合了现有的技术框架。
具体描述:
轻量级:Spring 是非侵入性的 – 基于 Spring 开发的应用中的对象可以不依赖于Spring 的 API
依赖注入(DI—-dependency injection、IOC)
面向切面编程(AOP—aspect oriented programming)
容器:Spring是一个容器,因为它包含并且管理应用对象的生命周期
框架:Spring实现了使用简单的组件配置组合成一个复杂的应用,在Spring中可以使用XML和Java注解组合这些对象
一站式:在IOC和AOP的基础上可以整合各种企业应用的开源框架和优秀的第三方类库(实际上Spring自身也提供了展现层的SpringMVC和持久层的Spring JDBC)
1.2Spring优点
低侵入式设计,代码污染极低。
独立于各种应用服务器,基于Spring框架的应用,可以真正实现Write Once,Run Anywhere的承诺。
Spring的DI机制降低了业务对象替换的复杂性,提高了组件之间的解耦。
Spring的AOP支持允许将一些通用任务如安全、事务、日志等进行集中式管理,从而提供了更好的复用,横向地拦截数据,对数据进行过滤。
Spring的ORM和DAO提供了与第三方持久层框架的良好整合,并简化了底层的数据库访问。
Spring并不强制应用完全依赖于Spring,开发者可自由选用Spring框架的部分或全部。
1.3Spring项目
Springboot
对三大框架的高度高度整合
Springcloud
1.4Spring模块
Spring框架由7个模块组成,核心模块定义了创建、配置和管理bean的方式
Spring架构
最重要的Spring业务处理逻辑
DisparcherServlet拦截客户端所有的请求,前端不需要再访问具体的.jsp文件,直接访问后台服务,DisparcherServlet会主动去寻找对应的Controller(类似servlet),最后响应还给DisparcherServlet后进行视图映射,判断返回给前端的数据是静态数据还是页面
我们要处理的部分只有:(1)前端业务请求 (2)后台的Controller,处理完以后封装给ModelAndView就行了,其他的就不用管了
二、 SpringMVC重构登录注册业务
2.1导入Spring和SpringMVC依赖
在pom.xml中引入Spring相关的dependency,让Maven下载相应的jar包
<?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.ysu</groupId> <artifactId>demo4</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <name>demo4 Maven Webapp</name> <!-- FIXME change it to the project's website --> <url>http://www.example.com</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.7</maven.compiler.source> <maven.compiler.target>1.7</maven.compiler.target> <spring-vison>5.1.2.RELEASE</spring-vison> </properties> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>${spring-vison}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>${spring-vison}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>${spring-vison}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring-vison}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>${spring-vison}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${spring-vison}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-expression</artifactId> <version>${spring-vison}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-instrument</artifactId> <version>${spring-vison}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${spring-vison}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jms</artifactId> <version>${spring-vison}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-messaging</artifactId> <version>${spring-vison}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>${spring-vison}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-oxm</artifactId> <version>${spring-vison}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${spring-vison}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>${spring-vison}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring-vison}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-websocket</artifactId> <version>${spring-vison}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${spring-vison}</version> </dependency> <dependency> <groupId>commons-dbutils</groupId> <artifactId>commons-dbutils</artifactId> <version>1.7</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version> <scope>test</scope> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.7</version> </dependency> </dependencies> <build> <finalName>demo4</finalName> <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) --> <plugins> <plugin> <artifactId>maven-clean-plugin</artifactId> <version>3.1.0</version> </plugin> <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging --> <plugin> <artifactId>maven-resources-plugin</artifactId> <version>3.0.2</version> </plugin> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.0</version> </plugin> <plugin> <artifactId>maven-surefire-plugin</artifactId> <version>2.22.1</version> </plugin> <plugin> <artifactId>maven-war-plugin</artifactId> <version>3.2.2</version> </plugin> <plugin> <artifactId>maven-install-plugin</artifactId> <version>2.5.2</version> </plugin> <plugin> <artifactId>maven-deploy-plugin</artifactId> <version>2.8.2</version> </plugin> </plugins> </pluginManagement> </build> </project> |
引入的比较多,只有一部分是前端会用到的,其中Spring是主流框架,接下来的项目中会用到,Spring全家桶相互之间是会有依赖的,所以就一次性全部引入了
导入依赖后表示项目具有使用Spring的能力了
2.2 配置web.xml
Web.xml是tomcat主动加载的配置文件,要使用DisparcherServlet肯定要让tomcat进行加载,web.xml内的结点声明是有先后顺序的,重要的需要先加载
原来的使用自己写的servlet
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://java.sun.com/xml/ns/javaee" version="2.5" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> <display-name>Archetype Created Web Application</display-name> <servlet> <servlet-name>loginServlet</servlet-name> <servlet-class>com.ysu.controller.LoginController</servlet-class> </servlet> <servlet-mapping> <servlet-name>loginServlet</servlet-name> <url-pattern>/login</url-pattern> </servlet-mapping> <servlet> <servlet-name>registerServlet</servlet-name> <servlet-class>com.ysu.controller.RegisterController</servlet-class> </servlet> <servlet-mapping> <servlet-name>registerServlet</servlet-name> <url-pattern>/register</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>login.html</welcome-file> <welcome-file>index.jsp</welcome-file> </welcome-file-list> </web-app> |
现在我们不需要用自己的servlet了,直接用DisparcherServlet
Spring和SpringMVC的配置文件.xml需要放在项目的资源文件resources中,在web.xml中告诉web服务器去哪里加载这个配置文件
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://java.sun.com/xml/ns/javaee" version="2.5" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> <display-name>Archetype Created Web Application</display-name> <!-- 优先配置Spring框架,初始化上下文,然后才能加载配置文件--> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- 加载Spring的配置文件--> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath*:spring\spring-context.xml</param-value> </context-param> <servlet> <servlet-name>spring-mvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath*:spring\spring-mvc-context.xml</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>spring-mvc</servlet-name> <!-- 框架拦截所有的请求(界面或者静态请求),然后再进行分流--> <url-pattern>/</url-pattern> </servlet-mapping> <filter> <filter-name>RequestContextFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>RequestContextFilter</filter-name> <!-- 拦截所有的请求,设置编码格式为UTF-8--> <url-pattern>/</url-pattern> </filter-mapping> <welcome-file-list> <welcome-file>login.html</welcome-file> <welcome-file>index.jsp</welcome-file> </welcome-file-list> </web-app> |
Spring的配置文件:spring-context.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.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!--打开包扫描器,扫描com.ysu中哪些类有@Controller或者@RestController,将这些类作为控制器实例化到IOC(spring容器)中--> <context:component-scan base-package="com.ysu"/> <!--开启注解驱动,才能使@Controller等正常使用--> <mvc:annotation-driven/> </beans> |
SpringMVC的配置:Spring-mvc-context.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.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!--剔除前段发送的请求中的静态资源,返回给web容器进行处理--> <mvc:default-servlet-handler/> <!-- 配置拦截处理请求函数的返回字符串转化为.jsp页面--> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/"/> <property name="suffix" value=".jsp"/> </bean> </beans> |
Spring-context.xml配置文件是可以派生出Spring-mvc-context.xml配置的,如果不想区分Spring配置和Spring前端业务,可以把这俩配置放在一起,都放在Spring-context.xml文件中,所以后面的Mybatis的配置就是直接放在Spring的配置文件中的
2.3 重构登录的Controller
原本的servlet处理逻辑:
package com.ysu.controller; //import com.sun.deploy.util.StringUtils; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import org.apache.commons.lang3.StringUtils; public class LoginController extends HttpServlet { @Override //加上这个表示参数写多了或者写少了不会出错 public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { //接受浏览器的GET请求 this.doPost(request,response); } @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException { request.setCharacterEncoding("UTF-8"); response.setCharacterEncoding("UTF-8"); //接收前端提交的数据 String username=request.getParameter("username"); String password=request.getParameter("password"); //后台常规判空判null操作 //这种代码如果前端的参数很多就会很长,而且这种代码会经常写,由apache commons来解决,极大地简化了代码 //if(null==username || username.equals("") || null==password ||password.equals("")) if(StringUtils.isEmpty(username)||StringUtils.isEmpty(password)) { //一般后台是返回状态,不要一会儿返回字符串,一会儿返回状态 printView(response,"用户名或密码为空"); } else { printView(response,"登录成功,等待跳转..."); } // System.out.println("用户名:"+username+"密码:"+password); //???表示第一次转码错误,菱形什么的奇怪符合是转码的转码的错误,乱码的乱码 } public void printView(HttpServletResponse response, String msg) throws IOException { response.setContentType("text/html;charset=UTF-8"); PrintWriter out=response.getWriter(); out.println("<html>"); out.println(" <head><title>登录</title></head>"); out.println(" <body>"); out.println(" <h1>"+msg+"<h1>"); out.println(" </body>"); out.println("</html>"); //刷新输出流,避免数据没有发送给浏览器 out.flush(); out.close(); } } |
Spring框架的登录界面Controller:
package com.ysu.controller; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller //猴头Controller,创建当前类的对象并存在IOC(Spring容器中),如果不加controller表示这就是一个普通的java类 //@RequestMapping("/login") //类级别的拦截,完成servlet-mapping的工作,表示将拦截什么请求 //但是只有类级别的拦截还是只能对应一个servlet,不能拦截具体的方法实现不同业务处理 //可以没有类拦截,直接方法级拦截 public class LoginController extends HttpServlet { //@RequestMapping("/a") //方法级别的拦截,login/a映射到此 @RequestMapping("/login") //如果返回String类型就默认是对应于.jsp页面 public String doLogin(HttpServletRequest request, HttpServletResponse response) throws IOException { request.setCharacterEncoding("UTF-8"); response.setCharacterEncoding("UTF-8"); //接收前端提交的数据 String username=request.getParameter("username"); String password=request.getParameter("password"); String message =""; //后台常规判空判null操作 //这种代码如果前端的参数很多就会很长,而且这种代码会经常写,由apache commons来解决,极大地简化了代码 //if(null==username || username.equals("") || null==password ||password.equals("")) if(StringUtils.isEmpty(username)||StringUtils.isEmpty(password)) { //一般后台是返回状态,不要一会儿返回字符串,一会儿返回状态 message="用户名或密码为空"; } else { message="登录成功,等待跳转..."; } //给request设置属性,返回的界面是请求转发过去的 request.setAttribute("mag",message); //return new ModelAndView("index"); return "index"; } } |
2.4 重构注册的Controller
原来的servlet处理注册请求
package com.ysu.controller; import com.ysu.domain.User; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import javax.servlet.ServletException; 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 RegisterController extends HttpServlet { private static final String REGISTER_METHOD ="register"; //重名方法 private static final String IS_SAME_NAME_METHOD="isSameName"; @Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { doPost(request,response); } @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { String method=request.getParameter("method"); PrintWriter out = response.getWriter(); //进行注册校验 if((StringUtils.equals(REGISTER_METHOD,method))) { boolean registerState=doRegister(request); request.setCharacterEncoding("UTF-8"); response.setCharacterEncoding("UTF-8"); //站内请求转发 if(registerState) request.getRequestDispatcher("/login.jsp").forward(request,response); } //进行重名校验 else if(StringUtils.equals(IS_SAME_NAME_METHOD,method)) { String username=request.getParameter("username"); boolean isSameName=isSameName(username); out.print(isSameName); out.flush(); } } private boolean doRegister(HttpServletRequest request) { User newUser=new User(); //特殊处理newUser为null的情况,如果前端没有传过来参数,数据库直接录入空串信息 newUser.setUsername(ObjectUtils.identityToString(request.getParameter("username"))); newUser.setPassword(ObjectUtils.identityToString(request.getParameter("password"))); newUser.setEmail(ObjectUtils.identityToString(request.getParameter("email"))); newUser.setTel(ObjectUtils.identityToString(request.getParameter("tel"))); //用户信息入库 return true; } private boolean isSameName(String username) { //这样写防止出现空指针的调用 return "admin".equals(username); } } |
Spring重构后:
前端表单提交:action=”register/doRegister”
Ajax异步请求路径:"register/doVerifySameName”
Spring的返回值是请求转发的方式跳转到页面,项目的上下文路径不变,可以给request设置属性,以此来进行传值
package com.ysu.controller; import com.ysu.domain.User; import org.apache.commons.lang3.ObjectUtils; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.ModelAndView; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; //@Controller //定义了返回值只能是页面,不能是数据,但是可以用ModelAndView携带数据,比较麻烦,也可以再spring-mvc-context.xml文件中进行配置,配置返回的字符串都转化为界面 @RestController //这个就可以返回页面或者数据 @RequestMapping("/register") public class RegisterController { @RequestMapping("/doRegister") //每次都返回一个ModelAndView,返回一个jsp页面 public ModelAndView doRegister(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { request.setCharacterEncoding("UTF-8"); response.setCharacterEncoding("UTF-8"); PrintWriter out = response.getWriter(); //进行注册校验 User newUser=new User(); //特殊处理newUser为null的情况,如果前端没有传过来参数,数据库直接录入空串信息 newUser.setUsername(ObjectUtils.identityToString(request.getParameter("username"))); newUser.setPassword(ObjectUtils.identityToString(request.getParameter("password"))); newUser.setEmail(ObjectUtils.identityToString(request.getParameter("email"))); newUser.setTel(ObjectUtils.identityToString(request.getParameter("tel"))); //用户信息入库 //http://localhost:8080/demo4_war_exploded/register/login.jsp因为是请求转发,会跳转到这个路径(错误路径) return new ModelAndView("login"); //return "login"; //跳转后资源是不存在的,只有基本的html界面,静态资源因为是相对路径,加载不出js、css等资源, //因此要在前端页面中设置所有的相对路径是动态获取上下文路径 } @RequestMapping("/doSameName") @ResponseBody //声明当前接口可以返回数据 private String doSameName(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { //objeftUtils防止得到的数据为空 String username=ObjectUtils.toString(request.getParameter("username")); // String username=request.getParameter("username"); //如果没有加ResponseBody,往前端传回的是true.jsp或者false.jsp,默认是去找一个页面 return "admin".equals(username)+""; } } |
三、MVC项目结构
3.1标准的MVC项目结构:
一定要注意不要直接在Controller层注入Services层,乱了
3.2 项目结构构建
Controller层和其以下的层都是逐层调用的,并且采用面向接口编程,实例化对象后调用相应的处理方法
代码撰写中,先写类之间耦合的伪代码,然后直接alt+enter利用idea直接生成相应的接口和类,每层示例化的对象都是接口引用指向相应的实例化类对象
Controller层注入Model依赖:RegisterController类中
//I开头表示这个是个接口(innerface),在当前类中只会有一个接口,一个Model,所以直接取名为model private IRegisterModel model=new RegisterModelImpl(); //创建一个实现IRegisterModel接口的类的对象 |
Model层注入Service依赖:RegisterModelImpl类中
private IRegisterService service =new RegisterServiceImpl(); |
Service层注入Dao依赖:RegisterServiceImpl类中
private IRegisterDao dao =new RegisterDaoImpl(); |
最终的项目结构:
采用直接在类中new对象的缺点:
1.正确的方式应该是在构造器中进行实例化,比较麻烦
2.主动创建导致强耦合,new了一个对象就要维护该对象,但是当前类只想用它的方法,不想去维护该对象
四、Spring框架IOC
解决上面的痛点问题,Spring框架提供一个平台来创建、维护和管理项目中的耦合对象,解除了类之间的耦合性,实现了控制翻转、依赖注入的功能。
4.1 Spring容器管理对象
管理类之间具有耦合性的所有对象,包括最顶层的Controller对象
早期的配置管理对象:
容器自动读取.xml配置文件,并创建对应id值和类的对象
在Spring-context.xml中进行配置,并且配置的时候一定要保证类中有相应的构造器,否则会报错
<bean id="controller" class="com.ysu.controller.RegisterController"> <constructor-arg ref="model"/> </bean> <bean id="model" class="com.ysu.model.impl.RegisterModelImpl"> <constructor-arg ref="service"/> </bean> <bean id="service" class="com.ysu.service.impl.RegisterServiceImpl"> <constructor-arg ref="dao"/> </bean> <bean id="dao" class="com.ysu.dao.impl.UserDaoImpl"/> |
xml配置文件的可读性很高,但是如果类很多,依赖对象很多,文件就会很大,维护起来也很麻烦
注解版管理对象:
首先Controller层的@Controller就是告诉容器帮我们创建和维护管理这个类的一个对象,其他的每一层也都有对应的注解来提示容器创建和管理该类的对象
Model层:@Component
Service层:@Service (父接口是@Component)
Dao层:@Repository (父接口是@Component)
4.2依赖注入方式
4.2.1构造器注入(配置注入)
以前所有的对象都在Spring-mvc-context.xml中的bean标签中进行管理(需要显示声明)
早期Spring提供构造器注入方式,通过构造器传入一个依赖
private IUserDao dao; public RegisterServiceImpl(IUserDao dao) { this.dao=dao; } |
4.2.2 Set注入
每个类中先声明一个引用,然后通过set映射函数给该引入赋值
private IUserDao dao; public void setDao(IUserDao dao) { this.dao=dao; } |
上面两种注入方式都是早期的依赖注入方式,实现了依赖的外部注入的特性,但是代码写起来繁琐,如果依赖对象很多就要写很多这种函数
4.2.3 自动注入(注解版)
直接通过@Autowired这一个注解自动注入,容器帮我们去找这个类型匹配的对象,反射机制
@Autowired private IUserDao dao; |
4.3注册后台逻辑代码
Controller层:RegisterController类
package com.ysu.controller; import com.ysu.domain.User; import com.ysu.model.IRegisterModel; import org.apache.commons.lang3.ObjectUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; //@Controller //定义了返回值只能是页面,不能是数据,但是可以用ModelAndView携带数据,比较麻烦 @RestController //这个就可以返回页面或者数据 @RequestMapping("/register") public class RegisterController { //注入model依赖 @Autowired private IRegisterModel model; @RequestMapping("/doRegister") //每次都返回一个ModelAndView,返回一个jsp页面 public String doRegister(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { request.setCharacterEncoding("UTF-8"); response.setCharacterEncoding("UTF-8"); PrintWriter out = response.getWriter(); //进行注册校验 User newUser=new User(); //特殊处理newUser为null的情况,如果前端没有传过来参数,数据库直接录入空串信息 newUser.setUsername(ObjectUtils.identityToString(request.getParameter("username"))); newUser.setPassword(ObjectUtils.identityToString(request.getParameter("password"))); newUser.setEmail(ObjectUtils.identityToString(request.getParameter("email"))); newUser.setTel(ObjectUtils.identityToString(request.getParameter("tel"))); //用户信息入库 //传对象数据给service,不要直接传request过去 boolean registerState =model.verifyRegister(newUser); request.setAttribute("regState",registerState); //return String.valueOf(registerState); return "/login.jsp"; } @RequestMapping("/doVerifySameName") @ResponseBody //声明当前接口可以返回数据 private String doSameName(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { //objeftUtils防止得到的数据为空 String username=ObjectUtils.toString(request.getParameter("username")); // String username=request.getParameter("username"); //如果没有加ResponseBody,往前端传回的是true.jsp或者false.jsp,默认是去找一个页面 return "admin".equals(username)+""; } } |
Model层:RegisterModelImpl类
package com.ysu.model.impl; import com.ysu.domain.User; import com.ysu.model.IRegisterModel; import com.ysu.service.IRegisterService; import com.ysu.service.impl.RegisterServiceImpl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class RegisterModelImpl implements IRegisterModel { // private IRegisterService service =new RegisterServiceImpl(); @Autowired private IRegisterService service; @Override public boolean verifyRegister(User newUser) { return service.verifyRegister(newUser); } } |
Service层:RegisterServiceImpl类
package com.ysu.service.impl; import com.ysu.dao.IUserDao; import com.ysu.domain.User; import com.ysu.service.IRegisterService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class RegisterServiceImpl implements IRegisterService { @Autowired private IUserDao dao; @Override public boolean verifyRegister(User newUser) { //数据入库 dao.insertUser(newUser); return true; } } |
Dao层:UserDaoImpl类
package com.ysu.dao.impl; import com.ysu.dao.IUserDao; import com.ysu.domain.User; import org.springframework.stereotype.Repository; import java.sql.*; import java.util.ArrayList; import java.util.List; @Repository public class UserDaoImpl implements IUserDao { @Override public void insertUser(User user) { } } |
五、MyBatis框架
直接使用javaAPI操作数据库的痛点:
- 数据库链接需要统一管理
- 属性硬编码导致代码反复被修改(数据库、驱动、数据库用户名、密码)
- 数据库表字段不能与实体对象自动封装
5.1 外部配置文件+c3p0+QueryRunner
首先导入需要用到的配置依赖
<dependency> <groupId>commons-dbutils</groupId> <artifactId>commons-dbutils</artifactId> <version>1.7</version> </dependency> <dependency> <groupId>c3p0</groupId> <artifactId>c3p0</artifactId> <version>0.9.1.2</version> </dependency> |
解决第一个痛点:通过配置文件来管理数据库属性
Java提供的配置文件:mysql.properties
url = jdbc:mysql://localhost:3306/ysu-demo?useUnicode=true &characterEncoding=utf8&serverTimezone=GMT jdbcDriver = com.mysql.cj.jdbc.Driver userName = root password = lcf2773743863 initialPoolSize = 10 //数据库链接池里最大链接的个数 |
在Dao层读取这个文件,并获取里面的内容,加载得到的Properties类是继承自HashTable的,通过get方法获取属性值
Properties props=new Properties(); //动态获取属性文件的路径 props.load(new FileReader(new File(MYSQL_PROPERTIES))); |
解决第二个痛点:通过C3P0管理数据库的连接池
数据库连接池的基本思想就是为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。我们可以通过设定连接池最大连接数来防止系统无尽的与数据库连接。获取一个连接,系统要在背后做很多消耗资源的事情,大多时候,创建连接的时间比执行sql语句的时间还要长。用户每次请求都需要向数据库获得链接,而数据库创建连接通常需要消耗相对较大的资源,创建时间也较长。
//c3p0连接池(管理连接) ComboPooledDataSource dataSource=new ComboPooledDataSource(); dataSource.setDriverClass(ObjectUtils.toString(props.get("jdbcDriver"))); dataSource.setUser(ObjectUtils.toString(props.get("userName"))); dataSource.setPassword(ObjectUtils.toString(props.get("password"))); dataSource.setJdbcUrl(ObjectUtils.toString(props.get("url"))); dataSource.setInitialPoolSize(NumberUtils.toInt(ObjectUtils.toString(props.get("initialPoolSize")))); |
解决第三个痛点:通过QueryRunner向数据库发起增删改查操作
//通过这个向数据库发起增删改查操作 QueryRunner qr=new QueryRunner(dataSource); //传入类类型,通过反射自动进行封装 String srcSql="INSERT INTO tbl_user(userName,password,email,tel) VALUES('%s','%s','%s','%s')"; String destSql=String.format(srcSql,user.getUsername(),user.getPassword(),user.getEmail(),user.getTel()); qr.insert(destSql,new BeanHandler<User>(User.class)); List<User>userList= qr.query("SELECT * FROM tbl_user",new BeanListHandler<User>(User.class)); for(User currentUser :userList) { System.out.println(currentUser.getUsername()+""+currentUser.getPassword()); } |
DAO层优化后的完整代码:
package com.ysu.dao.impl; import com.mchange.v2.c3p0.ComboPooledDataSource; import com.ysu.dao.IUserDao; import com.ysu.domain.User; import org.apache.commons.dbutils.QueryRunner; import org.apache.commons.dbutils.handlers.BeanHandler; import org.apache.commons.dbutils.handlers.BeanListHandler; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.math.NumberUtils; import org.springframework.stereotype.Repository; import java.beans.PropertyVetoException; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.sql.*; import java.util.List; import java.util.Properties; @Repository public class UserDaoImpl implements IUserDao { //系统的根目录 private static final String ROOT_PATH=UserDaoImpl.class.getResource("/").getPath(); //数据库配置文件 private static final String MYSQL_PROPERTIES=ROOT_PATH+"mysql.properties"; @Override public void insertUser(User user) { try { Properties props=new Properties(); //动态获取属性文件的路径 props.load(new FileReader(new File(MYSQL_PROPERTIES))); //c3p0连接池(管理连接) ComboPooledDataSource dataSource=new ComboPooledDataSource(); dataSource.setDriverClass(ObjectUtils.toString(props.get("jdbcDriver"))); dataSource.setUser(ObjectUtils.toString(props.get("userName"))); dataSource.setPassword(ObjectUtils.toString(props.get("password"))); dataSource.setJdbcUrl(ObjectUtils.toString(props.get("url"))); dataSource.setInitialPoolSize(NumberUtils.toInt(ObjectUtils.toString(props.get("initialPoolSize")))); //通过这个向数据库发起增删改查操作 QueryRunner qr=new QueryRunner(dataSource); //传入类类型,通过反射自动进行封装 String srcSql="INSERT INTO tbl_user(userName,password,email,tel) VALUES('%s','%s','%s','%s')"; String destSql=String.format(srcSql,user.getUsername(),user.getPassword(),user.getEmail(),user.getTel()); qr.insert(destSql,new BeanHandler<User>(User.class)); List<User>userList= qr.query("SELECT * FROM tbl_user",new BeanListHandler<User>(User.class)); for(User currentUser :userList) { System.out.println(currentUser.getUsername()+""+currentUser.getPassword()); } } catch (SQLException | IOException | PropertyVetoException e) { e.printStackTrace(); } } } |
以上三个技术就形成了一个简单的O/R映射框架,能小程度地做到功能可扩展,代码不需修改,但是Sql和java代码还是高度耦合的,比如利用java代码格式化了sql语句,对sql的维护是弱维护,经常会发生错误,如果能把sql语句提取出来进行统一管理会更好,又是一个痛点问题,于是就要引入大型框架MyBatis
5.2 MyBatis基础知识
Hibernate属于全自动化,可以通过内部机制生成sql代码,但是机器生成的sql代码和我们手写的不一样,无法进行优化和维护,代码不能做到精简高效;
MyBatis半自动化,程序员主动对sql代码进行管理和优化
基础配置:
5.2 配置MyBatis
5.2.1在POM.Xml文件中导入MyBatis依赖
<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.3</version> </dependency> <!-- 这个是mybatis的核心依赖,上一个是专门用于MyBatis和Spring整合的--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.5</version> </dependency> |
5.2.2在Spring容器中配置数据源和属性
数据库参数文件:mysql.properties
jdbc.url = jdbc:mysql://localhost:3306/ysu-demo?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT jdbc.jdbcDriver = com.mysql.cj.jdbc.Driver jdbc.userName = root jdbc.password = lcf2773743863 jdbc.initialPoolSize= 10 #注意这儿的名字要取得特殊一点,避免在前面导入的时候出现命名冲突 |
数据源对应的就是前面的c3p0入口类,就是创建一个这个类的对象在Spring容器中管理
Spring-context.xml:
<!--将配置文件关联进来,导入项目外的资源文件到Spring空间--> <context:property-placeholder location="classpath*:mysql.properties"/> <!--配置数据源--> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <!--属性注入相应的参数--> <property name="driverClass" value="${jdbc.jdbcDriver}"/> <property name="jdbcUrl" value="${jdbc.url}"/> <property name="user" value="${jdbc.userName}"/> <property name="password" value="${jdbc.password}"/> <property name="initialPoolSize" value="${jdbc.initialPoolSize}"/> </bean> |
5.2.3创建MyBatis的Mapper文件
文件映射一个java类,这个类中的主场是SQL语句,可以便利地进行SQL语句编写和维护
所有的Mapper文件都必须与持久层的Dao有关联,能够通过文件名进行自动映射,就像setter和getter函数一样,这样持久层的实现类就不需要我们自己实现,由MyBatis自动创建,我们就需要提供给他映射关系,接口和Mapper文件的映射关系
关联的要求是:Mapper配置文件名是接口的名字最左侧的文件名小写
接口:IUserDao Mapper文件名:iUserDao.xml
iUserDao.xml :
<?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"> <!-- namespace命名空间,作用:对sql进行分类化管理 --> <!--根据这个命名空间的接口会自动生成一个实现类,跟我们的UserDaoImpl一样,可能命名不同--> <mapper namespace="com.ysu.dao.IUserDao"> <!-- 里面含有update、select、query等结点--> <!-- insertUser方法传入的一个User对象--> <insert id="insertUser" parameterType="com.ysu.domain.User"> INSERT INTO tbl_user(userName,password,email,tel) VALUES(#{userName},#{password},#{email},#{tel}) </insert> <!-- #{userName}是映射找到类中对应的属性--> <select id="queryUserByName" resultType="com.ysu.domain.User" parameterType="com.ysu.domain.User"> SELECT * FROM tbl_user WHERE userName=#{userName} and password=#{password} </select> </mapper> |
这样Dao层的实现类就可以删除了
5.2.4创建MyBatis会话工厂和扫描器
Spring-context.xml:
<!--加载MyBatis会话工厂--> <bean id="sqlSession" class="org.mybatis.spring.SqlSessionFactoryBean"> <!--同级的对象必须用ref来关联--> <property name="dataSource" ref="dataSource"/> <!--加载Mapper文件,用一个通配符,表示以后加载所有与这个文件名匹配的Mapper文件--> <property name="mapperLocations" value="classpath*:mybatis/mapper/i*Dao.xml"/> </bean> <!--按类装配的,不需要写id值了--> <!--Mapper扫描器,说明去哪儿找需要映射的Java类(Dao实现类)--> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <!--接口Class文件存放位置--> <property name="basePackage" value="com.ysu.dao"/> <!--关联会话工厂--> <property name="sqlSessionFactoryBeanName" value="sqlSession"/> </bean> |
配置完成后调试代码可以看见,Service层的dao引用会指向一个MyBatis创建的代理对象,这个是自动生成的
Spring-context.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.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!--打开包扫描器,扫描com.ysu中哪些类有@Controller或者@RestController,将这些类作为控制器实例化到IOC(springring器)中--> <context:component-scan base-package="com.ysu"/> <!--开启注解驱动--> <mvc:annotation-driven/> <!--将配置文件关联进来,导入项目外的资源文件到Spring空间--> <context:property-placeholder location="classpath*:mysql.properties"/> <!--配置数据源--> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <!--属性注入相应的参数--> <property name="driverClass" value="${jdbc.jdbcDriver}"/> <property name="jdbcUrl" value="${jdbc.url}"/> <property name="user" value="${jdbc.userName}"/> <property name="password" value="${jdbc.password}"/> <property name="initialPoolSize" value="${jdbc.initialPoolSize}"/> </bean> <!--加载MyBatis会话工厂--> <bean id="sqlSession" class="org.mybatis.spring.SqlSessionFactoryBean"> <!--同级的对象必须用ref来关联--> <property name="dataSource" ref="dataSource"/> <!--加载Mapper文件,用一个通配符,表示以后加载所有与这个文件名匹配的Mapper文件--> <property name="mapperLocations" value="classpath*:mybatis/mapper/i*Dao.xml"/> </bean> <!--按类装配的,不需要写id值了--> <!--Mapper扫描器--> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <!--接口Class文件存放位置--> <property name="basePackage" value="com.ysu.dao"/> <property name="sqlSessionFactoryBeanName" value="sqlSession"/> </bean> <!--事务配置--> <!--分页插件配置或者用page-happer进行分页--> </beans> |