Java-Spring
学习视频: B站 狂神说java - https://www.bilibili.com/video/BV1WE411d7Dv?p=1
Spring官网:https://spring.io/
SpringFramework 官方下载链接:https://spring.io/projects/spring-framework#learn
参考的博客链接:https://blog.csdn.net/yalu_123456/article/details/98470446?spm=1001.2014.3001.5501
回顾
单体地狱 动态JavaWeb的开发:
- Servlet 和 JSP 的编写。继承 HttpServlet/servlet接口的web服务程序 servlet; 配合JSP 去完成 mvc结构的web开发。
- servlet的注册和映射:Tomcat 服务器。 应该还有 Jetty、Undertow 等servlet容器【Serclet程序绑定到servlet服务器】
- JDBC的繁琐代码。【每一次操作数据库,业务层/service层和dao层/底层去完成该功能时,都要去获取资源和数据库建立连接和销毁】
Servlet 和JDBC – demo
基础公共类:
-
数据库配置文件: 在resource下面创建 db.properties,关于mysql相关的数据库表、用户名和密码,便于JDBC驱动连接
driver=com.mysql.jdbc.Driver url=jdbc:mysql://127.0.0.1:3306/smbms?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai user=root password=123456
-
编写数据库的公共类:【JDBC】
package com.AL.dao;
import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
// 操作数据库的基类:静态类
public class BaseDao {
static{//静态代码块,在类加载的时候执行
init();
}
private static String driver;
private static String url;
private static String user;
private static String password;
//初始化连接参数,从配置文件里获得
public static void init(){
Properties properties = new Properties();
String configFile = "db.properties";
InputStream is = BaseDao.class.getClassLoader().getResourceAsStream(configFile);
try {
properties.load(is);
} catch (IOException e) {
e.printStackTrace();
}
driver=properties.getProperty("driver");
url=properties.getProperty("url");
user=properties.getProperty("user");
password=properties.getProperty("password");
}
/**
* 数据库连接
*/
public static Connection getConnection(){
Connection connection = null;
try {
Class.forName(driver);
connection = DriverManager.getConnection(url, user, password);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return connection;
}
/**
* 查询操作, static 方法, 查询返回的是一个 ResultSet 结果集
* @param connection
* @param preparedStatement
* @param resultSet
* @param sql
* @param params
* @return
*/
public static ResultSet execute(Connection connection, PreparedStatement preparedStatement, ResultSet resultSet, String sql, Object[] params) throws SQLException {
//预编译的sql不需要传参,直接执行即可
preparedStatement = connection.prepareStatement(sql);
/**
* //预编译中:手动给参数赋值
* st.setInt(1,17); // id
* st.setString(2,"略略略");
*/
for(int i = 0; i < params.length; i++){
preparedStatement.setObject(i+1, params[i]);
}
resultSet = preparedStatement.executeQuery();//新添加sql
return resultSet;
}
/**
* 数据库的更新操作:增加、删除、修改
* @param connection
* @param preparedStatement
* @param sql
* @param params
* @return
* @throws Exception
*/
public static int execute(Connection connection,PreparedStatement preparedStatement,String sql,Object[] params) throws Exception{
int updateRows = 0;
preparedStatement = connection.prepareStatement(sql);
for(int i = 0; i < params.length; i++){
preparedStatement.setObject(i+1, params[i]);
}
updateRows = preparedStatement.executeUpdate();
return updateRows;
}
/**
* 释放资源
* @param connection
* @param preparedStatement
* @param resultSet
* @return
*/
public static boolean closeResource(Connection connection,PreparedStatement preparedStatement,ResultSet resultSet){
boolean flag = true;
if(resultSet != null){
try {
resultSet.close();
resultSet = null;//GC回收
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
flag = false;
}
}
if(preparedStatement != null){
try {
preparedStatement.close();
preparedStatement = null;//GC回收
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
flag = false;
}
}
if(connection != null){
try {
connection.close();
connection = null;//GC回收
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
flag = false;
}
}
return flag;
}
}
一个简单功能实现的demo
-
编写前端页面
-
设置首页
-
编写dao层:登录用户 登录接口
package com.AL.dao.user; import com.AL.pojo.User; import java.sql.Connection; import java.sql.SQLException; // 登录用户接口 public interface UserDao { //得到要登录的用户, private String userCode; //用户编码 public User getLoginUser(Connection connection, String userCode) throws SQLException, Exception; }
-
编写dao层 接口实现类
在这里建立数据库连接后,利用 userCode 用户编码去获取数据库中对应的User对象
package com.AL.dao.user; import com.AL.dao.BaseDao; import com.AL.pojo.User; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; // 登录用户的 接口实现类 public class UserDaoImpl implements UserDao { //持久层只做查询数据库的内容 //得到要登录的用户 @Override public User getLoginUser(Connection connection, String userCode) throws SQLException, Exception { // connection JDBC数据库连接在BasDao中已经是一个静态方法,可以直接使用 // 创建数据库查询时的三个对象:预编译、查询结果对象、类 PreparedStatement pstm = null; ResultSet rs = null; User user = null; // 如果连接成功则进行查询 if (connection != null){ String sql = "select * from smbms_user where userCode=?"; Object[] params = {userCode}; rs = BaseDao.execute(connection, pstm, rs, sql, params); if (rs.next()){ // 查询得到用户信息,将此信息给了user,即给了后台,便于进行数据传递 user = new User(); user.setId(rs.getInt("id")); user.setUserCode(rs.getString("userCode")); user.setUserName(rs.getString("userName")); user.setUserPassword(rs.getString("userPassword")); user.setGender(rs.getInt("gender")); user.setBirthday(rs.getDate("birthday")); user.setPhone(rs.getString("phone")); user.setAddress(rs.getString("address")); user.setUserRole(rs.getInt("userRole")); user.setCreatedBy(rs.getInt("createdBy")); user.setCreationDate(rs.getTimestamp("creationDate")); user.setModifyBy(rs.getInt("modifyBy")); user.setModifyDate(rs.getTimestamp("modifyDate")); } BaseDao.closeResource(null, pstm, rs); } return user; } }
-
业务层接口:关于用户登录校验的这里
- 用户登录:userCode和password 从前端输入获取之后,userCode从数据库中查询得到对应的password,然后和前端得到的进行比较
package com.AL.service.user; import com.AL.pojo.User; // 关于用户的接口 public interface UserService { //用户登录 public User login(String userCode, String password); }
-
业务层接口实现类
- 业务层都会调用dao层.所以我们要引入Dao层(重点)
- userCode获取后调用dao层获取所在数据库中的user信息,进行比较,验证该登录用户
package com.AL.service.user; import com.AL.dao.BaseDao; import com.AL.dao.user.UserDao; import com.AL.dao.user.UserDaoImpl; import com.AL.pojo.User; import java.sql.Connection; public class UserServiceImpl implements UserService { //业务层都会调用dao层.所以我们要引入Dao层(重点) //只处理对应业务 private UserDao userDao; public UserServiceImpl(){ userDao = new UserDaoImpl(); } @Override public User login(String userCode, String password) { Connection connection = null; //通过业务层调用对应的具体数据库操作 User user = null; try { connection = BaseDao.getConnection(); user = userDao.getLoginUser(connection, userCode); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); }finally{ BaseDao.closeResource(connection, null, null); } // 匹配密码 if (null != user) { if (!user.getUserPassword().equals(password)) user = null; } return user; } }
-
编写Servlet:从前端获取信息,进行响应,service层会调用dao层进行校验,把调用结果返回给前端
package com.AL.servlet.user; import com.AL.pojo.User; import com.AL.service.user.UserServiceImpl; import com.AL.util.Constants; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; // 用户登录的Servlet web @SuppressWarnings("serial") public class LoginServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("正在登陆 ============ " ); System.out.println("。。。" ); System.out.println("。。" ); // 获取用户名和密码 String userCode = req.getParameter("userCode"); String userPassword = req.getParameter("userPassword"); // 调用service方法,进行用户验证 UserServiceImpl userService = new UserServiceImpl(); User user = userService.login(userCode, userPassword); if(null != user){//登录成功 System.out.println("登录成功 ============ " ); //放入session req.getSession().setAttribute(Constants.USER_SESSION,user); //页面跳转(frame.jsp) resp.sendRedirect("jsp/frame.jsp"); }else{ System.out.println("登录失败 ============ " ); //页面跳转(login.jsp)带出提示信息--转发 req.setAttribute("error", "用户名或密码不正确"); req.getRequestDispatcher("login.jsp").forward(req,resp); } } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { super.doPost(req, resp); } }
-
注册Servlet
<servlet> <servlet-name>LoginServlet</servlet-name> <servlet-class>com.AL.servlet.user.LoginServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>LoginServlet</servlet-name> <url-pattern>/login.do</url-pattern> </servlet-mapping>
Mybatis - demo
准备工作
- maven依赖的 jar包:mysql驱动、mybatis、junit测试
<!--导入依赖 -->
<dependencies>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.23</version>
</dependency>
<!--mybatis -->
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<!--junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
-
专注于处理数据库 sql。那么还需要搭建一个数据库
CREATE DATABASE `mybatis`; USE `mybatis`; CREATE TABLE `user`( `id` INT(20) NOT NULL PRIMARY KEY, `name` VARCHAR(30) DEFAULT NULL, `pwd` VARCHAR(30) DEFAULT NULL )ENGINE=INNODB CHARSET=utf8; INSERT INTO `user`(`id`, `name`, `pwd`) VALUES (1,'鑫仔','123456'), (2,'天啊','123456'),(3,'好胖','123890')
创建Maven项目
1.创建一个普通的maven项目,没有maven模板。
2.删除src目录
3.导入maven依赖:导入maven依赖需要导入的jar包:mysql驱动、mybatis、junit、【前面准备工作中的依赖】
4.创建一个子项目
此时的子项目 mybatis-01 会继承父项目中的 jar包,不用再次导入。
Mybatis框架的步骤:
- 每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。
- SqlSessionFactory 的实例需要通过SqlSessionFactoryBuilder 来得到。
- 这个SqlSessionFactoryBuilder 是要在xml配置文件中进行配置 config, 然后才能去构建SqlSessionFactory 实例。
参考的官方链接:https://mybatis.org/mybatis-3/zh/getting-started.html
【自己的理解:其实和JDBC一样,对mysql配置信息进行封装 在 resource。原来是资源配置文件 db.properties 中保存 driver、url、user、pwd的信息,再在java代码中进行 Connection 连接。而SqlSessionFactoryBuilder也同样是根据资源配置文件 *.xml文件来创建得到的】
SqlSesssionFactory 需要由SqlSessionFactoryBuilder 创建得到,其实这种就类似于statment 由Connection 创建得到。
原来的JDBC代码: 连接数据库:
//4.执行SQL的对象 Statement 表示执行SQL的对象,此时就相当于 school.student这个表
Statement statement = connection.createStatement();
//5.执行SQL的对象去执行 sql 可能存在结果,查看返回结果
String sql="select * from student";
ResultSet resultSet = statement.executeQuery(sql);
conn= JdbcUtils.getConnection();//获取数据库连接
//预编译sql 先写sql 然后不执行. 使用?占位符代替参数
String sql="INSERT INTO `student` VALUES(?,?,?,?,?,?,?)";
st=conn.prepareStatement(sql);//获取执行sql对象
通过数据库配置文件 建立数据库连接:
// 获取类加载器,然后得到数据库的资源
InputStream in = JdbcUtils.class.getClassLoader().getResourceAsStream("db.properties");
Properties properties = new Properties();
properties.load(in);
driver = properties.getProperty("driver");
2.2.1 编写mybatis的核心配置文件
如下所示:在资源文件 resource 中的 xml文件中进行预定义配置 config,才能去构建 SqlSessionFactoryBuilder。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--XML 配置文件或者预先定义的一个 config,去SqlSessionFactoryBuilder, 配置数据库,便于创建sqlSessionFactory实例-->
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&charsetEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<!--关联映射文件-->
<mappers>
<mapper resource="com/AL/dao/UserMapper.xml"/>
</mappers>
</configuration>
2.2.2、编写mybatis工具类
在这里我们预定义配置了mybatis-config.xml。我们是从 XML文件中去构建 SqlSessionFactory的实例。使用类路径下资源文件进行配置,即 String resource = “mybatis-config.xml”;
SqlSessionFactoryBuilder 从配置文件 mybatis-config.xml中 使用输入流InputStream 获得SqlSessionFactory,并从中获取sqlsession。
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
mybatis工具类:
通过SqlSessionFactory 获取 SqlSession。SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。
package com.AL.utils;
import org.apache.ibatis.jdbc.SQL;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
//import javax.annotation.Resources;
import org.apache.ibatis.io.Resources;
import java.io.InputStream;
// sqlSessionFactory --> sqlSession
public class MybatisUtils {
// 创建这个sqlSessionFactory,是从资源文件resource中的去获得。
private static SqlSessionFactory sqlSessionFactory;
static {
try {
//利用mybatis第一步: 获取一个sqlSessionFactory对象
String resource = "mybatis-config.xml";
/** 注意:不需要修改,是因为导入的包不正确.不是 import javax.annotation.Resources
* 将ResourcesgetResourceAsStream(resource);改为
* Resources.class.getResourceAsStream(resource);
* //得到配置文件流
* InputStream inputStream = Resources.class.getResourceAsStream(resource);
*/
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (Exception e) {
e.printStackTrace();
}
}
//有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession 的实例。
// SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。
public static SqlSession getSqlSession() {
//SqlSession sqlSession = sqlSessionFactory.openSession();
//return sqlSession;
return sqlSessionFactory.openSession();
}
}
2.2.3、实体类
编写实体类。实体类 class中的属性对应着 数据库 mybatis.user中的字段, 即 id,name, pwd。
package com.AL.pojo;
public class User {
private int id;
private String name;
private String pwd;
public User() {
}
public User(int id, String name, String pwd) {
this.id = id;
this.name = name;
this.pwd = pwd;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", pwd='" + pwd + '\'' +
'}';
}
}
2.2.4、Dao接口
package com.AL.dao;
import com.AL.pojo.User;
import java.util.List;
public interface UserDao {
// 获取全部的用户
List<User> getUserList();
}
2.2.5、Mapper配置文件
接口实现类:sql语句功能。
接口实现类由原来的 UserDaoImpl转变为一个 Mapper配置文件。
<?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= 绑定一个对应的Dao/Mapper接口
<mapper namespace="org.mybatis.example.BlogMapper">
<select id="selectBlog" resultType="Blog">
select * from Blog where id = #{id}
-->
<mapper namespace="com.AL.dao.UserDao">
<!--select 查询语句 -->
<select id="getUserList" resultType="com.AL.pojo.User">
select * from mybatis.user
</select>
</mapper>
理解:
在命名空间 “org.mybatis.example.BlogMapper” 中定义了一个名为 “selectBlog” 的映射语句,这样你就可以用全限定名 “org.mybatis.example.BlogMapper.selectBlog” 来调用映射语句。
对于像 BlogMapper 这样的映射器类来说,还有另一种方法来完成语句映射。 它们映射的语句可以不用 XML 来配置,而可以使用 Java 注解来配置。比如,上面的 XML 示例可以被替换成如下的配置:
package org.mybatis.example;
public interface BlogMapper {
@Select("SELECT * FROM blog WHERE id = #{id}")
Blog selectBlog(int id);
}
使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从心,还会让你本就复杂的 SQL 语句更加混乱不堪。
2.2.6、测试代码
在测试的里面,我们也建立和main中的java 对应的结构,这样更加规范:
package com.AL.dao;
import com.AL.pojo.User;
import com.AL.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.junit.Test;
import java.util.List;
public class UserDaoTest {
@Test
public void test(){
//1.拿到sqlSessionFactory对象
//SqlSessionFactory sqlSessionFactory = MybatisUtils.getSqlSession();
//2.通过sqlSessionFactory对象openSession()创建一个sqlSession。
//SqlSession sqlSession = sqlSessionFactory.openSession();
SqlSession sqlSession = MybatisUtils.getSqlSession();
//3.通过sqlSession获得mapper对象 , 参数为映射文件对应的接口类的class对象
UserDao userDao = sqlSession.getMapper(UserDao.class);
//4.通过mapper对象来执行操作;
List<User> userList = userDao.getUserList();
//获得结果集
for (User user : userList) {
System.out.println(user);
}
//关闭sqlSession
sqlSession.close();
}
}
2.2.7、结果
发现错误:
org.apache.ibatis.exceptions.PersistenceException:
### Error building SqlSession.
### The error may exist in com/AL/dao/UserMapper.xml
.......
Caused by: java.io.IOException: Could not find resource com/AL/dao/UserMapper.xml
可以发现是这个资源无法找到。
很明显是Could not find resource com/xxx/xxx/Xxx.xml的错误。本人使用的是idea编译器,在idea中是不会编译src的java目录的xml文件,所以在Mybatis的配置文件中找不到xml文件!(也有可能是Maven构建项目的问题)。
资源配置导出出现了问题,所以在 pom.xml文件中进行添加配置资源导出:
<build>
<!--希望maven在导出项目的时候,能够将我们的配置及资源导出-->
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
1、Spring简介
对于原先麻烦的 单体地狱:Servlet JDBC JSP …
**替代JDBC的框架: MyBatis;**不用再 重复地写JDBC代码以及设置参数和获取结果集的工作。 JDBC 涉及到数据库连接、加载、获取对象等重复性的工作,为了专注于只去修改sql语句,为了更加便捷,所以需要这个框架 Mybatis。
Spring:春天—>给软件行业带来春天
2002,Rod johnson 首次推出了Spring框架的前身 interface21框架
2003,Spring框架以interface21框架为基础,重新设计,发布1.0正式版。
版本说明:
我们在下载软件会遇到诸如release,stable,alpha,beta,pre,current,eval,rc,snapshot等版本,程序猿下载插件时尤为常见,以下为各种版本的意思:
1,snapshot(快照),也即开发版,我们创建maven项目时,编辑器会自动给我们填入 1.0-SNAPSHOT版本,也就是1.0开发版,这个版本不能使用,因为该版本处于开发的过程,所以运行时会不时地更新,导致功能变化,正式环境中不得使用snapshot版本的库;
2,alpha,内部测试版,来源于字母α,是比较早的版本,主要是给开发人员和测试人员测试和找BUG用的,不建议使用;
3,beta,公开测试版,来源于字母β,这是比alpha进一步的版本,面向公众测试,但是不建议使用
4,pre,这个和alpha版本类似,有时还会细分为M1,M2版本,不建议使用;
5,RC(Release Candidate) 顾名思义么 ! 用在软件上就是候选版本。系统平台上就是发行候选版本;
6,GA(General Availability)正式发布的版本,在国外都是用GA来说明release版本的;
7,release,发行版,这是release的中文意思,也就是官方推荐使用的版本;
8,stable,稳定版,这个版本相比于测试版更加稳定,去除了测试版许多的bug,完善了一些功能,建议使用;
9,current,最新版,但是不一定是稳定版本,需要看一下是否还有release或者stable等版本;
10,eval,评估版。可能会有一个月或者固定时间的使用期限;
Spring 是⼀种轻量级开发框架,旨在提⾼开发⼈员的开发效率以及系统的可维护性。Spring 官⽹:https://spring.io/。
我们⼀般说 Spring 框架指的都是 Spring Framework,它是很多模块的集合,使⽤这些模块可以很⽅便地协助我们进⾏开发。这些模块是:核⼼容器、数据访问/集成,、Web、AOP(⾯向切⾯编程)、⼯具、消息和测试模块。⽐如:Core Container 中的 Core 组件是Spring 所有组件的核⼼,Beans 组件和 Context 组件是实现IOC和依赖注⼊的基础,AOP组件⽤来实现⾯向切⾯编程。Spring 官⽹列出的 Spring 的 6 个特征:
- 核⼼技术 :依赖注⼊(DI),AOP,事件(events),资源,i18n,验证,数据绑定,类型转换,SpEL。
- 测试 :模拟对象,TestContext框架,Spring MVC 测试,WebTestClient。
- 数据访问 :事务,DAO⽀持,JDBC,ORM,编组XML。【事务、底层dao、jdbc数据库连接、orm对象关系映射】
- Web ⽀持 : Spring MVC和Spring WebFlux Web框架。
- 集成 :远程处理,JMS,JCA,JMX,电⼦邮件,任务,调度,缓存。
- 语⾔ :Kotlin,Groovy,动态语⾔。
1.1、Spring
Spring理念:使现有的技术更加容易使用, 本身是一个大杂烩,整合了现有的技术框架!
它具有的优点:
- Spring是一个开源的免费的框架(容器)!
- Spring是一个轻量级的、非侵入式的框架! 非侵入式,即你引用它,它不会改变你原来代码的意义。
- 控制反转(IOC),面向切面编程(AOP)!
- 支持事务的处理,对框架整合的支持!
总之:SPring是一个轻量级的控制反转(IOC)和面向切面编程(AOP)的框架!
【非侵入式:开闭原则吧,不修改原来的代码,增加新的扩展。】
1.2、组成
七大框架:
对于架构:前面有了解过 MVC 三层架构,model、view、control。
对于Spring框架 是一个分层架构,由 7 个定义良好的模块组成。Spring 模块构建在核心容器之上,核心容器定义了创建、配置和管理 bean 的方式。
组成 Spring 框架的每个模块(或组件)都可以单独存在,或者与其他一个或多个模块联合实现。每个模块的功能如下:
- 核心容器:核心容器提供 Spring 框架的基本功能。核心容器的主要组件是 BeanFactory,它是工厂模式的实现。BeanFactory 使用控制反转 (IOC) 模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。
- Spring 上下文:Spring 上下文是一个配置文件,向 Spring 框架提供上下文信息。Spring 上下文包括企业服务,例如 JNDI、EJB、电子邮件、国际化、校验和调度功能。
- Spring AOP:通过配置管理特性,Spring AOP 模块直接将面向方面的编程功能集成到了 Spring 框架中。所以,可以很容易地使 Spring 框架管理的任何对象支持 AOP。Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖 EJB 组件,就可以将声明性事务管理集成到应用程序中。【AOP基于动态代理,去实现 接口任务,且对于增加的业务代码能够进行面向切面编程】
- Spring DAO:JDBC DAO 抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量(例如打开和关闭连接)。Spring DAO 的面向 JDBC 的异常遵从通用的 DAO 异常层次结构。
- Spring ORM:Spring 框架插入了若干个 ORM 框架,从而提供了 ORM 的对象关系工具,其中包括 JDO、Hibernate 和 iBatis SQL Map。所有这些都遵从 Spring 的通用事务和 DAO 异常层次结构。
- Spring Web 模块:Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。所以,Spring 框架支持与 Jakarta Struts 的集成。Web 模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。
- Spring MVC 框架:MVC 框架是一个全功能的构建 Web 应用程序的 MVC 实现。通过策略接口,MVC 框架变成为高度可配置的,MVC 容纳了大量视图技术,其中包括 JSP、Velocity、Tiles、iText 和 POI。【就是针对 MVC三层架构创建的一个框架】
Spring 框架的功能可以用在任何 J2EE 服务器中,大多数功能也适用于不受管理的环境。Spring 的核心要点是:支持不绑定到特定 J2EE 服务的可重用业务和数据访问对象。毫无疑问,这样的对象可以在不同 J2EE 环境 (Web 或 EJB)、独立应用程序、测试环境之间重用。
拓展:
在Spring的官网上的介绍:现代化的Java开发,说白了就是基于Spring的开发。
- Spring Boot
- 一个快速开发的脚手架
- 基于SpringBoot可以快速开发单个微服务
- 约定大于配置
- Spring Cloud
- Spring Cloud 是基于 SpringBoot实现的
Spring Boot使用了约束优于配置的理念,很多集成方案已经帮你选择好了,能不配置就不配置 , Spring Cloud很大的一部分是基于Spring Boot来实现,Spring Boot可以离开Spring Cloud独立使用开发项目,但是Spring Cloud离不开Spring Boot,属于依赖的关系。
1.3、Spring的依赖和学习文档:
Maven仓库: 下载 Spring Web MVC
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.5</version>
</dependency>
Spring官方学习文档:https://docs.spring.io/spring-framework/docs/current/reference/html/
Spring官方下载链接:https://repo.spring.io/release/org/springframework/spring/
2、IOC理论推导
控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫==“依赖查找”(Dependency Lookup)==。通过控制反转,对象在被创建的时候,由一个调控系统内 所有对象 的 外界实体 将其所依赖的对象 的引用 传递给它。也可以说,依赖被注入到对象中。
在原来的业务中,用户的需求可能会影响我们的代码。我们需要根据用户的需求去修改原代码,但是当代码量巨大的时候,修改一次的成本非常昂贵。
例子:我们去获取几种默认的数据库,观察麻烦程度:
创建一个Maven项目,导入依赖。删除 src, 这样创建的其他模块就都是子项目。
maven依赖:
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.5</version>
</dependency>
例子:我们去获取几种默认的数据库,观察麻烦程度:
-
UserDao接口。
public interface UserDao { public void getUser(); }
-
UserDaoImpl实现类。这里,我们假设有三种类型的数据库,去完成接口实现类的工作。
public class UserDaoImpl implements UserDao { public void getUser(){ System.out.println("默认用户的数据"); } } //另外一种的 mysql public class UserMysqlDaoImpl implements UserDao{ public void getUser(){ System.out.println("Mysql用户的数据"); } } //另外一种的 oracle public class UserOracleDaoImpl implements UserDao{ public void getUser(){ System.out.println("Oracle用户的数据"); } }
-
UserService业务接口
public interface UserService { public void getUser(); }
-
UserServiceImpl业务实现类:
-
业务层都会调用Dao层,所以需要**引入Dao层**(重点)。只处理对应业务。
private UserDao userDao; // 利用set进行动态 实现值的注入。 public void setUserDao(UserDao userDao) { this.userDao = userDao; }
-
如果我们要去使用MySQL的话,需要在service实现类里面修改对应的实现。
-
如果要去使用Oracle的话,就在service实现类中修改需求对象为 Oracle Dao层的接口实现类
public class UserServiceImpl implements UserService { // 在原来的业务中, 根据用户想要调用哪个数据库的需求, 程序员需要每次去修改,创建和需求对应的 接口实现类。 //private UserDao userDao = new UserDaoImpl(); //private UserDao userDao = new UserMysqlDaoImpl(); private UserDao userDao = new UserOracleDaoImpl(); public void getUser(){ userDao.getUser(); } }
-
-
测试程序:
public class MyTest { public static void main(String[] args){ //用户实际调用的是业务层。 不会接触 dao 层。 UserService userService = new UserServiceImpl(); userService.getUser(); } }
结果: 根据用户的需求, 我们在业务层即 service中的UserServiceImpl业务实现类,需要修改代码,去创建或者是**调用 对应的 dao接口实现类**。 在代码量大的时候,这一个个去修改 去特别麻烦,修改代价昂贵。
// 用户调用的业务层,而 service 对应的 底层中的接口实现类 // private UserDao userDao = new UserDaoImpl(); // 默认的dao层接口实现类方法 // private UserDao userDao = new UserMysqlDaoImpl(); // MYSQL的dao层接口类实现方法 userDao.getUser(); 默认用户的数据 Mysql用户的数据 Oracle用户的数据
可以发现:用户的需求,他们实际上调用的是业务层即 service,但是 service里面的对应的 业务接口实现类 中,它调用的是 dao层对应的接口实现类, 而在需求不同的时候,我们还要去一个个修改代码,去创建 调用需求对应的 dao接口实现类, 这太繁琐了。
代码修改: 思想转变
我们需要在思想上转变这种修改的方法。原业务中,是在程序中创建对象,控制权是在程序员手里面。 这需要程序员去进行管理。我们可以在需要用到它的地方 , 不去实现它 , 而是留出一个接口 , 利用set。
在这里,我们使用一个set接口实现改变, 这是一次发生革命性的变化。
在UserServiceImpl 业务实现类的修改:利用set进行动态 实现值的注入。
public class UserServiceImpl implements UserService {
/**
//private UserDao userDao = new UserDaoImpl();
private UserDao userDao = new UserMysqlDaoImpl();
//private UserDao userDao = new UserOracleDaoImpl();
*/
private UserDao userDao;
// 利用set进行动态 实现值的注入。
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void getUser(){
userDao.getUser();
}
}
在测试中的修改:
public class MyTest {
public static void main(String[] args){
//用户实际调用的是业务层。 不会接触 dao 层。
UserService userService = new UserServiceImpl();
//((UserServiceImpl) userService).setUserDao(new UserDaoImpl());
((UserServiceImpl) userService).setUserDao(new UserMysqlDaoImpl());
//((UserServiceImpl) userService).setUserDao(new UserOracleDaoImpl());
userService.getUser();
}
}
结果和我们程序原来 手动的创建 对象,管理对象的结果是一样。 虽然这里我们还是要测试中去改,但测试这里的程序是 用户自己改的, 不是程序员根据用户需求,再去修改原代码。
- 修改之前,程序是主动创建的,控制权是在程序员手中
- 使用 set 注入后,程序就不再具有主动性。 它由用户的需求,然后被动的接收对象, 接收这个需求。
这种思想,从本质上解决了问题,程序员不用再去管理对象创建。 系统的耦合性大大降低。 可以更加专注的在业务的实现上面。 这就是 IOC的原型。
使用service层中的 UserService ,调用接口; 调用接口实现类也行。那么为什么不是直接调用接口实现类呢? 毕竟直接接口的话还要强制转化。
public class MyTest {
public static void main(String[] args) {
//用户实际调用的是业务层。 不会接触 dao 层。
UserService userService = new UserServiceImpl();
//((UserServiceImpl) userService).setUserDao(new UserDaoImpl());
//((UserServiceImpl) userService).setUserDao(new UserMysqlImpl());
((UserServiceImpl) userService).setUserDao(new UserOracleImpl());
UserServiceImpl userService1 = new UserServiceImpl();
userService1.setUserDao(new UserMysqlImpl());
userService.getUser();
userService1.getUser();
}
}
结果:
Oracle用户的数据
Mysql用户的数据
3、IOC本质
原来的思想:对于用户的需求的不同, 用户在去调用业务层的时候, 我们程序员在 service 层即业务层中 再去根据不同的需求 去调用不同的dao层 底层 接口实现类。 主动权 是在我们程序猿手里,我知道 用户要干嘛了,累死我 我也要一个个去调用不同的业务 对应着的底层接口实现类,来完成 service中的工作,用户的需求。
现在的思想:不行了,我顶不住了。 这么多的用户进行调用,或者有这么多的底层对应着 需求。 每次都要我去改,我在干嘛? 系统的耦合性太高了,代码的复合性降低了。 哎?我干嘛不让用户自己选择,用户去掌握主动权,用户自己想用啥 就去调用啥就行了。这样的话,降低了系统耦合性。
3.1、思想上的转换
控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。DI(Dependency Injection,简称DI)依赖注入是实现IOC的一种方法。
在么有IOC的程序中,对象的创建和依赖关系完全硬编码在程序中, 这时的对象创建是要由程序自己控制的。而控制反转后 对象的创建会转移给第三方。 控制反转,就是获得依赖对象的方式反转了。原来,对象是程序自己创建, 现在这个对象创建由别人控制,程序负责接收对象就行了。
控制反转 IOC 能够大大降低系统的耦合性。对于原来未修改之前的业务,程序猿还要去控制创建对象,这时把主动权交给了调用者 . 程序不用去管怎么创建,怎么实现了 . 它只负责提供一个接口 .
当然了,底层dao层是程序员写好的。dao层操作数据库。 原来主动权在程序员手里,知道用户想要什么了,我再去业务层接口实现类 修改,我去进行创建 然后返回给用户。 现在是,我在service接口实现类中提供一个接口,用户想要什么,从接口set注入(依赖注入DI),再去调用对应的dao层。
IoC是Spring框架的核心内容,使用多种方式完美的实现了IoC,可以使用XML配置,也可以使用注解,新版本的Spring也可以零配置实现IoC。
Spring容器在初始化时先读取配置文件,根据配置文件或元数据(注解也叫元数据)创建与组织对象存入容器中,程序使用时再从Ioc容器中取出需要的对象。
采用XML方式配置Bean的时候,Bean的定义信息 是和 实现 分离的,而采用注解的方式可以把两者合为一体,Bean的 定义信息 直接以注解的形式 定义在 实现类 中,从而达到了零配置的目的。
控制反转是一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式。
在Spring中实现控制反转的是IoC容器,其实现方法是依赖注入(Dependency Injection,DI)。
3.2、各层含义理解
— 忘记是在哪里搜索的了 这一部分。
-
DAO层: DAO层主要是做数据持久层的工作,负责与数据库进行联络的一些任务都封装在此,DAO层的设计首先是设计DAO的接口,然后在Spring的配置文件中定义此接口的实现类,然后就可在模块中 调用此接口 来进行 数据业务 的处理,而不用关心此接口的具体实现类是哪个类,显得结构非常清晰,DAO层的数据源配置,以及有关数据库连接的参数都在Spring的配置文件中进行配置。
-
Service层:Service层主要负责业务模块的逻辑应用设计。同样是首先设计接口,再设计其实现的类,接着再Spring的配置文件中配置其实现的关联。这样我们就可以在应用中调用Service接口来进行业务处理。Service层的业务实现,具体要调用到已定义的DAO层的接口,封装Service层的业务逻辑有利于通用的业务逻辑的独立性和重复利用性,程序显得非常简洁。
-
Controller层: Controller层负责具体的业务模块流程的控制,在此层里面要调用Service层的接口来控制业务流程,控制的配置也同样是在Spring的配置文件里面进行,针对具体的业务流程,会有不同的控制器,我们具体的设计过程中可以将流程进行抽象归纳,设计出可以重复利用的子单元流程模块,这样不仅使程序结构变得清晰,也大大减少了代码量。
-
View层: 此层与控制层结合比较紧密,需要二者结合起来协同工发。View层主要负责前台jsp页面的表示 .
-
DAO层,Service层这两个层次都可以单独开发,互相的耦合度很低,完全可以独立进行,这样的一种模式在开发大项目的过程中尤其有优势,Controller,View层因为耦合度比较高,因而要结合在一起开发,但是也可以看作一个整体独立于前两个层进行开发。这样,在层与层之前我们只需要知道接口的定义,调用接口即可完成所需要的逻辑单元应用,一切显得非常清晰简单。
-
DAO设计的总体规划需要和设计的表,和实现类之间一一对应。 DAO层所定义的接口里的方法都大同小异,这是由我们在DAO层对数据库访问的操作来决定的,对数据库的操作,我们基本要用到的就是新增,更新,删除,查询等方法。因而DAO层里面基本上都应该要涵盖这些方法对应的操作。除此之外,可以定义一些自定义的特殊的对数据库访问的方法。
Service逻辑层设计
Service层是建立在DAO层之上的,建立了DAO层后才可以建立Service层,而Service层又是在Controller层之下的,因而Service层应该既调用DAO层的接口,又要提供接口给Controller层的类来进行调用,它刚好处于一个中间层的位置。每个模型都有一个Service接口,每个接口分别封装各自的业务处理方法。
在DAO层定义的一些方法,在Service层并没有使用,那为什么还要在DAO层进行定义呢?这是由我们定义的需求逻辑所决定的。DAO层的操作 经过抽象后基本上都是通用的,因而我们在定义DAO层的时候可以将相关的方法定义完毕,这样的好处是在对Service进行扩展的时候不需要再对DAO层进行修改,提高了程序的可扩展性。
Service层例子:
- 调用dao层接口: userDao.getUser();
- 给 Controller层的类调用:提供setUserDao(UserDao userDao) 接口 【依赖注入,set注入】。
package com.AL.service;
import com.AL.dao.UserDao;
public class UserServiceImpl implements UserService{
/**
* UserServiceImpl业务实现类:
* - 如果我们要去使用MySQL的话,需要在service实现类里面修改对应的实现。
* - 如果要去使用Oracle的话,就在service实现类中修改需求对象为 Oracle Dao层的接口实现类
*/
// 在原来的业务中, 根据用户想要调用哪个数据库的需求, 程序员需要每次去修改,创建和需求对应的 接口实现类
//private UserDao userDao = new UserDaoImpl();
//private UserDao userDao = new UserMysqlImpl();
private UserDao userDao;
// 利用set进行动态 实现值的注入。
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void getUser() {
userDao.getUser();
}
}
4、HelloSpring
快速上手 Spring:
导入jar包,maven依赖:
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.5</version>
</dependency>
1.实体类: 这里运用了set注入, 依赖注入是一种 ioc的方法。即 setStr。Dependency Injection,DI
public class Hello {
private String str;
public String getStr() {
return str;
}
public void setStr(String str) {
this.str = str;
}
@Override
public String toString() {
return "hello{" +
"str='" + str + '\'' +
'}';
}
}
2.xml配置文件:采用XML方式配置Bean的时候,Bean的定义信息 是和 实现 分离的,在这里使用 xml 配置去实现 IOC 控制反转。
在 使用Spring来创建对象的时候,在Spring中 这些都称为容器 bean,此时 bean 中的信息 和 实现 分离。 在这里 使用 property 去进行赋值 信息。
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--使用Spring来创建对象,在Spring中 这些都称为容器 bean
类型 变量名 = new 类型();
Hello hello = new Hello
在这个bean中:
id = 变量名
class = new 的对象
注意: 这里的name并不是属性 , 而是set方法后面的那部分,首字母小写。 不过在这里,可以简单当作属性
property 就相当于给对象hello中的属性设置一个值
-->
<bean id="hello" class="com.AL.pojo.Hello">
<!-- collaborators and configuration for this bean go here -->
<property name="str" value="Spring"/>
</bean>
<!-- more bean definitions go here -->
</beans>
3.测试:
import com.AL.pojo.Hello;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
public static void main(String[] args) {
// 获取Spring的上下文. 解析beans.xml文件 , 获取ApplicationContext。得到了Spring容器,生成管理相应的Bean对象
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//我们的对象现在都在Spring的管理中, 我们如果要使用,直接从 bean中即context 取出来就行
//getBean : 参数即为spring配置文件中bean的id .
Hello hello = (Hello) context.getBean("hello");
System.out.println(hello.toString());
}
}
结果:
Hello{str='Spring'}
总结:
-
Hello 对象是谁创建的 ?
hello 对象是由Spring创建的
-
Hello 对象的属性是怎么设置的 ?
hello 对象的属性是由Spring容器设置的
-
在这里 Spring 是使用 xml配置文件去完成 bean 容器的 信息 和实现的,完成了IOC的一种方式
这个过程就叫控制反转 :
- 控制 : 谁来控制对象的创建 , 传统应用程序的对象是由程序本身控制创建的 , 使用Spring后 , 对象是由Spring来创建的
- 反转 : 程序本身不创建对象 , 而变成被动的接收对象 .
依赖注入 : 就是利用 set方法 来进行注入的。IOC是一种编程思想,由主动的编程变成被动的接收
可以通过new ClassPathXmlApplicationContext去浏览一下底层源码 .
此时,对于实现用户不同的需求,或者讲实现不同的操作,直接在 xml 配置文件中进行修改就可以了, 在这个 bean 容器中完成 信息和实现。
所谓的IOC:对象由Spring来创建,管理,装配。Bean容器中的信息 完成了定义和实现,对象放在了 bean 容器中,你需要啥,直接从容器中拿就完事了。
Spring会去完成对象的创建、管理、装配,都放置在了bean容器中。需要什么东西,直接从容器中获取。
对上一节的代码进行修改: 用户不同的需求,针对 想要获得不同的数据库。
1.xml配置文件: 使用Spring来创建对象,使用 bean 容器, 去完成IOC 控制反转。
-
对于上面的创建一个bean时,为:
-
注意: 这里的name并不是属性 , 而是set方法后面的那部分,首字母小写。
-
引用另外一个bean , 不是用value 而是用 ref。
-
public void setStr(String str) { //注意: 这里的name并不是属性 , 而是set方法后面的那部分,首字母小写。 this.str = str; }
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--使用Spring来创建对象,在Spring中 这些都称为容器 bean
类型 变量名 = new 类型();
Hello hello = new Hello
在这个bean中:
id = 变量名
class = new 的对象
property 就相当于给对象hello中的属性设置一个值
-->
<bean id="userImpl" class="com.AL.dao.UserDaoImpl"/>
<bean id="mysqlImpl" class="com.AL.dao.UserMysqlDaoImpl"/>
<bean id="oracle" class="com.AL.dao.UserOracleDaoImpl"/>
<bean id="UserServiceImpl" class="com.AL.service.UserServiceImpl">
<!-- collaborators and configuration for this bean go here -->
<!--注意: 这里的name并不是属性 , 而是set方法后面的那部分 , 首字母小写-->
<!--引用另外一个bean , 不是用value 而是用 ref-->
<property name="userDao" ref="mysqlImpl"/>
</bean>
<!--ref: 引用Spring中的容器
value: 表示具体的值,基本类型-->
<!-- more bean definitions go here -->
</beans>
2.测试:
import com.AL.dao.UserDaoImpl;
import com.AL.dao.UserMysqlDaoImpl;
import com.AL.dao.UserOracleDaoImpl;
import com.AL.service.UserService;
import com.AL.service.UserServiceImpl;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
public static void main(String[] args){
/**
//用户实际调用的是业务层。 不会接触 dao 层。
UserService userService = new UserServiceImpl();
//((UserServiceImpl) userService).setUserDao(new UserDaoImpl());
((UserServiceImpl) userService).setUserDao(new UserMysqlDaoImpl());
//((UserServiceImpl) userService).setUserDao(new UserOracleDaoImpl());
userService.getUser();
*/
解析beans.xml文件,获取 ApplicationContext, 得到Spring容器.生成管理相应的Bean对象
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
// 有了容器之后,想要什么,就去直接 get什么
UserServiceImpl userServiceImpl = (UserServiceImpl) context.getBean("UserServiceImpl");
userServiceImpl.getUser();
}
}
自己再理解一下:
ClassPathXmlAppliactionContext 获取beans.xml配置文件信息,上下文context容器信息和实现,得到Spring容器。里面有bean的信息和实现,可以去创建、管理、装配Bean对象。 有了这个容器Bean,那么想要什么,直接去get什么就可以了。
在beans.xml中:定义bean信息和实现。
- 原先的java语句:Hello hello = new Hello(); // 类型 变量名 = new 类型();
- 此时在bean中:id=变量名、 class=要new 的对象、name类似于属性,准确的说是set方法后面的那部分(首字母小写),value表示具体值,ref表示引用的容器
<bean id="mysqlImpl" class="com.AL.dao.UserMysqlImpl"/>
<bean id="UserServiceImpl" class="com.AL.service.UserServiceImpl">
<!-- collaborators and configuration for this bean go here -->
<!--注意: 这里的name并不是属性 , 而是set方法后面的那部分 , 首字母小写-->
<!--引用另外一个bean , 不是用value 而是用 ref-->
<property name="userDao" ref="mysqlImpl"/>
</bean>
<!--ref: 引用Spring中的容器
value: 表示具体的值,基本类型-->
set方法注入,提供一个接口。 Dependency Injection,DI 依赖注入。ClassPathXmlAppliactionContext
5、IOC创建对象的方式
DI依赖注入环境。依赖注入的方式有3种: 构造器注入、set注入、拓展方式注入。
5.1、构造器注入
- 使用无参构造创建对象, 默认的这是!!!
- 假设我们要使用有参构造创建对象
5.1.1、默认的无参构造器
例子: 便于理解:
1.构建一个实体类,User:
package com.AL.pojo;
public class User {
private String name;
public User() {
System.out.println("User的无参构造器!");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void show(){
System.out.println("name="+name);
}
}
2.Spring创建对象,设置 容器 bean:
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--使用Spring来创建对象,在Spring中 这些都称为容器 bean
类型 变量名 = new 类型();
Hello hello = new Hello
在这个bean中:
id = 变量名
class = new 的对象
property 就相当于给对象hello中的属性设置一个值
-->
<bean id="user" class="com.AL.pojo.User">
<!-- collaborators and configuration for this bean go here -->
<property name="name" value="鑫仔"/>
</bean>
<!-- more bean definitions go here -->
</beans>
3.测试:
import com.AL.pojo.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Mytest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//在执行getBean的时候, user已经创建好了 , 通过无参构造
User user =(User) context.getBean("user");
user.show();
}
}
结果: 默认走的是无参构造的方法;然后再运行 show()方法。
User的无参构造器!
name=鑫仔
在执行getBean的时候, user已经创建好了 , 通过无参构造。Spring中的Bean容器装配的时候,会直接走默认的无参构造方法。
修改:当没有无参构造的时候,初始化失败。 写有参就会默认把无参构造器干掉,除非你显示无参构造。
package com.AL.pojo;
public class User {
private String name;
public User(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void show(){
System.out.println("name="+name);
}
}
报错。报错内容如下所示:
警告: Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'user' defined in class path resource [beans.xml]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.AL.pojo.User]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.AL.pojo.User.<init>()
错误地创建bean容器,没有默认的构造器 No default constructor found。
5.1.2、有参构造器
使用构造器注入的方法: 使用有参构造创建对象的方法
https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#spring-core
通过有参构造方法来创建:
第一种:下标赋值:
You can use the index attribute to specify explicitly the index of constructor arguments, as the following example shows:
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg index="0" value="7500000"/>
<constructor-arg index="1" value="42"/>
</bean>
第二种:通过类型创建。 但是如果你有两个类型一样的,就会出错。
In the preceding scenario, the container can use type matching with simple types if you explicitly specify the type of the constructor argument by using the type attribute. as the following example shows:
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg type="int" value="7500000"/>
<constructor-arg type="java.lang.String" value="42"/>
</bean>
第三种:直接通过参数名来设置。
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg name="years" value="7500000"/>
<constructor-arg name="ultimateAnswer" value="42"/>
</bean>
第四种: 对类进行设置的。
<beans>
<bean id="beanOne" class="x.y.ThingOne">
<constructor-arg ref="beanTwo"/>
<constructor-arg ref="beanThree"/>
</bean>
<bean id="beanTwo" class="x.y.ThingTwo"/>
<bean id="beanThree" class="x.y.ThingThree"/>
</beans>
参考的狂神链接:https://mp.weixin.qq.com/s?__biz=Mzg2NTAzMTExNg==&mid=2247484096&idx=1&sn=c599734d2bc16a9a9c27a10731255bc9&scene=19#wechat_redirect
增加测试:再次创建一个 实体类 UserT。
1.实体类:
package com.AL.pojo;
public class UserT {
private String name;
public UserT() {
System.out.println("UserT被创建了");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void show(){
System.out.println("name="+name);
}
}
2.spring创建,容器 beans: 此时并没有给这个无参构造器 赋值.
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--使用Spring来创建对象,在Spring中 这些都称为容器 bean
类型 变量名 = new 类型();
Hello hello = new Hello
在这个bean中:
id = 变量名
class = new 的对象
property 就相当于给对象hello中的属性设置一个值
-->
<bean id="user" class="com.AL.pojo.User">
<!-- collaborators and configuration for this bean go here -->
<property name="name" value="鑫仔"/>
</bean>
<bean id="userT" class="com.AL.pojo.UserT">
<!-- 没有给这个无参构造器赋值-->
</bean>
<!-- more bean definitions go here -->
</beans>
测试 无参构造器的:
3.测试:观察测试结果:会发现,无论你有没有用这个UserT,它都被创建了。进行使用了。
import com.AL.pojo.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Mytest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//在执行getBean的时候, user已经创建好了 , 通过无参构造
User user =(User) context.getBean("user");
//调用对象的方法 .
user.show();
}
}
结果:
User的无参构造器!
UserT被创建了
name=鑫仔
总结:在配置文件加载的时候,容器中管理的对象就已经初始化了。会有bean的信息/定义和实现
Spring 就像婚介所,网站一样,它会把所有的信息加载,容器中所有的对象都会被初始化, 你需要什么,去调用就可以了。
设置有参构造器的 Spring创建对象 容器 bean:
对于有参构造器: 添加有参构造器。public UserT(String name)
public class UserT {
private String name;
public UserT(String name) {
this.name = name;
}
public void setName(String name) {
this.name = name;
}
public void show(){
System.out.println("name="+ name );
}
}
在 beans.xml 配置文件中进行 bean 容器中 信息的修改, 针对 有参构造器的:
<bean id="userT" class="com.AL.pojo.UserT">
<!--给这个有参构造器赋值-->
<!-- 第一种根据index参数下标设置 -->
<!-- index指构造方法 , 下标从0开始 -->
<constructor-arg index="0" value="ALZN00"/>
</bean>
<bean id="userT" class="com.AL.pojo.UserT">
<!--给这个有参构造器赋值-->
<!-- 第二种根据参数名字设置 -->
<!-- name指参数名 -->
<constructor-arg name="name" value="ALZN01"/>
</bean>
<bean id="userT" class="com.AL.pojo.UserT">
<!--给这个有参构造器赋值-->
<!-- 第三种根据参数类型设置 -->
<!-- name指参数名 -->
<constructor-arg type="java.lang.String" value="ALZN02"/>
</bean>
进行测试:
import com.AL.pojo.User;
import com.AL.pojo.UserT;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Mytest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//在执行getBean的时候, user已经创建好了 , 通过无参构造
User user =(User) context.getBean("user");
//调用对象的方法 .
user.show();
}
@Test
public void testT(){
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
UserT user = (UserT) context.getBean("userT");
user.show();
}
}
结果:
User的无参构造器!!!
name=ALZN00
总结:在配置文件加载的时候,容器中管理的对象就已经初始化了。
Spring 就像婚介所,网站一样,它会把所有的信息加载,容器中所有的对象都会被初始化, 你需要什么,去调用就可以了。
5.2、Spring配置
5.2.1、别名
关于Spring中 即 里面的方法功能:
其中的alias 就是别名方法。
1.别名:alias:在beans.xml容器中创建一个别名
alias 设置别名 , 为bean设置别名 , 可以设置多个别名
<!--设置别名:在获取Bean的时候可以使用别名获取-->
<alias name="userT" alias="userNew"/>
测试:如果添加了别名,我们也可以使用别名获取这个对象,当然,你也可以不使用。
@Test
public void testT(){
// Spring容器
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//UserT user = (UserT) context.getBean("userT");
// 别名
UserT user = (UserT) context.getBean("userNew");
user.show();
}
在配置文件加载的时候,容器中管理的对象就已经初始化了。
5.2.2、Bean的配置
<!--bean就是java对象,由Spring创建和管理-->
<!--
id 是bean的标识符,要唯一,如果没有配置id,name就是默认标识符
如果配置id,又配置了name,那么name是别名
name可以设置多个别名,可以用逗号,分号,空格隔开
如果不配置id和name,可以根据applicationContext.getBean(.class)获取对象;
class是bean的全限定名=包名+类名
-->
<bean id="hello" name="hello2 h2,h3;h4" class="com.kuang.pojo.Hello">
<property name="name" value="Spring"/>
</bean>
配置 bean:
<!--
id: bean 的唯一标识符,也就是相当于 对象名
class: bean 对象所对应的全限定名: 包名+类名
name:也是别名
-->
<bean id="user" class="com.AL.pojo.User" name="user2">
</bean>
测试:
// bean 配置
@Test
public void testT2(){
// Spring容器
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
User user = (User) context.getBean("user2");
user.show();
}
name可以同时取多个别名:多个别名可以用逗号 空格 分号进行隔开
<!--
id: bean 的唯一标识符,也就是相当于 对象名
class: bean 对象所对应的全限定名: 包名+类型
name:也是别名,而且name 可以取多个别名
-->
<bean id="user" class="com.AL.pojo.User" name="user2,u2,u3">
<property name="name" value="天下无敌"/>
</bean>
此时的beans,xml 文件中的代码为:有两个关于类User 的实例化, user 和 user1
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--使用Spring来创建对象,在Spring中 这些都称为容器 bean
类型 变量名 = new 类型();
Hello hello = new Hello
在这个bean中:
id = 变量名
class = new 的对象
property 就相当于给对象hello中的属性设置一个值
-->
<bean id="user" class="com.AL.pojo.User">
<property name="name" value="鑫仔"/>
</bean>
<!--
id: bean 的唯一标识符,也就是相当于 对象名
class: bean 对象所对应的全限定名: 包名+类型
name:也是别名,而且name 可以取多个别名
-->
<bean id="user1" class="com.AL.pojo.User" name="user2,u2,u3">
<property name="name" value="天下无敌"/>
</bean>
</beans>
测试 Test的代码为:
@Test
public void testBean() {
// 解析beans.xml文件,获取ApplicationContext,得到Spring容器,完成创建和装配。 加载的时候就会完成初始化
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
User user = (User) context.getBean("user1");
user.show();
User user2 = (User) context.getBean("user2");
user2.show();
}
结果为:
User的无参构造器!!!
User的无参构造器!!!
name=天下无敌
name=天下无敌
这表明,在**在配置文件加载的时候,容器中管理的对象就已经初始化了**。 而且因为是关于两个bean的信息,所以有默认无参构造器的 两个。
5.2.3、import
团队的合作通过import来实现 .
<import resource="{path}/beans.xml"/>
它可以将多个配置文件,导入合并为一个。
例如,此时项目中有多个人开发,这三个人负责的是不同的类开发,不同的类注册在不同的 bean中,我们可以利用 import将所有人的 beans.xml合并为一个。
-
applicationContext.xml
-
<import resource="beans.xml"/> <import resource="beans2.xml"/> <import resource="beans3.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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<import resource="beans.xml"/>
<import resource="beans2.xml"/>
<import resource="beans3.xml"/>
</beans>
测试:
@Test
public void testT2(){
// Spring容器
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = (User) context.getBean("u2");
user.show();
}
5.3、Set方式注入【重点】
- 依赖注入(Dependency Injection,DI)。
- 依赖 : 指Bean对象的创建依赖于容器 . Bean对象的依赖资源 .
- 注入 : 指Bean对象所依赖的资源 , 由容器来设置和装配 .
DI依赖注入环境。依赖注入的方式有3种: 构造器注入、set注入、拓展方式注入。
set注入:
要求被注入的属性 , 必须有set方法 , set方法的方法名由set + 属性首字母大写 , 如果属性是boolean类型 , 没有set方法 , 是 is .
1.复杂类型,测试pojo类 :
Address.java 类:
package com.AL.pojo;
public class Address {
private String address;
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
2.Student.java: 真实的测试对象。
package com.AL.pojo;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
public class Student {
private String name;
private Address address;
private String[] books;
private List<String> hobbys;
private Map<String,String> card;
private Set<String> games;
private String wife;
private Properties info;
public void setName(String name) {
this.name = name;
}
public void setAddress(Address address) {
this.address = address;
}
public void setBooks(String[] books) {
this.books = books;
}
public void setHobbys(List<String> hobbys) {
this.hobbys = hobbys;
}
public void setCard(Map<String, String> card) {
this.card = card;
}
public void setGames(Set<String> games) {
this.games = games;
}
public void setWife(String wife) {
this.wife = wife;
}
public void setInfo(Properties info) {
this.info = info;
}
public String getName() {
return name;
}
public Address getAddress() {
return address;
}
public String[] getBooks() {
return books;
}
public List<String> getHobbys() {
return hobbys;
}
public Map<String, String> getCard() {
return card;
}
public Set<String> getGames() {
return games;
}
public String getWife() {
return wife;
}
public Properties getInfo() {
return info;
}
public void show(){
System.out.println("name="+ name
+ ",address="+ address.getAddress()
+ ",books="
);
for (String book:books){
System.out.print("<<"+book+">>\t");
}
System.out.println("\n爱好:"+hobbys);
System.out.println("card:"+card);
System.out.println("games:"+games);
System.out.println("wife:"+wife);
System.out.println("info:"+info);
}
}
3.beans.xml 配置文件:Spring创建,容器beans创建:
第一种的普通值注入,value。
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--使用Spring来创建对象,在Spring中 这些都称为容器 bean
类型 变量名 = new 类型();
Hello hello = new Hello
在这个bean中:
id = 变量名
class = new 的对象
property 就相当于给对象hello中的属性设置一个值
-->
<bean id="student" class="com.AL.pojo.Student">
<property name="name" value="小明"/>
</bean>
</beans>
测试:
import com.AL.pojo.Student;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTset {
@Test
public void test01(){
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Student student = (Student) context.getBean("student");
System.out.println(student.getName());
System.out.println(student.getAddress());
}
}
依赖注入之set注入 :其它的值注入。
Bean注入:注意点:这里的值是一个引用,ref
由于这里的Address类,是我们自己定义的,所以我们要引入这个类,然后进行 property 定义值:
<bean id="address" class="com.AL.pojo.Address"/> <!--我们引入自定义的 Address 类-->
<bean id="student" class="com.AL.pojo.Student">
<!--第一种,普通值注入,value-->
<property name="name" value="小明"/>
<!--第二种,Bean注入,ref-->
<property name="address" ref="address"/>
</bean>
在这里, address 地址这个值,Bean注入 的值是一个引用, 你也可以在 这个Address类的 容器 bean中去配置信息。 然后在后面进行引用:
<bean id="address" class="com.AL.pojo.Address"> <!--我们引入自定义的 Address 类-->
<property name="address" value="重庆"/>
</bean>
<bean id="student" class="com.AL.pojo.Student">
<!--第一种,普通值注入,value-->
<property name="name" value="小明"/>
<!--第二种,Bean注入,ref-->
<property name="address" ref="address"/>
</bean>
其它的注入方式:
- 如果没有值的话,直接使用 null 就可以表示:
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--使用Spring来创建对象,在Spring中 这些都称为容器 bean
类型 变量名 = new 类型();
Hello hello = new Hello
在这个bean中:
id = 变量名
class = new 的对象
property 就相当于给对象hello中的属性设置一个值
-->
<bean id="address" class="com.AL.pojo.Address"/> <!--我们引入自定义的 Address 类-->
<bean id="student" class="com.AL.pojo.Student">
<!--第一种,普通值注入,value-->
<property name="name" value="小明"/>
<!--第二种,Bean注入,ref-->
<property name="address" ref="address"/>
<!--数组-->
<property name="books">
<array>
<value>西游记</value>
<value>红楼梦</value>
<value>水浒传</value>
</array>
</property>
<!--List注入-->
<property name="hobbys">
<list>
<value>听歌</value>
<value>看电影</value>
<value>爬山</value>
</list>
</property>
<!--Map注入-->
<property name="card">
<map>
<entry key="中国邮政" value="456456456465456"/>
<entry key="建设" value="1456682255511"/>
</map>
</property>
<!--set注入-->
<property name="games">
<set>
<value>LOL</value>
<value>BOB</value>
<value>COC</value>
</set>
</property>
<!--Null注入-->
<property name="wife"><null/></property>
<!--Properties注入-->
<property name="info">
<props>
<prop key="学号">20190604</prop>
<prop key="性别">男</prop>
<prop key="姓名">小明</prop>
</props>
</property>
</bean>
</beans>
测试:
import com.AL.pojo.Student;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTset {
@Test
public void test01(){
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Student student = (Student) context.getBean("student");
System.out.println(student.getName());
student.show();
}
}
结果:
name=小明,address=重庆,books=
<<西游记>> <<红楼梦>> <<水浒传>>
爱好:[听歌, 看电影, 爬山]
card:{中国邮政=456456456465456, 建设=1456682255511}
games:[LOL, BOB, COC]
wife:null
info:{学号=20190604, 性别=男, 姓名=小明}
5.4、c命名和p命名空间注入
手册文档中的例子:
在这种类型的注入:
- XML Shortcut with the p-namespace:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean name="classic" class="com.example.ExampleBean">
<property name="email" value="someone@somewhere.com"/>
</bean>
<bean name="p-namespace" class="com.example.ExampleBean"p:email="someone@somewhere.com"/>
</beans>
- 或者:c命名法:XML Shortcut with the c-namespace:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="beanTwo" class="x.y.ThingTwo"/>
<bean id="beanThree" class="x.y.ThingThree"/>
<!-- traditional declaration with optional argument names -->
<bean id="beanOne" class="x.y.ThingOne">
<constructor-arg name="thingTwo" ref="beanTwo"/>
<constructor-arg name="thingThree" ref="beanThree"/>
<constructor-arg name="email" value="something@somewhere.com"/>
</bean>
<!-- c-namespace declaration with argument names -->
<bean id="beanOne" class="x.y.ThingOne" c:thingTwo-ref="beanTwo" c:thingThree-ref="beanThree" c:email="something@somewhere.com"/>
</beans>
- p命名法对应着 set类型注入; properties 属性,set类型注入。
- c命名法对应着 构造器类型注入。 constructor 构造器
例子:
1.创建一个类,来进行测试:
package com.AL.pojo;
public class User {
private String name;
private int age;
public String getName() {
return name;
}
public int getAge() {
return age;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
2.userbeans.xml, 通过创建Spring,利用 spring中的容器 bean 完成 IOC 控制反转:
对于使用 p 命名空间注入时,需要在头文件中加入约束文件:
导入约束 : xmlns:p="http://www.springframework.org/schema/p"
**P(属性: properties)命名空间 , 属性依然要设置set方法.**set方法,即此时的 setName 等.,在这里可以直接注入属性的值:property. 所以说 p命名法对应着 set类型注入。
<?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:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--使用Spring来创建对象,在Spring中 这些都称为容器 bean
类型 变量名 = new 类型();
Hello hello = new Hello
在这个bean中:
id = 变量名
class = new 的对象
property 就相当于给对象hello中的属性设置一个值
-->
<!--P(属性: properties)命名空间 , 属性依然要设置set方法
set方法,即此时的 setName 等. 在这里可以直接注入属性的值:property-->
<bean id="user" class="com.AL.pojo.User" p:name="鑫鑫" p:age="18"/>
</beans>
3.测试: 注意:如果在这个 得到容器 bean中设置了类型,就不需要强转了。
原来的:
User user = (User) context.getBean("user");
现在的:
User user = context.getBean("user", User.class);
测试代码:
@Test
public void test02(){
// 解析xml文件,得到ApplicationContext。 获取Spring容器
ApplicationContext context = new ClassPathXmlApplicationContext("userbeans.xml");
// 如果在这个 得到容器 bean中设置了类型,就不需要强转了
User user = context.getBean("user", User.class);
System.out.println(user);
}
需要注意的是:还要导入 junit包:
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
例子2: c命名法进行依赖注入,类似于构造器方法。 需要在类中创建有参构造器。
1.类中增加有参构造器: 此时无参构造器也得写出来,显示出来。不然上面的方法会报错。
// 无参构造器
public User() {
}
// 有参构造器
public User(String name, int age) {
this.name = name;
this.age = age;
}
2.在 bean中创建 spring的方法:
在 c 命名空间注入 : 也需要在头文件中加入约束文件
导入约束 : xmlns:c="http://www.springframework.org/schema/c"
在没有写有参构造的时候 会爆红。
解决:把有参构造器加上,这里也能知道,c 就是所谓的构造器注入!
<?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:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--P(属性: properties)命名空间 , 属性依然要设置set方法
set方法,即此时的 setName 等. 在这里可以直接注入属性的值:property-->
<bean id="user" class="com.AL.pojo.User" p:name="鑫鑫" p:age="18"/>
<!--C(构造: Constructor)命名空间, 通过构造器注入-->
<bean id="user2" class="com.AL.pojo.User" c:name="鑫仔" c:age="18"/>
</beans>
测试:
import com.AL.pojo.Student;
import com.AL.pojo.User;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTset {
@Test
public void test01(){
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Student student = (Student) context.getBean("student");
System.out.println(student.getName());
System.out.println(student.getAddress());
System.out.println(student.toString());
student.show();
}
@Test
public void test02(){
ApplicationContext context = new ClassPathXmlApplicationContext("userbeans.xml");
User user = context.getBean("user", User.class);
System.out.println(user);
}
@Test
public void test03(){
ApplicationContext context = new ClassPathXmlApplicationContext("userbeans.xml");
User user = context.getBean("user2", User.class);
System.out.println(user);
}
}
总结:
-
P(属性: properties)命名空间 , 属性依然要设置set方法。通过set注入
-
C(构造: Constructor)命名空间, 通过构造器注入
-
需要导入 xml 头文件约束
xmlns:p="http://www.springframework.org/schema/p" xmlns:c="http://www.springframework.org/schema/c"
6、Bean的作用域
在Spring中,那些组成应用程序的主体及由Spring IoC容器所管理的对象,被称之为bean。简单地讲,bean就是由IoC容器初始化、装配及管理的对象 .
几种作用域中,request、session作用域仅在基于web的应用中使用(不必关心你所采用的是什么web应用框架),只能用在基于web的Spring ApplicationContext环境。
-
singleton : 唯⼀ bean 实例,Spring 中的 bean 默认都是单例的。
-
prototype : 每次请求都会创建⼀个新的 bean 实例。
-
request : 每⼀次HTTP请求都会产⽣⼀个新的bean,该bean仅在当前HTTP request内有效。
-
session : 每⼀次HTTP请求都会产⽣⼀个新的 bean,该bean仅在当前 HTTP session 内有效。
-
global-session: 全局session作⽤域,仅仅在基于portlet的web应⽤中才有意义,Spring5已经没有了。Portlet是能够⽣成语义代码(例如:HTML)⽚段的⼩型Java Web插件。它们基于portlet容器,可以像servlet⼀样处理HTTP请求。但是,与 servlet 不同,每个 portlet 都有不同的会话
sqlSession的作用域 最好是在方法作用域或者请求作用域。SqlSession sqlSession = MybatisUtils.getSqlSession(); 同样的是单例模式,为了保证并发安全。
一级缓存和二级缓存。默认的是一级缓存(Sqlsession级别):在同一个session会话中。二级缓存需要去开启,同一个 namespace 中下的查询。
1.singleton :单例模式
例子: Spring默认的机制就是单例模式。 不过我们也可以显示的表示出来
<bean id="accountService" class="com.something.DefaultAccountService"/>
<!-- the following is equivalent, though redundant (singleton scope is the default) -->
<bean id="accountService" class="com.something.DefaultAccountService" scope="singleton"/>
<!--C(构造: Constructor)命名空间 , 通过构造器注入-->
<bean id="user2" class="com.AL.pojo.User" c:name="鑫仔" c:age="18" scope="singleton"/>
2.prototype :原型模式:每次从容器中 get的时候,都会产生一个新的对象。
例子:将 scope 改为prototype 即变化为原型模式。
<bean id="accountService" class="com.something.DefaultAccountService" scope="prototype"/>
<!--C(构造: Constructor)命名空间 , 属性依然要设置set方法-->
<bean id="user2" class="com.AL.pojo.User" c:name="鑫仔" c:age="18" scope="prototype"/>
测试:观察取了两次的 user2 这个对象的 hashCode,会发现它们表示的 的确不一样。
@Test
public void test03(){
ApplicationContext context = new ClassPathXmlApplicationContext("userbeans.xml");
User user = context.getBean("user2", User.class);
User user2 = context.getBean("user2", User.class);
System.out.println(user.hashCode());
System.out.println(user2.hashCode());
System.out.println(user==user2);
}
结果:
1607305514
146305349
false
############ 下方的是单例模式下的测试结果, singleton
183284570
183284570
true
3.其余的request、session、application,这些只能在web开发中使用到。
Singleton
当一个bean的作用域为Singleton,那么Spring IoC容器中只会存在一个共享的bean实例,并且所有对bean的请求,只要id与该bean定义相匹配,则只会返回bean的同一实例。Singleton是单例类型,就是在创建起容器时就同时自动创建了一个bean的对象,不管你是否使用,他都存在了,每次获取到的对象都是同一个对象。注意,Singleton作用域是Spring中的缺省作用域。要在XML中将bean定义成singleton,可以这样配置:
<bean id="ServiceImpl" class="cn.csdn.service.ServiceImpl" scope="singleton">
测试:
@Test
public void test03(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = (User) context.getBean("user");
User user2 = (User) context.getBean("user");
System.out.println(user==user2);
}
Prototype
当一个bean的作用域为Prototype,表示一个bean定义对应多个对象实例。Prototype作用域的bean会导致在每次对该bean请求(将其注入到另一个bean中,或者以程序的方式调用容器的getBean()方法)时都会创建一个新的bean实例。Prototype是原型类型,它在我们创建容器的时候并没有实例化,而是当我们获取bean的时候才会去创建一个对象,而且我们每次获取到的对象都不是同一个对象。根据经验,对有状态的bean应该使用prototype作用域,而对无状态的bean则应该使用singleton作用域。在XML中将bean定义成prototype,可以这样配置:
<bean id="account" class="com.foo.DefaultAccount" scope="prototype"/>
或者
<bean id="account" class="com.foo.DefaultAccount" singleton="false"/>
Request
当一个bean的作用域为Request,表示在一次HTTP请求中,一个bean定义对应一个实例;即每个HTTP请求都会有各自的bean实例,它们依据某个bean定义创建而成。该作用域仅在基于web的Spring ApplicationContext情形下有效。考虑下面bean定义:
<bean id="loginAction" class=cn.csdn.LoginAction" scope="request"/>
针对每次HTTP请求,Spring容器会根据loginAction bean的定义创建一个全新的LoginAction bean实例,且该loginAction bean实例仅在当前HTTP request内有效,因此可以根据需要放心的更改所建实例的内部状态,而其他请求中根据loginAction bean定义创建的实例,将不会看到这些特定于某个请求的状态变化。当处理请求结束,request作用域的bean实例将被销毁。
Session
当一个bean的作用域为Session,表示在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。考虑下面bean定义:
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>
针对某个HTTP Session,Spring容器会根据userPreferences bean定义创建一个全新的userPreferences bean实例,且该userPreferences bean仅在当前HTTP Session内有效。与request作用域一样,可以根据需要放心的更改所创建实例的内部状态,而别的HTTP Session中根据userPreferences创建的实例,将不会看到这些特定于某个HTTP Session的状态变化。当HTTP Session最终被废弃的时候,在该HTTP Session作用域内的bean也会被废弃掉。
7、自动装配Bean
手动装配,前面的 还需要我们手动地在 xml中进行配置 bean。
自动的装配,就不需要我们动手操作了。
- 自动装配是Spring 满足bean依赖的 一种方式
- Spring会在上下文中自动寻找,并自动给了 bean 装配属性
Spring中bean有三种装配机制,分别是:
- 在xml中显式配置;
- 在java中显式配置;
- 隐式的bean发现机制和自动装配。【重要】
Spring的自动装配需要从两个角度来实现,或者说是两个操作:
- 组件扫描(component scanning):spring会自动发现应用上下文中所创建的bean;
- 自动装配(autowiring):spring自动满足bean之间的依赖,也就是我们说的IoC/DI;
组件扫描和自动装配组合发挥巨大威力,使得显示的配置降低到最少。
推荐不使用自动装配xml配置 , 而使用注解 .
XML和注解区别,
- 注解:是一种分散式的元数据,与源代码紧绑定。
- xml:是一种集中式的元数据,与源代码无绑定
7.1、使用XML实现自动装配
例子:
环境搭建:
一个人有两种宠物。
1.创建实体类: cat、dog、people类
对于Cat Dog 都有一个叫的方法:
package com.AL.pojo;
public class Cat {
public void shout(){
System.out.println("miaomiaomiao");
}
}
package com.AL.pojo;
public class Dog {
public void shout(){
System.out.println("wangwangwang");
}
}
人拥有这两个宠物:
package com.AL.pojo;
public class People {
private String name;
private Cat cat;
private Dog dog;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Cat getCat() {
return cat;
}
public void setCat(Cat cat) {
this.cat = cat;
}
public Dog getDog() {
return dog;
}
public void setDog(Dog dog) {
this.dog = dog;
}
@Override
public String toString() {
return "People{" +
"name='" + name + '\'' +
", cat=" + cat +
", dog=" + dog +
'}';
}
}
2.创建Spring,容器 beans.xml:完成 IOC 控制反转,进行 bean配置 信息和实现。
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="dog" class="com.AL.pojo.Dog"/>
<bean id="cat" class="com.AL.pojo.Cat"/>
<!-- collaborators and configuration for this bean go here -->
<bean id="people" class="com.AL.pojo.People">
<!-- collaborators and configuration for this bean go here -->
<property name="cat" ref="cat"/>
<property name="dog" ref="dog"/>
<property name="name" value="ALZN"/>
</bean>
</beans>
3.测试:
import com.AL.pojo.People;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
@Test
public void testMethodAutowire() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//People people = (People) context.getBean("people");
People people = context.getBean("people", People.class);
people.getCat().shout();
people.getDog().shout();
}
}
结果正常输出,表示环境搭建ok。
byName
autowire byName (按名称自动装配)
由于在手动配置xml过程中,常常发生字母缺漏和大小写等错误,而无法对其进行检查,使得开发效率降低。
采用自动装配将避免这些错误,并且使配置简单化。
byName自动装配:会自动在容器上下文中去查找,和自己对象set方法后面的值相对应的 bean id!
测试:
1、修改bean配置,增加一个属性 autowire=“byName”
<!--
byName自动装配:会自动在容器上下文中去查找,和自己对象set方法后面的值相对应的 bean id!
-->
<bean id="people2" class="com.AL.pojo.People" autowire="byName">
<property name="name" value="鑫仔"/>
</bean>
2、结果仍然可以正常输出。
3、我们将 cat 的bean id修改为 catXXX
4、再次测试, 执行时报**空指针java.lang.NullPointerException**。因为按byName规则找不对应set方法,真正的setCat就没执行,对象就没有初始化,所以调用时就会报空指针错误。
小结:
当一个bean节点带有 autowire byName的属性时。
- 将查找其类中所有的set方法名,例如setCat,获得将set去掉并且首字母小写的字符串,即cat。
- 去spring容器中寻找是否有此字符串名称id的对象。
- 如果有,就取出注入;如果没有,就报空指针异常。
byType
autowire byType (按类型自动装配)
使用autowire byType首先需要保证:同一类型的对象,在spring容器中唯一。如果不唯一,会报不唯一的异常。
NoUniqueBeanDefinitionException
byType自动装配: 它必须要保证 类型全局唯一,才能够使用ByType。
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="dog" class="com.AL.pojo.Dog"/>
<bean id="cat" class="com.AL.pojo.Cat"/>
<bean id="cat2" class="com.AL.pojo.Cat"/>
<!--
byType自动装配:会自动在容器上下文中去查找,和自己对象属性类型相同的 bean!
-->
<bean id="people3" class="com.AL.pojo.People" autowire="byType">
<property name="name" value="鑫仔"/>
</bean>
</beans>
出现错误:报错:NoUniqueBeanDefinitionException
nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.AL.pojo.Cat' available: expected single matching bean but found 2: cat,cat2
删除掉 cat2,将cat的bean名称改掉!测试!因为是按类型装配,所以并不会报异常,也不影响最后的结果。甚至将id属性去掉,也不影响结果。
这就是按照类型自动装配!
在byType中,没有名字,只有后面的类 class 也能成功。 因为它寻找的是对象的属性类型。如下所示:
<bean id="dog" class="com.AL.pojo.Dog"/>
<!-- <bean id="cat" class="com.AL.pojo.Cat"/>-->
<bean class="com.AL.pojo.Cat"/>
经过测试,仍然可以成功输出。
总结:
- byName的时候,需要保证所有 bean的 id 唯一,并且这个bean 需要 和 自动注入的属性的 set方法的值一致【即只能是 cat,而不能是cat1】
- byType的时候,需要保证所有 bean 的class唯一,并且这个bean需要和自动注入的属性的类型一致
7.2、使用注解实现自动装配
使用注解:
jdk1.5开始支持注解,spring2.5开始全面支持注解。
准备工作:利用注解的方式注入属性。
1、在spring配置文件中引入context文件头
xmlns:context="http://www.springframework.org/schema/context"
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
2、开启属性注解支持!
<context:annotation-config/>
整体的 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
https://www.springframework.org/schema/beans/springbeans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
</beans>
7.2.1、@Autowired
1.在xml配置文件中:
-
加入 context 约束 和加入aop 约束的的头文件:
-
并增加 bean 容器的配置 内容, 开启注解的支持:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
https://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--开启注解的支持-->
<context:annotation-config/>
<bean id="dog" class="com.AL.pojo.Dog"/>
<bean id="cat" class="com.AL.pojo.Cat"/>
<bean id="people" class="com.AL.pojo.People"/>
</beans>
2.在实体类中完成注解装配; 这里注解装配下的 id 要和beans.xml 中的 id 保持一样。
@Autowired
- @Autowired是按类型自动转配的,不支持id匹配。
- 需要导入 spring-aop的包!
所以在前面已经添加了 aop 约束头文件。
在People.java 文件中进行注解 装配的代码:
package com.AL.pojo;
import org.springframework.beans.factory.annotation.Autowired;
public class People {
private String name;
@Autowired
private Cat cat;
@Autowired
private Dog dog;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Cat getCat() {
return cat;
}
public void setCat(Cat cat) {
this.cat = cat;
}
public Dog getDog() {
return dog;
}
public void setDog(Dog dog) {
this.dog = dog;
}
@Override
public String toString() {
return "People{" +
"name='" + name + '\'' +
", cat=" + cat +
", dog=" + dog +
'}';
}
}
3.测试:能够成功输出结果。
@Test
public void testMethodAutowire() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//People people = (People) context.getBean("people");
People people = context.getBean("people", People.class);
people.getCat().shout();
people.getDog().shout();
}
这个注解装配,可以在属性上应用,也能直接在 set方法上面应用。 而且,你还可以把set方法删除,不用写。 因为注解是通过 反射实现的。
修改People.java的代码: 进行测试,仍然可以成功输出。
package com.AL.pojo;
import org.springframework.beans.factory.annotation.Autowired;
public class People {
private String name;
@Autowired
private Cat cat;
@Autowired
private Dog dog;
public String getName() {
return name;
}
public Cat getCat() {
return cat;
}
public Dog getDog() {
return dog;
}
}
@Autowired:
-
直接在属性上使用即可,也可以在 set方法上使用
-
使用Autowired 我们可以不用编写set 方法, 前提是 这个自动装配的属性在 IOC (Spring) 容器中存在,且符合名字 byName。【符合名字 byName 。自动装配的属性在 IOC容器中,即此时 这个 cat 应该在 xml 配置文件的 bean 中出现】
<bean class="com.AL.pojo.Cat"/> <bean id="cat" class="com.AL.pojo.Cat"/>
上面这两种都能成功输出。
名字符合byName:和自己对象set方法后面的值相对应的 bean id。如果是 catxxx 就会报错。
@Autowired(required=false) 说明:false,对象可以为null;true,对象必须存对象,不能为null。
//如果允许对象为null,设置required = false,默认为true
@Autowired(required = false)
private Cat cat;
如果想给 People.name赋值,可以采用Set方式注入:
<!--开启注解的支持-->
<context:annotation-config/>
<bean id="dog" class="com.AL.pojo.Dog"/>
<bean id="cat" class="com.AL.pojo.Cat"/>
<bean id="people" class="com.AL.pojo.People"/>
<bean id="people2" class="com.AL.pojo.People">
<property name="name" value="人人"/>
</bean>
测试代码:
@Test
public void test04() {
//解析xml文件,获取ApplicationContext Spring容器
ApplicationContext context = new ClassPathXmlApplicationContext("beansAnnotation.xml");
// 使用java注解自动配置
//People people = (People) context.getBean("people");
People people = context.getBean("people", People.class);
System.out.println(people.getName());
people.getCat().shout();
people.getDog().shout();
System.out.println("#####################");
People people2 = context.getBean("people2", People.class);
System.out.println(people2.getName());
people2.getCat().shout();
people2.getDog().shout();
}
结果:
null
miaomiaomiao
wangwangwang
#####################
人人
miaomiaomiao
wangwangwang
7.2.2、@Qualifier
当容器 beans.xml中有多个属性类型一样,但 id 名字不一样的,我们可以使用**@Qualifier 配合**
@Autowired 来指定其中特定的 id:
1.实体类中 注解自动装配,并指定特定 id 对象
@Autowired
@Qualifier(value = "cat2")
private Cat cat;
@Autowired
@Qualifier(value = "dog2")
private Dog dog;
2.配置文件修改内容,保证类型存在对象。且名字不为类的默认名字!
因为你的名字如果默认为 类的默认名字, 即 Cat 的默认就是 cat,Dog就是dog。 这样你不添加Qualifier去测试,不会报错, 它会去拥有 默认的那个。
在未添加 @Qualifier 时, 像下面这种就不会报错:【因为存在默认的名字,即 dog cat】
<bean id="dog" class="com.AL.pojo.Dog"/>
<bean id="dog2" class="com.AL.pojo.Dog"/>
<bean id="cat" class="com.AL.pojo.Cat"/>
<bean id="cat2" class="com.AL.pojo.Cat"/>
在未添加 @Qualifier 时, 像下面这种 直接报错:【因为不存在默认的名字,为 dog1 dog2 cat1 cat2】
<bean id="dog1" class="com.AL.pojo.Dog"/>
<bean id="dog2" class="com.AL.pojo.Dog"/>
<bean id="cat1" class="com.AL.pojo.Cat"/>
<bean id="cat2" class="com.AL.pojo.Cat"/>
错误:
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'people': Unsatisfied dependency expressed through field 'cat'; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.AL.pojo.Cat' available: expected single matching bean but found 2: cat1,cat2
对于xml中没有出现默认的 类的默认名字,那么@Autowired结合@Qualifier 使用:
@Autowired
@Qualifier(value = "cat1")
private Cat cat;
@Autowired
@Qualifier(value = "dog1")
private Dog dog;
7.2.3、Resource
@Resource:
- @Resource如有指定的name属性,先按该属性进行byName方式查找装配; @Resource(name = “cat2”)
- 其次再进行默认的byName方式进行装配;
- 如果以上都不成功,则按byType的方式自动装配。
- 都不成功,则报异常。
使用java自带的 @Resource 去完成注解装配。在这里,它会选择更高级的方式 去匹配目标,比如这里的属性 id=‘cat12313’ 不符合前面的 cat,但它会去匹配 后面的类型 找到Cat,也会完成注解装配。【先进行byName方式查找装配,然后再使用byType方式装配】
例子如下:
1.不一样的,不准确的属性 id
<bean id="dog1" class="com.AL.pojo.Dog"/>
<bean id="cat1" class="com.AL.pojo.Cat"/>
2.实体类中完成 @Resource注解装配:
private String name;
//@Autowired
//@Qualifier(value = "cat2")
@Resource
private Cat cat;
//@Autowired
//@Qualifier(value = "dog2")
@Resource
private Dog dog;
3.测试,此时能够成功输出。
@Test
public void testMethodAutowire() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//People people = (People) context.getBean("people");
People people = context.getBean("people", People.class);
people.getCat().shout();
people.getDog().shout();
}
科普小知识:
如果显示定义了 Autowired 的 required属性为 false,说明这个对象可以为 null,否则不允许为空
private String name;
// 如果显示定义了 Autowired 的 required属性为 false,说明这个对象可以为 null,否则不允许为空
//@Autowired(required = false)
@Resource(name = "cat2")
private Cat cat;
@Resource
private Dog dog;
使用java自带的 @Resource 去完成注解装配。在这里,它会选择更高级的方式 去匹配目标,比如这里的属性 id=‘cat1’ 不符合前面的 cat,但它会去匹配 后面的类型 找到Cat,也会完成注解装配。【先进行byName方式查找装配,然后再使用byType方式装配】
实体类:
public class User {
//如果允许对象为null,设置required = false,默认为true
@Resource(name = "cat2")
private Cat cat;
@Resource
private Dog dog;
private String str;
}
beans.xml
<bean id="dog" class="com.kuang.pojo.Dog"/>
<bean id="cat1" class="com.kuang.pojo.Cat"/>
<bean id="cat2" class="com.kuang.pojo.Cat"/>
<bean id="user" class="com.kuang.pojo.User"/>
测试:结果OK
配置文件2:beans.xml , 删掉cat2
<bean id="dog" class="com.kuang.pojo.Dog"/>
<bean id="cat1" class="com.kuang.pojo.Cat"/>
实体类上只保留注解
@Resource
private Cat cat;
@Resource
private Dog dog;
结果:OK
结论:先进行byName查找,失败;再进行byType查找,成功。
7.2.3、小结
@Autowired与@Resource异同:
1、@Autowired与@Resource都可以用来装配bean。都可以写在字段上,或写在set方法上。
2、@Autowired默认按类型装配(属于spring规范),默认情况下必须要求依赖对象必须存在,如果要允许null 值,可以设置它的required属性为false,如:@Autowired(required=false) ,如果我们想使用名称装配可以结合@Qualifier注解进行使用
3、@Resource(属于J2EE复返),默认按照名称进行装配,名称可以通过name属性进行指定。如果没有指定name属性,当注解写在字段上时,默认取字段名进行按照名称查找,如果注解写在setter方法上默认取属性名进行装配。当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。
它们的作用相同都是用注解方式注入对象,但执行顺序不同。@Autowired先byType,@Resource先byName。
8、使用注解开发
- bean容器
- 属性如何注入
- 衍生的注解
- 自动装配 配置
- 作用域
- 小结
在Spring4之后,要使用注解开发,必须要保证 aop 的jar包导入:
使用 context 约束的 Spring Beans的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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!--开启注解的支持-->
<context:annotation-config/>
</beans>
8.1、Bean的注解开发
前面的装配Bean的方式:
- xml配置:使用bean标签。自动装配的时候使用byName、byType方式。 autowire=“byName”。
- 对于注入的方式有:构造器注入、Set注入、c命名和p命名空间注入
- 在java中显式配置;@Autowired、@Qualifier、@Resource,然后配合着 xml配置开启注解(context:annotation-config) 去完成的
那么bean的注解开发方式:使用注解开发,不使用 xml配置(bean标签、开启注解)的方式。
例子: Bean 的注解开发实现
创建Maven项目:
1.创建每个层中的类和接口代码:
我们之前都是使用 bean 的标签进行bean注入,但是实际开发中,我们一般都会使用注解!
原来的bean 容器配置 信息方式如下所示,然后再使用@Autowired 注解进行装配 或者在xml配置文件中进行装配 和实现。
<bean id="user" class="com.kuang.pojo.User"/>
<!--开启注解的支持-->
<context:annotation-config/>
pojo 目标层中的实体类:
package com.AL.pojo;
import org.springframework.stereotype.Component;
@Component
public class User {
public String name = "鑫仔";
}
此时的 @Component 组件,其实等价于
配置文件xml:指定扫描包下的类能够进行注解装配, 即能够使用 bean 在pojo 实体类中。
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!--开启注解的支持-->
<context:annotation-config/>
<!--指定要扫描的包,这个包下的 注解 就会生效-->
<context:component-scan base-package="com.AL.pojo"/>
</beans>
测试: 尽管在 spring bean这个xml配置文件中没有定义具体的 bean 和id,但是使用注解后,在这个实体类 User中就相当于进行了 bean 的创建,且 id默认为是这个类名称的小写。 当然在得到这个 bean的代码中,可以直接写"user" 也可以写"User.calss"。
当然了,你在实体类中对于 bean 中的 id 可以定义: 这样的话,在测试代码中去获取容器中的信息就变为了: getBean(“user1”)
@Component("user1")
// 相当于配置文件中 <bean id="user" class="当前注解的类"/>
public class User {
public String name = "鑫仔";
}
测试代码: 我们仍然采用的一开始的 默认的@Component,没有设置 bean 中的 id
import com.AL.pojo.User;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
@Test
public void test(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = (User) applicationContext.getBean("user");
System.out.println(user.name);
}
}
注入属性:
那么使用这个注解开发, 如何注入属性呢:采用 @value 关键字。
package com.AL.pojo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
// 相当于配置文件中 <bean id="user" class="当前注解的类"/>
// @Component 组件
@Component
public class User {
// 相当于配置文件中 <property name="name" value="xinxin"/>
@Value("xinxin")
public String name;
}
衍生的注解:我们这些注解,就是替代了在配置文件 xml 当中配置步骤而已!更加的方便快捷!
@Component三个衍生注解
为了更好的进行分层,在 web 开发中,会按照 mvc 三层架构分层。Spring可以使用其它三个注解,功能一样,目前使用哪一个功能都一样。
- @Controller:web层
- @Service:service层
- @Repository:dao层
写上这些注解,就相当于将这个类交给Spring管理装配了!
自动装配配置:
- @Autowired: 自动装配 通过类型,名字
- 如果@Autowired 不能唯一自动装配属性,则还需要通过 @Qualifier(value = “xxx”)
- @Nullable: 字段标注了这个注解,表示这个字段可以为 null
- @Resource:自动装配 通过名字, 类型。先去匹配byName,后去进行byType。
关于@Component和@Autowired 注解之间的区别:
- @Component、@Bean 告诉 Spring:“这是这个类的一个实例,请保留它,并在我请求时将它还给我”。 将这个类的一个实例注册交管给Spring容器中
- @Autowired、@Qualifiter 说:“请给我一个这个类的实例,例如,一个我之前用@Bean注释创建的实例”。从Spring容器中获取这个类的实例。
作用域:
单例模式和 原型模式。
原来在xml配置中定义 bean的作用域:bean id=“accountService” class=“com.something.DefaultAccountService” scope=“singleton”/>
@scope
- singleton:默认的,Spring会采用单例模式创建这个对象。关闭工厂 ,所有的对象都会销毁。
- prototype:多例模式。关闭工厂 ,所有的对象不会销毁。内部的垃圾回收机制会回收
@Component
@Scope("prototype")
public class User {
@Value("鑫仔")
public String name;
}
小结:
XML与注解比较
- XML更加万能。可以适用任何场景 ,结构清晰,维护方便
- 注解不是自己提供的类使用不了,开发简单方便,维护相对复杂
xml与注解整合开发 :推荐最佳实践
- xml管理Bean
- 注解完成属性注入
- 使用过程中, 可以不用扫描,扫描是为了类上的注解
<!--开启注解的支持-->
<context:annotation-config/>
<!--指定要扫描的包,这个包下的 注解 就会生效-。 这个扫描可以不要-->
<context:component-scan base-package="com.AL.pojo"/>
作用:
-
进行注解驱动注册,从而使注解生效
-
用于激活那些已经在spring容器里注册过的bean上面的注解,也就是显示的向Spring注册
-
如果不扫描包,就需要手动配置bean。即在bean中进行如下的 信息
<bean id="user" class="当前注解的类"/> <bean id="user" class="com.kuang.pojo.User"/>
-
如果不加注解驱动,则注入的值为null!
8.2、使用JavaConfig实现配置
我们现在要完全 不使用Spring的 xml配置了,全权交给 java来做。
JavaConfig 原来是 Spring 的一个子项目,它通过 Java 类的方式提供 Bean 的定义信息,在 Spring4 的版本, JavaConfig 已正式成为 Spring4 的核心功能 。
例子: 创建一个maven项目进行测试:
pojo 实体类 User class:原来是 使用 注解装配 和 xml配置一起使用。
此时的注解装配如下所示:
package com.AL.pojo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component // 注解说明,表示创建使用 spring bean
public class User {
private String name;
public String getName() {
return name;
}
@Value("xinxin") // 属性注入
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
}
现在,创建 java config 配置:完成 xml配置文件中 xml管理 bean 的功能。
- public User getUser() 中这里的方法名就是 bean名字(dean的id), 即 @Bean下面的 getUser就是对应 bean容器中的 id : getUser。
- 这里的getUser就相当于bean标签 中的 id=“User”
package com.AL.config;
import com.AL.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ALConfig {
@Bean //通过方法注册一个bean,这里的返回值就Bean的类型,方法名就是bean的id!
public User getUser(){
return new User();
}
}
测试:
import com.AL.config.ALConfig;
import com.AL.pojo.User;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class MyTest {
@Test
public void test01() {
/**等价于ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
* 原先的是xml配置文件中寻找 bean 的配置信息
* 这里是JavaConfig 中定义了bean的配置信息
*/
ApplicationContext context = new AnnotationConfigApplicationContext(ALConfig.class);
// ALConfigz中的方法名就是bean的id! getUser()方法,则od为 getUser
//User getUser = (User) applicationContext.getBean("getUser");
User getUser = context.getBean("getUser", User.class);
System.out.println(getUser.getName());
}
}
结果:
xinxin
上方代码的解释:
对于 User 类上的 @Component ,我们在使用注解开发的时候了解过,它表示这个类在用注解开发,对应的在 xml配置文件中,开启 注解的支持,并扫描这个包下的类。
在这里呢,@Component 这个注解的意思和前面一样,就是说明这个类 被 Spring 接管了,注册到了容器中。 不过我们使用javaConfig 去进行配置容器。而不是使用xml文件去配置容器了。
@Component
public class User {
private String name;
public String getName() {
return name;
}
对于ALConfig 类 中的 @Configuration,它表示这个类 是一个配置类, 就和我们之前的 beans.xml 一样。 这个也会被Spring 容器托管,注册到容器中,因为它本来就是一个 @Component
package com.AL.config;
import com.AL.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ALConfig {
@Bean //通过方法注册一个bean,这里的返回值就Bean的类型,方法名就是bean的id!
public User getUser(){
return new User();
}
}
而对于 @Bean 来讲,这里就是注册一个 bean,相当于 我们之前写的一个 bean 标签; 这个方法的名字就 相当于 bean标签中的 id属性; 这个方法的返回值,就相当于 bean标签中的 class属性。
<bean id="user" class="当前注解的类"/>
<bean id="user" class="com.kuang.pojo.User"/>
如何将其它的 多个 beans.xml导入,一起使用。如下所示:
1、我们再编写一个配置类!
package com.AL.config;
import com.AL.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration //代表这是一个配置类
public class ALConfig2 {
@Bean //通过方法注册一个bean,这里的返回值就Bean的类型,方法名就是bean的id!
public User getUser2(){
return new User();
}
}
2、在之前的配置类中我们来选择导入这个配置类
package com.AL.config;
import com.AL.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@Configuration
@Import(ALConfig2.class) //导入合并其他配置类,类似于配置文件中的 inculde 标签
public class ALConfig {
// 注册一个 bean,相当于 我们之前写的一个 bean 标签
// 这个方法的名字就 相当于 bean标签中的 id属性
// 这个方法的返回值,就相当于 bean标签中的 class属性
@Bean //通过方法注册一个bean,这里的返回值就Bean的类型,方法名就是bean的id!
public User getUser(){
return new User();
}
}
3、测试:
import com.AL.config.ALConfig;
import com.AL.pojo.User;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class MyTest {
@Test
public void test(){
ApplicationContext applicationContext =
new AnnotationConfigApplicationContext(ALConfig.class);
User getUser = (User) applicationContext.getBean("getUser");
System.out.println(getUser.getName());
User getUser2 = (User) applicationContext.getBean("getUser2");
System.out.println(getUser2.getName());
}
}
在SpringBoot 和 SpringCloud中会看到大量的纯Java类的配置方式。
9、代理模式
为什么要学习代理模式,因为AOP的底层机制就是动态代理!【Spring AOP 和 Spring MVC】
代理模式:
- 静态代理
- 动态代理
学习aop之前 , 我们要先了解一下代理模式!
9.1、静态代理
静态代理角色分析
- 抽象角色 : 一般使用接口或者抽象类来实现
- 真实角色 : 被代理的角色
- 代理角色 : 代理真实角色 ; 代理真实角色后 , 一般会做一些附属的操作 .
- 客户 : 使用代理角色来进行一些操作 . 访问 代理对象 的人
代码步骤:
- 接口
- 真实角色
- 代理角色
- 客户端访问代理角色
代理模式的好处:
- 可以使真实角色更加纯粹,不用去关注一些公共的业务;
- 公共的业务由代理来完成,实现业务的分工;
- 公共业务要扩展的话,可以更加集中和方便管理;
缺点:
- 一个真实角色就会产生一个代理角色; 假如我们的真实角色变得非常多,代理类也会随之增多,工作量变大,开发效率变低!
例子:关于你想租房、 中介、房东的 租房代理事件:
1.接口: 租房接口,抽象类或者接口去 表示 抽象角色,即租房的这件 抽象接口.
package com.AL.demo01;
//抽象角色:租房
public interface Rent {
public void rent();
}
2.真实角色:房东:
package com.AL.demo01;
//真实角色: 房东,房东要出租房子
public class Host implements Rent{
public void rent() {
System.out.println("房东要出租房子了");
}
}
3.你去租房,即客户端去租房子了。
原来没有 代理 即中介的时候,你直接去找房东,房东出租就完事了。
package com.AL.demo01;
//客户类,一般客户都会去找代理!
public class Client {
public static void main(String[] args) {
//真实角色:房东要出租房
Host host = new Host();
host.rent();
}
}
4。代理角色: 房屋中介。
中介会去完成 房东出租房子这件业务, 也会一般增加一些附属操作。
package com.AL.demo01;
//代理角色:中介
public class Proxy implements Rent {
private Host host;
public Proxy() { }
public Proxy(Host host) {
this.host = host;
}
//租房
public void rent(){
seeHouse();
hetong();
host.rent();
fare();
}
//看房
public void seeHouse(){
System.out.println("带房客看房");
}
// 看房
public void hetong(){
System.out.println("签租赁合同");
}
//收中介费
public void fare(){
System.out.println("收中介费");
}
}
5.客户端访问代理角色:此时你再租房子,就会增加一个步骤,增加代理
对于访问代理对象, 即客户端去进行访问 代理,代理角色会代替真实角色的任务以及附属操作。 这时候,你不用面对房东,直接找中介租房即可。
package com.AL.demo01;
//客户类,一般客户都会去找代理!
public class Client {
public static void main(String[] args) {
//真实角色:房东要出租房
Host host = new Host();
//代理: 中介帮助房东出租房子, 但是 代理角色一般会有附属操作
Proxy proxy = new Proxy(host);
//客户端: 访问代理对像 的 人,即你去找中介! 客户端找代理,代理角色代替真实角色的任务和附属操作
proxy.rent();
}
}
分析:
在这个过程中,你直接接触的就是中介,就如同现实生活中的样子,你看不到房东,但是你依旧租到了房东的房子通过代理,这就是所谓的代理模式, 程序其实和生活中的例子一样,学会抽象的看待生活发生的业务。
- 租房子是一个业务,可以说是一个抽象接口,接口就只完成租房子,也可以说是一个抽象接口中的方法是租房子; 在这里它被称为 抽象角色。
- 而房东 和 我都要涉及到这个接口业务,即代码中的真实角色 和 客户端, 不过真实角色的业务 通常由代理角色来进行完成,且会有一些附属操作。
- 我即客户端 去访问代理 即中介 就能完成租房的这个业务,即完成真实角色房东 和 客户端 之间的业务联系。
这样在以后出现公共业务的时候 直接由代理去进行完成就 ok了,实现了业务的分工; 且这样在扩展的时候更便于集中和管理。
9.2、静态代理再理解
再来一个例子进行巩固。 此时我们完成的业务不是租房子这个业务。 而是增删查改。
Demo02例子:
1.接口: UserService 接口,抽象角色,表示平时做的用户业务,抽象起来就是增删改查!
package com.AL.demo02;
//抽象角色:增删改查业务
public interface UserService {
void add();
void delete();
void update();
void query();
}
2.接口实现类:真实对象,真实对象来完成这些增删改查操作
package com.AL.demo02;
//真实对象,完成增删改查操作的人
public class UserServiceImpl implements UserService {
public void add() {
System.out.println("增加了一个用户");
}
public void delete() {
System.out.println("删除了一个用户");
}
public void update() {
System.out.println("更新了一个用户");
}
public void query() {
System.out.println("查询了一个用户");
}
}
3.客户的操作:此时客户直接访问 真实角色,进行业务的完成
package com.AL.demo02;
public class Client {
public static void main(String[] args) {
//真实业务
UserServiceImpl userService = new UserServiceImpl();
userService.add();
}
}
如果真实对象每次变化需要记录,有一个日志:如下所示,手动添加会比较麻烦。
public void add() {
System.out.println("使用了add方法");
System.out.println("增加了一个用户");
}
public void delete() {
System.out.println("使用了delete方法");
System.out.println("删除了一个用户");
}
结果:
使用了add方法
增加了一个用户
从结果中可以发现,客户端直接访问真实角色的模式下,当需求增加时:
- 在业务发生改变的时候,我们就需要对原代码进行扩展【麻烦】
- 如果此时利用一个代理模式: 可以做到不改变原有业务 代码的情况下,进行扩展
4.设置一个代理类来处理原有业务,并增加新需求日志!代理角色
package com.AL.demo02;
//代理角色,在这里面增加日志的实现
public class UserServiceProxy implements UserService {
private UserServiceImpl userService;
public void setUserService(UserServiceImpl userService) {
this.userService = userService;
}
public void add() {
log("add");
userService.add();
}
public void delete() {
log("delete");
userService.delete();
}
public void update() {
log("update");
userService.update();
}
public void query() {
log("query");
userService.query();
}
public void log(String msg){
System.out.println("执行了"+msg+"方法");
}
}
5.此时客户端所作的工作:
package com.AL.demo02;
public class Client {
public static void main(String[] args) {
//真实业务
UserServiceImpl userService = new UserServiceImpl();
//代理类
UserServiceProxy proxy = new UserServiceProxy();
//使用代理类实现日志功能!
proxy.setUserService(userService);
proxy.add();
}
}
代理模式的其中的思想:
我们在不改变原来的代码的情况下,实现了对原有功能的增强,这是AOP中最核心的思想
在公司中,直接在原业务代码上进行更改,是大忌。所以新建一个类去通过接口连接最好。
AOP的简单描述: 纵向开发流程:从dao层开始,到service层即业务层 完成;再到controller层;最后到达前端。
再次回忆 代理模式的优缺点:
静态代理模式的好处:
- 可以使真实角色更加纯粹,不用去关注一些公共的业务;
- 公共的业务由代理来完成,实现业务的分工;
- 公共业务的要扩展的话,可以更加集中和方便管理;
缺点:
- 一个真实角色就会产生一个代理角色; 假如我们的真实角色变得非常多,代理类也会随之增多,工作量变大,开发效率变低!
对于业务租房这个接口:房东是真实角色,会产生一个代理角色 中介(进行租房)。代理角色可以管理房东出租房屋的业务,以及扩展中介费、服务费等公共业务。
对于CRUD用户业务这个接口:需要去产生一个代理角色agent,agent不仅去完成CRUD,还扩展日志业务。
那么,对于其它的真实角色,业务接口,就需要新建一个代理角色,代理类会越来越多。工作量和开发效率繁琐。
9.3、动态代理
动态代理:
-
动态代理的角色和静态代理的一样 .
-
动态代理的代理类是动态生成的,不是我们直接写好的。 静态代理的代理类是我们提前写好的
-
动态代理分为两类 : 一类是基于接口动态代理 , 一类是基于类的动态代理
- 基于接口的动态代理----JDK动态代理 【我们在这里使用】
- 基于类的动态代理-- cglib
- 现在用的比较多的是 javasist 来生成动态代理, java字节码实现
我们这里使用JDK的原生代码来实现,其余的道理都是一样的!、
这里需要了解两个类:Proxy:代理类, InvocationHandler:调用处理程序接口。
Proxy的官方文档介绍:
-
模块 java.base
-
软件包 java.lang.reflect 。 【表明这个是属于反射包的】
-
实现的所有接口
Serializable
-
Proxy提供了用于创建对象的静态方法,这些对象充当接口实例但允许自定义方法调用。要为某些接口
Foo
创建代理实例:InvocationHandler handler = new MyInvocationHandler(...); Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(), new Class<?>[] { Foo.class }, handler);
-
代理类是在运行时创建的类,它实现指定的接口列表,称为代理接口 。 代理实例是代理类的实例。 每个代理实例都有一个关联的调用
处理程序对象,该对象实现了接口
InvocationHandler
。 通过其代理接口之一对代理实例的方法调用将被分派到实例的调用处理程序的
invoke
方法,传递代理实例,标识被调用方法的java.lang.reflect.Method
对象,以及包含参数的类型Object
的数组。 调用处理程序适当地处理编码的方法调用,并且它返回的结果将作为代理实例上的方法调用的结果返回。
-
【自我理解: Proxy 即代理类, 它用于实现 某个接口,然后得到代理类的 实例,一个具体对象。且这个对象又实现了InvocationHandler接口(调用处理程序接口),然后再调用这个 调用程序接口 中的 invoke方法,且此方法返回的结果 就是 代理实例中的方法调用的结果, 即实现了 你最开始想要代理的 那个接口 中的 方法 】
InvocationHandler 的官方文档介绍:
- 模块 java.base
- 软件包 java.lang.reflect
- Interface InvocationHandler
public interface InvocationHandler
-
InvocationHandler是由代理实例的调用处理程序实现的接口。
-
每个代理实例都有一个关联的调用处理程序。 在代理实例上调用方法时,方法调用将被编码并调度到其调用处理程序的 invoke方法 。
-
方法详细信息
-
invoke
Object invoke(Object proxy, 方法 method, Object[] args) throws Throwable
处理代理实例上的方法调用并返回结果。 在与其关联的代理实例上调用方法时,将在调用处理程序上调用此方法。
-
参数
proxy
- 调用该方法的代理实例method
- 对应于在代理实例上调用的接口方法的方法
实例。方法
对象的声明类将是声明方法的接口,它可以是代理接口继承方法的代理接口的超接口。
args
- 包含代理实例上方法调用中传递的参数值的对象数组,如果接口方法不带参数,null
。 原始类型的参数包含在适当的原始包装类的实例中,例如
java.lang.Integer
或java.lang.Boolean
。
-
-
例子1:
对上一节中的案例:租房和代理进行处理分析:
1.接口:抽象角色,
package com.AL.demo03;
//抽象角色:租房
public interface Rent {
public void rent();
}
2.具体对象:具体角色,房东,即真实角色
package com.AL.demo03;
//真实角色: 房东,房东要出租房子
public class Host implements Rent {
public void rent() {
System.out.println("房东要出租房子了");
}
}
3.自动生成代理类:这个只是去生成一个,并不代表自己本身就是一个代理类了。
-
这个对象 实现了InvocationHandler 接口,且其中的 invoke 方法即调用处理程序方法 重写了。
-
在这里,利用 Proxy 代理类 去生成一个代理实例。
- Proxy.newProxyInstance(this.getClass().getClassLoader(), rent.getClass().getInterfaces(),this); // 其中的第二个参数,是利用反射机制,去想要 实现 Rent 接口, 生成一个 rent 租房的代理实例。【接口的代理实例,接口的动态代理。】
package com.AL.demo03;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 用这个类 去 自动生成代理类!
public class ProxyInvocationHandler implements InvocationHandler {
// 被代理的接口
private Rent rent;
public void setRent(Rent rent) {
this.rent = rent;
}
//生成代理类,重点是第二个参数,获取要代理的抽象角色!之前都是一个角色,现在可以代理一类角色
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(), rent.getClass().getInterfaces(),this);
}
// proxy : 代理类 method : 代理类的调用处理程序的方法对象.
// 处理代理实例上的方法调用并返回结果
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
seeHouse();
//核心:本质利用反射实现!
Object result = method.invoke(rent, args); // 这个是Proxy 去生成的代理实例中的,即实现某个接口的 接口本身的方法
fare();
return result;
}
//看房
public void seeHouse(){
System.out.println("带房客看房");
}
//收中介费
public void fare(){
System.out.println("收中介费");
}
}
4.客户端去调用代理: 要有真实角色、代理角色。此时的代理角色是 通过调用程序 来处理角色,来处理我们要调用的接口对象。
package com.AL.demo03;
//租客
public class Client {
public static void main(String[] args) {
//真实角色
Host host = new Host();
// 代理角色:现在没有
ProxyInvocationHandler pih = new ProxyInvocationHandler();
// 通过调用程序处理角色来 处理 我们要调用的接口对象
pih.setRent(host); //将真实角色放置进去!
Rent proxy = (Rent)pih.getProxy(); //动态生成对应的代理类!
proxy.rent();
}
}
核心:一个动态代理 , 一般代理某一类业务 , 一个动态代理可以代理多个类,代理的是接口!、
例子2:
对用户的增删查改,使用动态代理模式:
我们来使用动态代理实现代理我们后面写的UserService!
我们也可以编写一个通用的动态代理实现的类!所有的代理对象设置为Object即可!
1.自动生成代理类
package com.AL.demo04;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyInvocationHandler implements InvocationHandler {
private Object target;
public void setTarget(Object target) {
this.target = target;
}
//生成代理类
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
target.getClass().getInterfaces(),this);
}
// proxy : 代理类
// method : 代理类的调用处理程序的方法对象.
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log(method.getName());
Object result = method.invoke(target, args);
return result;
}
public void log(String methodName){
System.out.println("执行了"+methodName+"方法");
}
}
2.客户端去使用代理程序:
package com.AL.demo04;
import com.AL.demo02.UserService;
import com.AL.demo02.UserServiceImpl;
public class Client {
public static void main(String[] args) {
//真实对象
UserServiceImpl userService = new UserServiceImpl();
//代理对象的调用处理程序
ProxyInvocationHandler pih = new ProxyInvocationHandler();
pih.setTarget(userService); //设置要代理的对象
UserService proxy = (UserService)pih.getProxy(); //动态生成代理类!
proxy.delete();
}
}
3.当增加了几个方法,利用反射去处理方法,返回结果。这里为 method.getName():
实现动态代理的步骤:
- 生成一个 自动生成代理类。 这个代理类用于去创建 代理实例。
- 实现 InvocationHandler 接口,即实现 调用处理程序接口 类,
- 在这个类中定义了 生成代理类,代理实例的方法。
- 重写了 invoke 方法, 对应着代理接口的 方法调用返回结果
- 客户端进行调用的时候
- 使用代理对象的 调用处理程序
- 对调用处理程序中,设置要代理的对象 即 代理接口
- 使用调用处理程序这个生成的自动代理类中的 生成代理实例的方法, 去 动态生成代理类。
- 调用代理实例的方法。
动态代理的好处:
静态代理有的它都有,静态代理没有的,它也有!
- 可以使得我们的真实角色更加纯粹 . 不再去关注一些公共的事情 .
- 公共的业务由代理来完成 . 实现了业务的分工 ,
- 公共业务发生扩展时变得更加集中和方便 .
- 一个动态代理 , 一般代理某一类业务
- 一个动态代理可以代理多个类,代理的是接口!
10、AOP
10.1、什么是AOP
AOP(Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
10.2、Aop在Spring中的作用
提供声明式事务;允许用户自定义切面
- 横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志 , 安全 , 缓存 , 事务等等 …
- 切面(ASPECT):横切关注点 被模块化 的特殊对象。即,它是一个类。
- 通知(Advice):切面必须要完成的工作。即,它是类中的一个方法。
- 目标(Target):被通知对象。
- 代理(Proxy):向目标对象应用通知之后创建的对象。
- 切入点(PointCut):切面通知 执行的 “地点”的定义。
- 连接点(JointPoint):与切入点匹配的执行点。
SpringAOP中,通过Advice定义横切逻辑,Spring中支持5种类型的Advice: 即AOP在不改变原有代码的情况下,去增加新的功能。
1、前置通知(Before advice):在某连接点(JoinPoint)之前执行的通知,但这个通知不能阻止连接点前的执行。ApplicationContext 中在
aop:aspect 里面使用 aop:before 元素进行声明;
2、后置通知(After advice):当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。ApplicationContext 中在 aop:aspect 里面使用 aop:after 元素进行声明。
3、返回后通知(After return advice :在某连接点正常完成后执行的通知,不包括抛出异常的情况。ApplicationContext 中在 aop:aspect 里面
使用 <> 元素进行声明。
4、环绕通知(Around advice):包围一个连接点的通知,类似 Web 中 Servle t规范中的 Filter 的 doFilter 方法。可以在方法的调用前后完成自定义的行为,也可以选择不执行。ApplicationContext 中在 aop:aspect 里面使用 aop:around 元素进行声明。
5、抛出异常后通知(After throwing advice): 在方法抛出异常退出时执行的通知。 ApplicationContext 中在 aop:aspect 里面使用 aop:after-throwing 元素进行声明。
切入点表达式 :如execution(* com.spring.service…(…))
10.3、使用Spring实现AOP
使用AOP织入时的 ,需要导入 maven仓库依赖包:
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>
例子: 创建一个maven项目:spring-09-aop。关于增删改查,使用Spring去完成AOP。
第一种方式:通过 Spring API 实现
1.接口:UserService,
package com.AL.service;
public interface UserService {
public void add();
public void delete();
public void update();
public void search();
}
2.接口实现类:UserServiceImpl。
package com.AL.service;
public class UserServiceImpl implements UserService{
public void add() {
System.out.println("增加了一个用户");
}
public void delete() {
System.out.println("删除了一个用户");
}
public void update() {
System.out.println("更新了一个用户");
}
public void search() {
System.out.println("查询了一个用户");
}
}
3.原先是写一个动态代理去做这些事情, 在这里使用 Spring去做;
方式一:使用Spring中的API接口去做,日志:
前置通知:
package com.AL.log;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
public class Log implements MethodBeforeAdvice {
//method : 要执行的目标对象的方法
//objects : 被调用的方法的参数
//Object : 目标对象
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println( o.getClass().getName() + "的" + method.getName() + "方法被执行了");
}
}
后置通知:
package com.AL.log;
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;
public class AfterLog implements AfterReturningAdvice {
//returnValue 返回值
//method被调用的方法
//args 被调用的方法的对象的参数
//target 被调用的目标对象
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("执行了" + target.getClass().getName()
+"的"+method.getName()+"方法,"
+"返回值:"+returnValue);
}
}
4.把这些类注册到 Spring中: 在这里进行配置 AOP。
注意:这里需要加入 aop 的头文件约束
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--注册bean-->
<bean id="userService" class="com.AL.service.UserServiceImpl"/>
<bean id="log" class="com.AL.log.Log"/>
<bean id="afterLog" class="com.AL.log.AfterLog"/>
<!-- 方式一: 使用原生 Spring API接口-->
<!--aop的配置-->
<aop:config>
<!--切入点 expression:表达式匹配要执行的方法-->
<aop:pointcut id="pointcut" expression="execution(* com.AL.service.UserServiceImpl.*(..))"/>
<!--执行环绕增加; advice-ref执行方法 . pointcut-ref切入点-->
<aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>
</beans>
5.测试: 动态代理代理的是接口。
import com.AL.service.UserService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//动态代理代理的是接口。
UserService userService = (UserService) context.getBean("userService");
userService.search();
}
}
结果:
com.AL.service.UserServiceImpl的search方法被执行了
查询了一个用户
执行了com.AL.service.UserServiceImpl的search方法,返回值:null
Aop的重要性 : 很重要 . 一定要理解其中的思路 , 主要是思想的理解这一块 .
Spring的Aop就是将公共的业务 (日志 , 安全等) 和领域业务结合起来 , 当执行领域业务时 , 将会把公共业务加进来 . 实现公共业务的重复利用 . 领域业务更纯粹 , 程序猿专注领域业务 , 其本质还是动态代理 .
第二种方式:自定义类来实现 Aop
1.自定义的方法类:
package com.AL.diy;
public class DiyPointCut {
public void before(){
System.out.println("---------方法执行前---------");
}
public void after(){
System.out.println("---------方法执行后---------");
}
}
2.spring中,注册bean,采用自定义类:
<!--第二种方式自定义实现-->
<!--注册bean-->
<bean id="diy" class="com.AL.diy.DiyPointCut"/>
<!--aop的配置-->
<aop:config>
<!--第二种方式:使用AOP的标签实现-->
<aop:aspect ref="diy">
<aop:pointcut id="diyPonitCut" expression="execution(* com.AL.service.UserServiceImpl.*(..))"/>
<aop:before pointcut-ref="diyPonitCut" method="before"/>
<aop:after pointcut-ref="diyPonitCut" method="after"/>
</aop:aspect>
</aop:config>
3.测试。
第三种方式:使用注解实现
1.使用注解去实现,在这里定义了切点和切面。
package com.AL.diy;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class AnnotationPointCut {
@Before("execution(* com.AL.service.UserServiceImpl.*(..))")
public void before(){
System.out.println("---------方法执行前---------");
}
@After("execution(* com.AL.service.UserServiceImpl.*(..))")
public void after(){
System.out.println("---------方法执行后---------");
}
@Around("execution(* com.AL.service.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("环绕前");
System.out.println("签名:"+jp.getSignature());
//执行目标方法proceed
Object proceed = jp.proceed();
System.out.println("环绕后");
System.out.println(proceed);
}
}
2.在spring中的注册:在Spring配置文件中,注册bean,并增加支持注解的配置.
<!--第三种方式:注解实现-->
<bean id="annotationPointcut" class="com.AL.diy.AnnotationPointCut"/>
<!--开启注解支持-->
<aop:aspectj-autoproxy/>
3.测试。
aop:aspectj-autoproxy:说明
通过aop命名空间的<aop:aspectj-autoproxy />声明自动为spring容器中那些配置@aspectJ切面的bean创建代理,织入切面。当然,spring 在内部依旧采用AnnotationAwareAspectJAutoProxyCreator进行自动代理的创建工作,但具体实现的细节已经被<aop:aspectj-autoproxy />隐藏起来了
<aop:aspectj-autoproxy />有一个proxy-target-class属性,默认为false,表示使用jdk动态代理织入增强,当配为<aop:aspectj-autoproxy poxy-target-class="true"/>时,表示使用CGLib动态代理技术织入增强。不过即使proxy-target-class设置为false,如果目标类没有声明接口,则spring将自动使用CGLib动态代理。
11、整合Mybatis
步骤:
- 导入相关的 jar 包
- junit
- mybatis
- mysql数据库
- spring相关的
- aop织入
- mybatis-spring
- 编写 xml 配置文件
- 测试
1.导入相关的jar包, maven依赖:
<?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">
<parent>
<artifactId>spring-study</artifactId>
<groupId>com.AL</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-10-mybatis</artifactId>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.23</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.5</version>
</dependency>
<!--Spring操作数据库的话,还需要一个spring-jdbc-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.4</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.6</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.18</version>
</dependency>
</dependencies>
</project>
11.1、回忆Mybatis
- 编写实体类
- 编写核心配置文件,连接数据库的信息配置,mybatis-config.xml
- 编写 mapper 接口,dao层接口
- 编写接口实现类, Mapper.xml完成,
- 测试
创建一个maven项目,
1.编写实体类: 利用了 lmbok
package com.AL.pojo;
import lombok.Data;
@Data
public class User {
private int id; //id
private String name; //姓名
private String pwd; //密码
}
2.编写核心配置文件:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--XML 配置文件或者预先定义的一个 config,去SqlSessionFactoryBuilder, 配置数据库,便于创建sqlSessionFactory实例-->
<configuration>
<typeAliases>
<package name="com.AL.pojo"/>
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&charsetEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<!--关联映射文件-->
<mappers>
<package name="com.AL.mapper"/>
<!-- <mapper class="com.AL.mapper.UserMapper"/>-->
<!-- <mapper resource="com/AL/mapper/UserMapper.xml"/>-->
</mappers>
</configuration>
3.编写接口:
package com.AL.mapper;
import com.AL.pojo.User;
import java.util.List;
public interface UserMapper {
public List<User> selectUser();
}
4.编写Mapper.xml, 接口实现类的完成,sql语句的完成
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.AL.mapper.UserMapper">
<select id="selectUser" resultType="com.AL.pojo.User">
select * from mybatis.user
</select>
</mapper>
5.测试:
import com.AL.mapper.UserMapper;
import com.AL.pojo.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class MyTest {
@Test
public void selectUser() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = mapper.selectUser();
for (User user: userList){
System.out.println(user);
}
sqlSession.close();
}
}
11.2、整合Mybatis方式一
Mybatis-Spring学习
关于mybatis-sping的官网学习文档:http://mybatis.org/spring/zh/index.html
需要注意的地方:MyBatis-Spring 需要以下版本:版本匹配的要求限制。
MyBatis-Spring | MyBatis | Spring Framework | Spring Batch | Java |
---|---|---|---|---|
2.0 | 3.5+ | 5.0+ | 4.0+ | Java 8+ |
1.3 | 3.4+ | 3.2.2+ | 2.1+ | Java 6+ |
MyBatis-Spring 会帮助你将 MyBatis 代码无缝地整合到 Spring 中。它将允许 MyBatis 参与到 Spring 的事务管理之中,创建映射器 mapper 和 SqlSession
并注入到 bean 中,以及将 Mybatis 的异常转换为 Spring 的 DataAccessException
。 最终,可以做到应用代码不依赖于 MyBatis,Spring 或 MyBatis-Spring。
如果使用 Maven 作为构建工具,仅需要在 pom.xml 中加入以下代码即可:
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.2</version>
</dependency>
要和 Spring 一起使用 MyBatis,需要在 Spring 应用上下文中定义至少两样东西:一个 SqlSessionFactory 和至少一个数据映射器类。
在 MyBatis-Spring 中,可使用SqlSessionFactoryBean来创建 SqlSessionFactory。要配置这个工厂 bean,只需要把下面代码放在 Spring 的 XML 配置文件中:
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
</bean>
注意:SqlSessionFactory需要一个 DataSource(数据源)。这可以是任意的 DataSource,只需要和配置其它 Spring 数据库连接一样配置它就可以了。
在基础的 MyBatis 用法中,是通过 SqlSessionFactoryBuilder 来创建 SqlSessionFactory 的。而在 MyBatis-Spring 中,则使用 SqlSessionFactoryBean 来创建。
在 MyBatis 中,你可以使用 SqlSessionFactory 来创建 SqlSession。一旦你获得一个 session 之后,你可以使用它来执行映射了的语句,提交或回滚连接,最后,当不再需要它的时候,你可以关闭 session。
SqlSessionFactory有一个唯一的必要属性:用于 JDBC 的 DataSource。这可以是任意的 DataSource 对象,它的配置方法和其它 Spring 数据库连接是一样的。
一个常用的属性是 configLocation,它用来指定 MyBatis 的 XML 配置文件路径。它在需要修改 MyBatis 的基础配置非常有用。通常,基础配置指的是 < settings> 或 < typeAliases>元素。
需要注意的是,这个配置文件并不需要是一个完整的 MyBatis 配置。确切地说,任何环境配置(),数据源()和 MyBatis 的事务管理器()都会被忽略。SqlSessionFactoryBean 会创建它自有的 MyBatis 环境配置(Environment),并按要求设置自定义环境的值。
SqlSessionTemplate 是 MyBatis-Spring 的核心。作为 SqlSession 的一个实现,这意味着可以使用它无缝代替你代码中已经在使用的 SqlSession。
模板可以参与到 Spring 的事务管理中,并且由于其是线程安全的,可以供多个映射器类使用,你应该总是用 SqlSessionTemplate 来替换 MyBatis 默认的 DefaultSqlSession 实现。在同一应用程序中的不同类之间混杂使用可能会引起数据一致性的问题。
可以使用 SqlSessionFactory 作为构造方法的参数来创建 SqlSessionTemplate 对象。
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory" />
</bean>
现在,这个 bean 就可以直接注入到你的 DAO bean 中了。你需要在你的 bean 中添加一个 SqlSession 属性,就像下面这样:
public class UserDaoImpl implements UserDao {
private SqlSession sqlSession;
public void setSqlSession(SqlSession sqlSession) {
this.sqlSession = sqlSession;
}
public User getUser(String userId) {
return sqlSession.getMapper...;
}
}
按下面这样,注入 SqlSessionTemplate:
<bean id="userDao" class="org.mybatis.spring.sample.dao.UserDaoImpl">
<property name="sqlSession" ref="sqlSession" />
</bean>
整合实现方式一:
此时spring和mybatis整合进行处理的步骤为:
- 编写数据源配置
- sqlSessionFactory
- sqlSessionTemplate
- 需要给接口加实现类
- 将自己写的实现类,注入到Spring中
- 测试使用
1、引入Spring配置文件spring-dao.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
2、配置数据源替换mybaits的数据源.
使用Spring的数据源替换mybatis的配置:
<!--配置数据源:数据源有非常多,可以使用第三方的, 如: c3p0 dbcp druid
也可使使用Spring的:org.springframework.jdbc.datasource
-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=utf8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>
3、配置SqlSessionFactory,关联MyBatis
此时的Spring的数据源替换mybatis配置的xml文件为: 那么定义了关于 sqlSessionFactory的东西,就不用再new去新建一个sqlSessionFactory了。
<!--配置SqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!--关联Mybatis-->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<property name="mapperLocations" value="classpath:com/AL/mapper/*.xml"/>
</bean>
4、那么如何创建一个sqlSession?注册**sqlSessionTemplate**,关联sqlSessionFactory;
只能使用 构造器注入sqlSessionFactory,因为它没有 set 方法。
<!--注册sqlSessionTemplate , 关联sqlSessionFactory-->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<!--利用构造器注入-->
<constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>
5、使用类完成的一个sqlSession: 给接口加实现类。
增加Dao接口的实现类;私有化sqlSessionTemplate
package com.AL.mapper;
import com.AL.pojo.User;
import org.mybatis.spring.SqlSessionTemplate;
import java.util.List;
public class UserMapperImpl implements UserMapper {
//sqlSession不用我们自己创建了,Spring来管理,使用 SqlSessionTemplate
private SqlSessionTemplate sqlSession;
public void setSqlSession(SqlSessionTemplate sqlSession) {
this.sqlSession = sqlSession;
}
public List<User> selectUser() {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
return mapper.selectUser();
}
}
6、注册bean实现。 对应的,需要将这个注册到spring中:
<bean id="userMapper" class="com.AL.mapper.UserMapperImpl">
<property name="sqlSession" ref="sqlSession"/>
</bean>
7、测试
@Test
public void test2(){
ApplicationContext context = new ClassPathXmlApplicationContext("spring-dao.xml");
UserMapper mapper = context.getBean("userMapper",UserMapper.class);
List<User> user = mapper.selectUser();
System.out.println(user);
}
结果成功输出!现在我们的Mybatis配置文件的状态!发现都可以被Spring整合!
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<typeAliases>
<package name="com.AL.pojo"/>
</typeAliases>
</configuration>
11.3、整合Mybatis方式二
mybatis-spring1.2.3版以上的才有这个 .
dao继承Support类 , 直接利用 getSqlSession() 获得 , 然后直接注入SqlSessionFactory . 比起方式1 , 不需要管理SqlSessionTemplate , 而且对事务的支持更加友好 . 可跟踪源码查看
测试:
1、将我们上面写的UserDaoImpl类似,创建一个 UserDaoImpl2 接口实现类。
public class UserMapperImpl2 extends SqlSessionDaoSupport implements UserMapper {
public List<User> selectUser() {
UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
return mapper.selectUser();
}
}
2、修改bean的配置
<bean id="userMapper2" class="com.AL.mapper.UserMapperImpl2">
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
3、测试
@Test
public void test2(){
ApplicationContext context = new ClassPathXmlApplicationContext("spring-dao.xml");
UserMapper mapper = (UserMapper) context.getBean("userMapper");
List<User> user = mapper.selectUser();
System.out.println(user);
}
总结 : 整合到spring以后可以完全不要mybatis的配置文件,除了这些方式可以实现整合之外,我们还可以使用注解来实现,这个等我们后面学习SpringBoot的时候还会测试整合!
12、声明式事务
12.1 、 回 顾 事 务
-
把 一 组 业 务 当 成 一 个 业 务 来 做 ; 要 么 都 成 功 , 要 么 都 失 败 !
-
事 务 在 项 目 开 发 中 , 十 分 的 重 要 , 涉 及 到 数 据 的 一 致 性 问 题 , 不 能 马 虎 !
-
确 保 完 整 性 和 一 致 性 ;
事 务 ACID 原 则 :
-
原 子 性
-
一 致 性
-
隔 离 性 :多 个 业 务 可 能 操 作 同 一 个 资 源 , 防 止 数 据 损 坏
-
持 久 性 :事 务 一 旦 提 交 , 无 论 系 统 发 生 什 么 问 题 , 结 果 都 不 会 再 被 影 响 , 被 持 久 化 的
写 到 存 储 器 中 !
例子:创建一个maven项目,为 spring-11-transaction,关于事务的。
1.导入依赖:
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.23</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.5</version>
</dependency>
<!--Spring操作数据库的话,还需要一个spring-jdbc-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.4</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.6</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.18</version>
</dependency>
</dependencies>
2.创建实体类、dao(mapper)接口:
实体类 User:
package com.AL.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int id;
private String name;
private String pwd;
}
mapper接口:
package com.AL.mapper;
import com.AL.pojo.User;
import java.util.List;
public interface UserMapper {
public List<User> selectUser();
//添加一个用户
int addUser(User user);
//根据id删除用户
int deleteUser(int id);
}
mapper.xml配置文件,我们故意把 deletes 写错,测试!
<?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">
<!--configuration 核心配置文件-->
<mapper namespace="com.AL.mapper.UserMapper">
<select id="selectUser" resultType="com.AL.pojo.User">
select * from mybatis.user;
</select>
<insert id="addUser" parameterType="com.AL.pojo.User">
insert into mybatis.user (id,name,pwd) values (#{id},#{name},#{pwd})
</insert>
<delete id="deleteUser" parameterType="int">
deletes from mybatis.user where id = #{id}
</delete>
</mapper>
3.dao层中,采用UserMapperImpl实体类, 完成接口实体类的工作。
此时需要添加接口中对应的两种方法,即 add 和delete
package com.AL.mapper;
import com.AL.pojo.User;
import org.mybatis.spring.support.SqlSessionDaoSupport;
import java.util.List;
public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper {
//增加一些操作
public List<User> selectUser() {
User user = new User(4,"小明","123456");
UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
mapper.addUser(user);
mapper.deleteUser(4);
return mapper.selectUser();
}
//新增
public int addUser(User user) {
UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
return mapper.addUser(user);
}
//删除
public int deleteUser(int id) {
UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
return mapper.deleteUser(id);
}
}
4.测试:
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("spring-dao.xml");
UserMapper mapper = (UserMapper) context.getBean("userMapper");
List<User> user = mapper.selectUser();
System.out.println(user);
}
会发现,此时事务失败了,但是仍然插入了数据,代表存在着问题。
注意:需要添加配置资源导出依赖:
<build>
<!--希望maven在导出项目的时候,能够将我们的配置及资源导出-->
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
12.2、spring中的事务管理
- 声 明 式 事 务 : AOP
- 编 程 式 事 务 : 需 要 在代 码 中 , 进 行 事 务 的 管 理
例子:对于上一节这种的错误,如何去解决?
在spring-dao.xml文件中配置声明式事务:
<!-- 配置声明式事务-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
还需要导入tx事务:头文件的约束导入
xmlns:tx="http://www.springframework.org/schema/tx"
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
完整的头文件约束,还包括 aop 的:
<?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"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
配置事务通知和 配置事务切入:
<!--配置事务通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!--配置哪些方法使用什么样的事务,配置事务的传播特性-->
<tx:method name="add" propagation="REQUIRED"/>
<tx:method name="delete" propagation="REQUIRED"/>
<tx:method name="update" propagation="REQUIRED"/>
<tx:method name="search*" propagation="REQUIRED"/>
<tx:method name="get" read-only="true"/>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!--配置aop织入事务-->
<aop:config>
<aop:pointcut id="txPointcut" expression="execution(* com.AL.mapper.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>
修改之后,此时进行测试: 此时事务不成功。
spring中的 propagation有七种配置,支持7种事务传播行为。如下所示:
- propagation_requierd:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,这是最常见的选择。
- propagation_supports:支持当前事务,如果没有当前事务,就以非事务方法执行。
- propagation_mandatory:使用当前事务,如果没有当前事务,就抛出异常。
- propagation_required_new:新建事务,如果当前存在事务,把当前事务挂起。
- propagation_not_supported:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
- propagation_never:以非事务方式执行操作,如果当前事务存在则抛出异常。
- propagation_nested:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与propagation_required类似的操作
思 考 : 为 什 么 需 要 事 务 ?
- 如 果 不 配 置 事 务 , 可 能 存 在 数 据 提 交 不 一 致 的 情 况 下 ;
- 如 果 我 们 不 在 SPRING 中 去 配 置 声 明 式 事 务 , 我 们 就 需 要 在 代 码 中 手 动 配 置 事 务 !
- 事 务 在 项 目 的 开 发 中 十 分 重 要 , 涉及 到 数 据 的 一 致 性 和 完 整 性 问 题 , 不 容 马 虎