1. MyBatis
1.1 基本概念
- 三层架构
- 表现层:用于展示数据
- 业务层:处理业务需求
- 持久层:和数据库交互
-
持久层技术解决方案
- JDBC技术:Connection,PreparedStatement,ResultSet
- Spring的JdbcTemplate:Spring中对jdbc的简单封装
- Apache的DBUtils:和2很想,也是简单封装
- 以上都不是框架,JDBC是规范,JdbcTemplate和DBUtils都只是工具类
-
mybatis的概述
- mybatis是一个持久层框架,用java编写的。它封装了jdbc操作的很多细节,使开发者只需要关注sql语句本身,而无需关注注册驱动,创建连接等繁杂过程,它使用了ORM思想实现了结果集的封装
- ORM:Object Relational Mapping 对象关系映射
- 就是把数据库表和实体类及实体类的属性对应起来,让我们可以操作实体类就实现操作数据库表
1.2 入门案例
- mybatis的环境搭建
- 准备好数据库
- pom配置,用jar包打包,引入依赖
- mybatis:官网的入门部分找
- mysql:连接数据库
- log4j:日志信息
- junit:单元测试
- 创建实体类,实现Serializable接口,今天要求属性名与数据库对应名称一致,创建dao的接口
- 创建Mybatis的主配置文件SqlMapConfig.xml
- 创建映射配置文件IUserDao.xml
-
注意事项
- 命名中包含Dao是与之前的知识保持一致,也可交做Mapper
- directory和package是不一样的
- mabatis的配置文件位置必须和dao接口的包结构相同
- 映射配置文件的mapper标签namespace属性的取值必须是dao接口的全限定类名
- 映射配置文件的操作配置(select),id属性的取值必须是dao接口的方法名
当我们遵从了第三四五点后,我们在开发中就无需再写dao的实现类
- mybatis的入门
- 读取配置文件
- 创建SqlSessionFactory工厂
- 创建SqlSession
- 创建Dao接口的代理对象
- 执行dao中的方法
- 释放资源
- 注意:不要忘记在映射配置中告知mybatis要封装到哪个实体类中,指定实体类的权限定类名
- mybatis基于注解的入门案例:
- 把IUserDao.xml移除,在dao接口的方法上使用@Select注解,并且指定SQL语句,同时需要在SqlMapConfig.xml中的mapper配置时,使用calss属性指定dao接口的全限定类名。
- 明确:我们为了简便一般不写dao实现类,但是实际上是可以写的
- 入门案例的分析
- 测试类中main方法详解
这些方法都后面都可以封装的,只是现在细一点讲底层原理,而且都写出来的话灵活性更大,参数什么的可以自由调配。
mybatis在使用代理dao的方式实现增删改查时做什么事
-
创建代理对象
-
在代理对象中调用selectList
让这两件事都串起来,每个接口和类都各司其职,以下是分析
- 手撕源码
1.3 完成CRUD
-
在接口中申明方法
public interface IUserDao { List<User> findAll(); void saveUser(User user); void updateUser(User user); void deleteUser(Integer userId); User findById(Integer userId); List<User> findByName(String userName); Integer findTotal(); List<User> findUserByVo(QueryVo vo); }
-
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.itheima.dao.IUserDao">
<select id="findAll" resultType="com.itheima.domain.User">
select * from user
</select>
<insert id="saveUser" parameterType="com.itheima.domain.User">
<selectKey keyProperty="id" keyColumn="id" resultType="int" order="AFTER">
select last_insert_id();
</selectKey>
insert into user(username,address,sex,birthday)values(#{username},#{address},#{sex},#{birthday});
</insert>
<update id="updateUser" parameterType="com.itheima.domain.User">
update user set username=#{username},address=#{address},sex=#{sex},birthday=#{birthday} where id=#{id};
</update>
<delete id="deleteUser" parameterType="java.lang.Integer">
delete from user where id=#{uid};
</delete>
<select id="findById" parameterType="INT" resultType="com.itheima.domain.User">
select * from user where id=#{uid};
</select>
<select id="findByName" parameterType="String" resultType="com.itheima.domain.User">
select * from user where username like #{username};
</select>
<select id="findTotal" resultType="int">
select count(id) from user;
</select>
//根据queryVo中的条件查询用户
<select id="findUserByVo" parameterType="com.itheima.domain.QueryVo" resultType="com.itheima.domain.User">
select * from user where username like #{user.username};
</select>
</mapper>
- QueryVo类,可以添加其他条件
public class QueryVo {
private User user;
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
-
OGNL表达式:Object Graphic Navigation Language(对象 图 导航 语言)
它是通过对象的取值方法来获取数据,在写法上把get给省略了
类中的写法:user.getUsername() -> user.username
mybatis中可以直接写username,因为在parameterType中已经提供了属性所属的类,所以此时不需要写对象名
- Test写对查询到的数据的处理
public class MybatisTest {
private InputStream in = null;
private SqlSession session = null;
private IUserDao userDao = null;
@Before
public void init()throws Exception{
//1.读取配置文件
in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.创建SqlSessionFactory工厂
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(in);
//3.使用工厂生产SqlSession对象
session = factory.openSession();
//4.使用SqlSession创建Dao接口的代理对象
userDao = session.getMapper(IUserDao.class);
}
@After
public void destroy() throws Exception{
session.commit();
//6.释放资源
session.close();
in.close();
}
@Test
public void testFindAll(){
//5.使用代理对象执行方法
List<User> users = userDao.findAll();
for(User user : users){
System.out.println(user);
}
}
@Test
public void testSave(){
User user = new User();
user.setUsername("Mybatis lastsaveuser");
user.setAddress("湖北武汉");
user.setSex("男");
user.setBirthday(new Date());
System.out.println(user);
//5.使用代理对象添加方法
userDao.saveUser(user);
System.out.println(user);
}
@Test
public void testUpdate(){
User user = new User();
user.setId(49);
user.setUsername("Mybatis");
user.setAddress("中国北京");
user.setSex("女");
user.setBirthday(new Date());
//5.使用代理对象更新方法
userDao.updateUser(user);
}
@Test
public void testDelete(){
userDao.deleteUser(49);
}
@Test
public void testFindOne(){
User user = userDao.findById(45);
System.out.println(user);
}
@Test
public void testFindByName(){
List<User> user = userDao.findByName("%王%");
for (User users : user) {
System.out.println(users);
}
}
@Test
public void testTotal(){
int count = userDao.findTotal();
System.out.println(count);
}
@Test
public void testFindByVo(){
User user = new User();
QueryVo vo = new QueryVo();
user.setUsername("%王%");
vo.setUser(user);
List<User> users = userDao.findUserByVo(vo);
for (User u : users) {
System.out.println(u);
}
}
}
-
注意:此时我们的数据库名称和pojo类属性名称是保持一致的,如果不一致,有两种解决方法
- 在sql语句中起别名,如:select id as userId
- 执行速度最快,但是费时间
- 采用配置的方式
- 要读一次xml文件,速度慢一点,但是开发很方便
<resultMap id="userMap" type="com.itheima.domain.User"> <!-- 主键字段的对应--> <id property="userId" column="id"></id> <!-- 非主键字段的对应--> <result property="userName" column="username"></result> <result property="userAddress" column="address"></result> <result property="userSex" column="sex"></result> <result property="userBirthday" column="birthday"></result> </resultMap> <!-- 还要把resultType的属性改为resultMap="userMap"-->
- 在sql语句中起别名,如:select id as userId
使用Mybatis完成DAO层的开发先跳
1.4 连接池及事务
-
连接池:我们在实际开发中都会使用连接池,因为他可以减少我们获取连接所消耗的时间
-
mybatis连接池提供了3种方式的配置
-
位置:主配置文件SqlMapConfig.xml钟的dataSource标签,type属性就是表示采用何种连接池方式
-
type属性的取值:
-
POOLED 采用传统的javax.sql.DataSource规范中的连接池,mybatis中有针对规范的实现
-
UNPOOLED 采用传统的获取连接的方式,虽然也实现Javax.sql.DataSource接口,但是并没有使用池的思想
-
JNDI 采用服务器提供的JNDI技术实现,来获取DataSource对象,不同的服务器所能拿到DataSource是不一样的
- 注意:如果不是web或者maven的war工程,是不能使用的,我们课程中是使用的是tomcat服务器,采用连接池就是dbcp连接池
-
-
-
mybatis中的事务
- 注意面试中常问的问题
- 它是通过sqlsession对象的commit方法和rollback方法实现事务的提交和回滚
- 在openSession方法里的参数添加true,即可自动提交事务,不用在代码中手动提交,不过只有每次执行一个对数据库的crud操作才可以用这种方式
1.5 动态sql
-
if标签
- 如果不知道在User类里面提供了什么信息作为条件,可以使用标签
//查找的是已有条件的交集 <select id="findUserByCondition" parameterType="com.itheima.domain.User" resultType="com.itheima.domain.User"> select * from user where 1 = 1 <if test="username!=null"> and username = #{username} </if> <if test="sex!=null"> and sex = #{sex} </if> </select>
@Test public void testFindUserByCondition(){ User user = new User(); user.setUsername("老王"); user.setSex("女"); List<User> users = userDao.findUserByCondition(user); for (User u : users) { System.out.println(u); } }
-
where标签
- 如果不想出现where 1 = 1这个条件,可以把下面的内容用标签包裹
-
foreach和sql标签
- foreach用于提供sql语句查询多个条件数据的结果,如查id为42,45,46的那些用户的所有数据
<select id="findUserInIds" parameterType="com.itheima.domain.QueryVo" resultType="com.itheima.domain.User"> select * from user <where> <if test="ids!=null and ids.size()>0"> //字符串的拼接 <foreach collection="ids" open="and id in (" close=")" item="id" separator=","> #{id} </foreach> </if> </where> </select>
@Test public void testFindUserInIds(){ QueryVo queryVo = new QueryVo(); List<Integer> list = new ArrayList<Integer>(); list.add(42); list.add(45); list.add(46); queryVo.setIds(list); List<User> users = userDao.findUserInIds(queryVo); for (User user : users) { System.out.println(user); } }
- xml中的#{}是将参数调过来处理,参数类型是parameterType,然后根据resultType将结果传回去
1.6 Mybatis的多表操作
-
完成account的一对一操作,当查询账户时,可以同时得到账户的所属用户信息
- 数据库和domain都建立两张表:用户表(user),账户表(account),用各自的格式表示出一对多的关系
- 数据库用外键约束
- 账户表添加用户表为属性,加上getter,setter方法,并在xml中设置resultMap标签
<resultMap id="accountUserMap" type="account"> //可以直接写account是因为在主配置文件中用typeAliases配置别名,它智能配置domain中类的别名 <id property="id" column="aid"></id> //aid是a.id的别名,在sql语句中体现 <result property="uid" column="uid"></result> <result property="money" column="money"></result> <association property="user" column="uid" javaType="user"> <id property="id" column="id"></id> <result property="username" column="username"></result> <result property="address" column="address"></result> <result property="sex" column="sex"></result> <result property="birthday" column="birthday"></result> </association> </resultMap>
- 测试类中通过调用account.getUser()输出user表的内容
- 数据库和domain都建立两张表:用户表(user),账户表(account),用各自的格式表示出一对多的关系
-
完成user的一对多查询操作,查询用户时,可以同时得到用户下所包含的账户信息
- User类中添加private List accounts,并加上getter,setter方法
- 在xml中设置resultMap标签
<mapper namespace="com.itheima.dao.IUserDao"> <resultMap id="userAccountMap" type="user"> <id property="id" column="id"></id> <result property="username" column="username"></result> <result property="address" column="address"></result> <result property="sex" column="sex"></result> <result property="birthday" column="birthday"></result> <collection property="accounts" ofType="account"> <id property="id" column="aid"></id> <result property="uid" column="uid"></result> <result property="money" column="money"></result> </collection> </resultMap> <select id="findAll" resultMap="userAccountMap"> SELECT * FROM USER u LEFT OUTER JOIN account a ON a.`UID` = u.id; </select> </mapper>
- 测试类中输出
@Test public void testFindAll(){ List<User> users = userDao.findAll(); for (User user : users) { System.out.println(user); System.out.println(user.getAccounts()); } }
-
多对多操作
-
建立两张表:用户表,角色表;让两张表具有多对多的关系,需要使用中间表,中间表包含各种的主键,在中间表是外键
-
建立两个实体类:用户实体类和角色实体类,让两个实体类能体现出来多对多的关系,各自包含对方的一个集合引用
- 如在User表中 private List roles;并生成getter,setter方法
-
建立两个映射配置文件:用户的和角色的配置文件(以角色配置文件举例)
<?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.itheima.dao.IRoleDao"> <resultMap id="roleMap" type="role"> <id property="roleId" column="id"></id> <result property="roleName" column="role_name"></result> <result property="roleDesc" column="role_desc"></result> <collection property="users" ofType="user"> <id property="id" column="id"></id> <result property="username" column="username"></result> <result property="address" column="address"></result> <result property="sex" column="sex"></result> <result property="birthday" column="birthday"></result> </collection> </resultMap> <select id="findAll" resultMap="roleMap"> <!-- 多行sql语句最好在每个后面加一个空格,防止前后连接在一起导致错误--> SELECT * FROM role r LEFT OUTER JOIN user_role ur ON r.`ID`=ur.`RID` LEFT OUTER JOIN USER u ON u.`id`=ur.`UID`; </select> </mapper>
-
实现配置:当我们查询用户时,可以同时得到用户所包含的角色信息,查询角色时,能同时得到角色所属的用户信息。
@Test public void testFindAll(){ List<Role> roles = roleDao.findAll(); for (Role role : roles) { System.out.println(role); System.out.println(role.getUsers()); } }
-
总结:多表操作的步骤都类似,按照流程走就行了
1.7 JNDI
是SUN公司推出的一套规范,属于JavaEE技术之一,目的是模范windows系统的注册表
注册表里面为什么同一个key名称可以有不同的value,因为这些key处在不同的目录中,实际上不是同一个key
1.8 延迟加载
-
概念:在真正使用数据时才发起查询,按需加载(懒加载)
-
什么时候使用:
- 一对一,多对多:通常用延迟加载
- 多对一,一对一:通常用立即加载
- 举例:一个用户有100个账户
-
如何使用:
mybatis第三天实现多表操作时,我们使用了resultMap来实现一对一,一对多,多对多关系的操作。主要 是通过 association、collection 实现一对一及一对多映射。association、collection 具备延迟加载功能
-
一对一实现延迟加载,找到账户表以及对应的用户
-
在之前一对一工程基础上改造
-
配置account的映射配置文件,user的配置文件不变
<?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.itheima.dao.IAccountDao"> <resultMap id="accountUserMap" type="account"> <id property="id" column="id"></id> <result property="uid" column="uid"></result> <result property="money" column="money"></result> <!-- 一对一的关系映射:配置封装user的内容 select属性指定的内容:查询用户的唯一标识 column属性指定的内容:用户根据id查询时,所需要的参数的值 --> <association property="user" column="uid" javaType="user" select="com.itheima.dao.IUserDao.findById">//uid不能省略,要传到findById里面 </association> </resultMap> <select id="findAll" resultMap="accountUserMap"> select * from account; </select> </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"> <mapper namespace="com.itheima.dao.IUserDao"> <resultMap id="userAccountMap" type="user"> <id property="id" column="id"></id> <result property="username" column="username"></result> <result property="address" column="address"></result> <result property="sex" column="sex"></result> <result property="birthday" column="birthday"></result> <collection property="accounts" ofType="account"> <id property="id" column="aid"></id> <result property="uid" column="uid"></result> <result property="money" column="money"></result> </collection> </resultMap> <select id="findAll" resultMap="userAccountMap"> SELECT * FROM user u left outer join account a on u.id = a.uid </select> <select id="findById" resultType="user"> SELECT * FROM user where id = #{uid} </select> </mapper>
-
主配置文件添加setting配置,打开延迟加载开关
<settings> <setting name="lazyLoadingEnabled" value="true"/> <setting name="aggressiveLazyLoading" value="false"></setting> </settings>
- 完成读一个加载一个
-
-
一对多实现延迟加载
- 将之前对account做的对user再做一遍就行,xml中的collection和association用哪个好像无所谓,里面的select属性调用另一个里面的find方法
1.9 缓存
-
概念:存在于内存中的临时数据
-
为什么使用:减少和数据库的交互次数
-
什么时候使用:
- 适用于缓存
- 经常查询并且不经常改变的
- 数据的正确与否对最终结果影响不大的(商品库存,银行汇率,股市牌价)
- 适用于缓存
-
一级缓存和二级缓存
-
一级缓存:指的是Mybatis中SqlSession对象的缓存,当我们执行查询之后,查询的结果会同时存入到SqlSession为我们提供的一块区域中,该区域的结构是一个Map。当SqlSession对象消失时,mybatis的以及缓存也就消失了
sqlSession = factory.close(); //再次获取SqlSession对象 sqlSession = factory.openSession(); userDao = sqlSession.getMapper(IUserDao.class);
sqlSession.clearCache();//此方法也可以清空缓存 userDao = sqlSession.getMapper(IUserDao.class);
- 一级缓存是SqlSession范围的缓存,当调用SqlSession的修改,添加,删除,commit(),close()等方法时,就会清空一级缓存。
-
二级缓存:指的是Mybatis中SqlSessionFacrory对象的缓存。由同一个SqlSessionFactory对象创建的SqlSession共享其缓存。
使用步骤
- 让Mybatis框架支持二级缓存(在SqlMapConfig.xml中配置)
- 让当前的映射文件支持二级缓存(在IUserDao.xml中配置)
- 让当前的操作支持二级缓存(在select标签中配置)
-
1.10 注解开发
-
为什么注解开发可以代替映射配置文件
-
建立实体类属性和数据库列的对应关系,用Results和ResultMap注解,如下
public interface IUserDao { @Select("select * from user") @Results(id = "userMap",value = { @Result(id = true,column = "id",property = "userId"), @Result(column = "username",property = "userName"), @Result(column = "address",property = "userAddress"), @Result(column = "sex",property = "userSex"), @Result(column = "birthday",property = "userBirthday"), }) List<User> findAll(); @Select("select * from user where id=#{id}") @ResultMap("userMap") User findById(Integer userId); @Select("select * from user where username like '%${value}%' ") @ResultMap("userMap") List<User> findByName(String userName); }
-
注解开发一对一的查询配置
-
建立account表的javaBean并且添加private User user;
-
注解
public interface IAccountDao { @Select("select * from account") @Results(id = "accountMap",value = { @Result(id = true,column = "id",property = "id"), @Result(column = "uid",property = "uid"), @Result(column = "money",property = "money"), //下面这行是精华,并且同时实现了延迟加载 @Result(property = "user",column = "uid",one = @One(select = "com.itheima.dao.IUserDao.findById",fetchType = FetchType.EAGER)) }) List<Account> findAll(); }
-
-
注解开发一对多的查询配置
-
在user表中添加private List accounts;
-
IAccountDao中添加查询方法
@Select("select * from account where uid=#{userId}") List<Account> findByUid(Integer userId);
-
IUserDao注解加一行
@Result(property = "accounts",column = "id",many = @Many(select = "com.itheima.dao.IAccountDao.findByUid",fetchType = FetchType.LAZY))
-
-
使用二级缓存
- 一级二级都是默认开启的,如果不想开启二级缓存,可以通过settings设置关闭
2. Spring
2.1 概述
-
概念:Spring是分层的 Java SE/EE应用 full-stack 轻量级开源框架,以 IoC(Inverse Of Control: 反转控制)和 AOP(Aspect Oriented Programming:面向切面编程)为内核,提供了展现层 Spring MVC 和持久层 Spring JDBC 以及业务层事务管理等众多的企业级应用技术
-
优点:方便解耦,简化开发、AOP编程的支持 、声明式事务的支持 、方便程序的测试 、方便集成各种优秀框架、降低 JavaEE API的使用难度 、Java源码是经典学习范例
-
体系结构
2.2 程序间耦合
2.2.1 问题及手动解决方法
-
耦合:程序间的依赖关系,包括:类之间的依赖,方法间的依赖
-
解耦:降低程序间的依赖关系
- 实际开发中应该做到:编译器不依赖,运行时才依赖
- 使用反射来创建对象,而避免使用new关键字
- 通过读取配置文件来获取要创建的对象的全限定类名
-
例子:
- JDBC的注册驱动
//DriverManager.registerDriver(new com.mysql.jdbc.Driver());没有导入jar包编译器就会报错 Class.forName("com.mysql.jdbc.Driver");//写死了,还是不太行
- 曾经写的javaWeb中也出现过这个问题,表现层调业务层,业务层调持久层
-
使用工厂模式解耦
-
创建一个Bean对象的工厂,它就是创建我们的service和dao对象的
- 需要一个配置文件来配置我们的service和dao
- 配置的内容:唯一标识=全限定类名(key)
- 配置文件可以是xml也可以是properties
- 通过读取配置文件中配置的内容,反射创建对象
- Bean:在计算机英语中,有可重用组件的含义
- javabean:用java语言编写的可重用组件。(javabean>实体类)
public class BeanFactory { private static Properties props; static { try { //实例化对象 props = new Properties(); //获取properties文件的流对象 InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties"); props.load(in); }catch(Exception e){ throw new ExceptionInInitializerError("初始化properties失败!"); } } /** * 根据bean的名称获取对象 * @param beanName * @return */ public static Object getBean(String beanName){ Object bean = null; try{ String beanPath = props.getProperty(beanName); bean = Class.forName(beanPath).newInstance(); }catch (Exception e){ e.printStackTrace(); } return bean; } }
- 需要一个配置文件来配置我们的service和dao
-
bean.properties配置文件内容
accountService=com.itheima.service.impl.AccountServiceImpl accountDao=com.itheima.dao.impl.AccountDaoImpl
-
更改之前通过new创建下层示例
//IAccountService as = new AccountServiceImpl();废弃 IAccountService as = (IAccountService) BeanFactory.getBean("accountService");
//private IAccountDao accountDao = new AccountDaoImpl();废弃 private IAccountDao accountDao = (IAccountDao) BeanFactory.getBean("accountDao");
-
-
工厂模式解耦的升级版
- 存在的问题:工厂创建的对象是多例的,因为每次都会调用newInstance(),由于我们一般都不会在方法中修改属性的值,没必要用多例,所以改成单例更快。
- 改变方法
public class BeanFactory { //定义一个Properties对象 private static Properties props; //定义一个Map,用于存放我们要创建的对象。我们把它称之为容器 private static Map<String,Object> beans; //使用静态代码块为Properties对象赋值 static { try { //实例化对象 props = new Properties(); //获取properties文件的流对象 InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties"); props.load(in); //实例化容器 beans = new HashMap<String,Object>(); //取出配置文件中所有的Key Enumeration keys = props.keys(); //遍历枚举 while (keys.hasMoreElements()){ //取出每个Key String key = keys.nextElement().toString(); //根据key获取value String beanPath = props.getProperty(key); //反射创建对象 Object value = Class.forName(beanPath).newInstance(); //把key和value存入容器中 beans.put(key,value); } }catch(Exception e){ throw new ExceptionInInitializerError("初始化properties失败!"); } } /** * 根据bean的名称获取对象 * @param beanName * @return */ public static Object getBean(String beanName){ return beans.get(beanName); } }
2.2.2 IOC控制反转
-
概念:把创建对象的权力交给框架,是框架的重要特征,并非面向对象编程的专用术语。它包括依赖注入DI和依赖查找DL,之前用的BeanFactory类就使用了这个思想,可以消减计算机程序的耦合。它只能解决程序间的依赖关系,其他什么都做不了
2.3 使用spring的IOC解决程序耦合
2.3.1 配置方法
-
使用spring代替自己写的BeanFactory类
-
pom中引入spring的依赖
-
配置文件bean.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"> <bean id="acountService" class="com.itheima.service.impl.AccountServiceImpl"> <!-- collaborators and configuration for this bean go here --> </bean> <bean id="acountDao" class="com.itheima.dao.impl.AccountDaoImpl"> <!-- collaborators and configuration for this bean go here --> </bean> </beans>
-
客户端实现
public class Client { public static void main(String[] args) { /** * 获取spring的Ioc核心容器,并根据id获取对象 * * ApplicationContext的三个常用实现类: * ClassPathXmlApplicationContext:它可以加载类路径下的配置文件,要求配置文件必须在类路径下。不在的话,加载不了。(更常用) * FileSystemXmlApplicationContext:它可以加载磁盘任意路径下的配置文件(必须有访问权限) * AnnotationConfigApplicationContext:它是用于读取注解创建容器的,是下次的内容 * * 核心容器的两个接口引发出的问题: * ApplicationContext: 单例对象使用 * 它在构建核心容器时,创建对象采取的策略是采用立即加载的方式。也就是说,只要一读取完配置文件马上就创建配置文件中配置的对象 * BeanFactory: 多例对象使用 * 它在构建核心容器时,创建对象采取的策略是采用延迟加载的方式。也就是说,什么时候根据id获取对象了,什么时候才真正的创建对象 */ ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); //2.根据id获取bean对象 IAccountService as = (IAccountService)ac.getBean("acountService"); IAccountDao adao = ac.getBean("accountDao", IAccountDao.class); System.out.println(as); System.out.println(adao); } }
2.3.2 bean的管理细节
-
创建bean的三种方式
-
使用默认构造函数创建:在spring的配置文件中使用bean标签,配以id和class属性之后,且没有其他属性和标签时,采用的就是默认构造函数创建bean对象,此时如果类中没有默认构造函数,则对象无法创造。
-
使用普通工厂中的方法创建对象(使用某个类中的方法创建对象,并存入spring容器)
-
使用工厂中的静态方法创建对象(使用某个类中的静态方法创建对象,并存入spring容器)
-
-
bean的作用范围调整:bean标签的scope属性:
-
作用:用于指定bean的作用范围
-
取值:常用的就是单例的和多礼的
-
singleton:单例的(默认值)
-
prototype:多例的
-
request:作用于web应用的请求范围
-
session:作用于web应用的会话范围
-
global-session:作用于集群环境的会话范围(全局会话范围),当不是集群环境时,它就是session
-
-
-
bean的生命周期
-
单例对象
- 出生:当容器创建时对象出生
- 活着:只要容器还在,对象一直活着
- 死亡:容器销毁,对象消亡(如果在main函数中需要手动销毁,使用.close方法,而且不能用多态,因为接口中没有)
- 总结:单例对象的生命周期和容器相同
-
多例对象
- 出生:当我们使用对象时spring框架为我们创建
- 活着:对象只要是在使用过程中就一直活着
- 死亡:当对象长时间不用,且没有别的对象引用时,由Java的垃圾回收器回收,spring不能主动销毁,容易出问题
-
xml中的标签定义
//init和destroy方法已经在实现类中定义好了 <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl" scope="prototype" init-method="init" destroy-method="destroy"></bean>
-
2.3.3 依赖注入
-
概念:在当前类需要用到其他类的对象,由spring为我们提供,以后依赖关系的管理都交给spring来维护,我们只需要在配置文件中说明。依赖关系的维护就称之为依赖注入
-
能注入的数据:
-
基本类型和String
-
其它bean类型(在配置文件中或者注解配置过的bean,就是使用ref的这个)
-
复杂类型/集合类型
-
用于给List结构集合注入的标签:list array set
-
用于给Map结构集合注入的标签:map props
-
结构相同,标签可以互换,如
<bean id="accountService3" class="com.itheima.service.impl.AccountServiceImpl3" <property name="myStrs"> <set> <value>AAA</value> <value>BBB</value> <value>CCC</value> </set> </property> </bean>
-
-
-
注入的方式:
-
使用构造函数提供(把数据传入构造方法的参数)
-
使用方法
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"> <constructor-arg name="name" value="test"></constructor-arg>//String类型 <constructor-arg name="age" value="18"></constructor-arg>//Interger类型 <constructor-arg name="birthday" ref="now"></constructor-arg>//Date类型 </bean> <bean id="now" class="java.util.Date"></bean>
-
优势:在获取bean对象时,注入数据是必须的操作,否则对象无法创建成功
-
弊端:改变了bean对象的实例化方式,是我们在创建对象时如果用不到这些数据也必须提供
-
-
使用set方法提供(get方法没必要) 更常用
-
使用方法
<bean id="accountService2" class="com.itheima.service.impl.AccountServiceImpl2"> <property name="name" value="test></property> <property name="age" value="21"></property> <property name="birthday" ref="now></property> </bean>
-
优势:创建对象时没有明确的限制,可以直接使用默认构造函数
-
弊端:如果有某个成员必须有值,则获取对象时有可能set方法没有执行
-
-
使用注解提供
-
2.4 基于注解的IOC配置
-
XML和注解对比
曾经的XML的配置:
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"> scope=" init-method="" destroy-method=""> <property name="" value|ref=""></property> </bean>
-
用于创建对象的:作用和XML配置文件中编写一个标签实现的功能是一样的
-
在要被创建的对象的类上写上Component注解
//作用:用于把当前类对象存入spring容器中 //属性:value:用于指定bean的id。不写的时候默认是当前类名,且首字母改小写 @Component("accountService") public class AccountServiceImpl implements IAccountService { private IAccountDao accountDao = new AccountDaoImpl(); public AccountServiceImpl() { System.out.println("对象创建了"); } public void saveAccount() { accountDao.saveAccount(); } }
-
现在还不能扫描找到,需要在官网找到xml的配置代码
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> //这行自己配 <context:component-scan base-package="com.itheima"></context:component-scan> </beans>
-
现在在客户端类中可以正常使用了,控制台输出:“对象创建了”
public class Client { public static void main(String[] args) { ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); //根据id获取bean对象 IAccountService as = (IAccountService)ac.getBean("accountService"); System.out.println(as); } }
-
其他用于创建对象的注解
-
Controller:一般用在表现层
-
Service:一般用在业务层
-
Repository:一般用在持久层
以上三个注解的作用和属性于Component一模一样,是spring框架为我们提供明确的三层使用的注解,使我们的三层对象更加清晰
-
-
-
用于注入数据的:作用就和xml配置文件中的bean标签中写一个标签的作用是一样的
-
Autowired:
-
作用:自动按照类型注入。只要容器中有唯一的一个bean对象类型和要注入的变量类型匹配,就可以注入成功
如果ioc容器中没有任何bean的类型和要注入的变量类型匹配,则报错。
-
出现位置:可以是变量上,也可以是方法上
-
细节:在使用注解注入时,set方法就不是必须的了
-
实现原理:
-
-
Qualifier:
- 作用:在按照类中注入的基础之上再按照名称注入(解决上图中的问题)。它在给类成员注入时不能单独使用,但是在给方法参数注入时可以
- 属性:value:用于指定注入bean的id
-
Resource
- 作用:直接按照bean的id注入。它可以独立使用
- 属性:name:用于指定bean的id
- 以上三个注入都只能注入其他bean类型的数据,而基本类型和String类型无法使用上述注解实现。另外,集合类型的注入只能通过xml来实现
- Value
- 作用:用于注入基本类型和String类型的数据
- 属性:value:用于指定数据的值。它可以使用spring中SpEL(也就是spring的el表达式),SpEL的写法:${表达式}。区别这个表达式是谁的表达式就看它定义的位置
-
-
用于改变作用范围的:作用就和在bean标签中使用scope属性实现的功能是一样的
- Scope
- 作用:用于指定bean的作用范围
- 属性:value:指定范围的取值。常用取值:singleton Prototype
- Scope
-
和生命周期相关(了解):作用就和在bean标签中使用init-method和destroy-method的作用是一样的
- PreDestroy:用于指定销毁方法
- PostConstruct:用于指定初始化方法
-
2.5 IOC案例
-
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"> //如何创建bean对象 <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"> //如何注入数据,注入的两种类型之其它bean类型 <property name="accountDao" ref="accountDao"></property> </bean> <bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl"> //注入数据的两种方式之set方法注入 <property name="runner" ref="runner"></property> </bean> <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"> 注入数据的两种方式之构造函数注入 <constructor-arg name="ds" ref="dataSource"></constructor-arg> </bean> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> //注入的两种类型之基本类型和String <property name="driverClass" value="com.mysql.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/eesy"></property> <property name="user" value="root"></property> <property name="password" value="root"></property> </bean> </beans>
-
在每个测试方法前面加上获取bean对象,其他操作同之前不变,如:
@Test public void testFindAll(){ //之后下面这两行可以在spring整合junit中解决 ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); //根据id获取bean对象 IAccountService as = ac.getBean("accountService", IAccountService.class); List<Account> accounts = as.findAllAccount(); for (Account account : accounts) { System.out.println(account); } }
-
用注解改造
@Service("accountService") public class AccountServiceImpl implements IAccountService { @Autowired private AccountDaoImpl accountDao;
@Repository("accountDao") public class AccountDaoImpl implements IAccountDao { @Autowired private QueryRunner runner;
并且在bean对象中添加扫描属性以及修改成context头
2.6 Spring的新注解
-
用配置类代替bean.xml文件(这节课有点偷懒了)
package config; /** * 该类是一个配置类,它的作用和bean.xml是一样的 * spring中的新注解 * Configuration * 作用:指定当前类是一个配置类 * 细节:当配置类作为AnnotationConfigApplicationContext对象创建的参数时,该注解可以不写。 * ComponentScan * 作用:用于通过注解指定spring在创建容器时要扫描的包 * 属性: * value:它和basePackages的作用是一样的,都是用于指定创建容器时要扫描的包。 * 我们使用此注解就等同于在xml中配置了: * <context:component-scan base-package="com.itheima"></context:component-scan> * Bean * 作用:用于把当前方法的返回值作为bean对象存入spring的ioc容器中 * 属性: * name:用于指定bean的id。当不写时,默认值是当前方法的名称 * 细节: * 当我们使用注解配置方法时,如果方法有参数,spring框架会去容器中查找有没有可用的bean对象。 * 查找的方式和Autowired注解的作用是一样的 * Import * 作用:用于导入其他的配置类 * 属性: * value:用于指定其他配置类的字节码。 * 当我们使用Import的注解之后,有Import注解的类就父配置类,而导入的都是子配置类 * PropertySource * 作用:用于指定properties文件的位置 * 属性: * value:指定文件的名称和路径。 * 关键字:classpath,表示类路径下 */ //@Configuration @ComponentScan("com.itheima") @Import(JdbcConfig.class) @PropertySource("classpath:jdbcConfig.properties") public class SpringConfiguration { //这是共同的配置类 }
-
子配置类(针对一些特定的配置)
public class JdbcConfig { //读取properties配置文件内容的一个好方法 @Value("${jdbc.driver}") private String driver; @Value("${jdbc.url}") private String url; @Value("${jdbc.username}") private String username; @Value("${jdbc.password}") private String password; /** * 用于创建一个QueryRunner对象 * @param dataSource * @return */ @Bean(name="runner") @Scope("prototype") //Qualifier可以用在参数位置上,选择对象具体的哪个实现类 public QueryRunner createQueryRunner(@Qualifier("ds2") DataSource dataSource){ return new QueryRunner(dataSource); } /** * 创建数据源对象 * @return */ @Bean(name="ds2") public DataSource createDataSource(){ try { ComboPooledDataSource ds = new ComboPooledDataSource(); //不用写死了 ds.setDriverClass(driver); ds.setJdbcUrl(url); ds.setUser(username); ds.setPassword(password); return ds; }catch (Exception e){ throw new RuntimeException(e); } } //一个对象有多个实现类的情况 @Bean(name="ds1") public DataSource createDataSource1(){ try { ComboPooledDataSource ds = new ComboPooledDataSource(); ds.setDriverClass(driver); ds.setJdbcUrl("jdbc:mysql://localhost:3306/eesy02"); ds.setUser(username); ds.setPassword(password); return ds; }catch (Exception e){ throw new RuntimeException(e); } } }
-
使用xml配置还是注解配置
- 公司用什么就用什么
- 如果类写好了存在jar包,推荐用xml,如果要自己写类,推荐用注解
- 一般来说部分xml部分更简洁
-
spring整合junit
-
整合的思路
- 应用程序的入口
main方法 - junit单元测试中,没有main方法也能执行
junit集成了一个main方法
该方法就会判断当前测试类中哪些方法有 @Test注解
junit就让有Test注解的方法执行 - junit不会管我们是否采用spring框架
在执行测试方法时,junit根本不知道我们是不是使用了spring框架
所以也就不会为我们读取配置文件/配置类创建spring核心容器 - 由以上三点可知
当测试方法执行时,没有Ioc容器,就算写了Autowired注解,也无法实现注入
- 应用程序的入口
-
整合的步骤
/** * 使用Junit单元测试:测试我们的配置 * Spring整合junit的配置 * 1、导入spring整合junit的jar(坐标) * 2、使用Junit提供的一个注解把原有的main方法替换了,替换成spring提供的 * @Runwith * 3、告知spring的运行器,spring和ioc创建是基于xml还是注解的,并且说明位置 * @ContextConfiguration * locations:指定xml文件的位置,加上classpath关键字,表示在类路径下 * classes:指定注解类所在地位置 * * 当我们使用spring 5.x版本的时候,要求junit的jar必须是4.12及以上 */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = SpringConfiguration.class) public class AccountServiceTest { @Autowired private IAccountService as = null; @Test public void testFindAll() { //3.执行方法 List<Account> accounts = as.findAllAccount(); for(Account account : accounts){ System.out.println(account); } } @Test public void testFindOne() { //3.执行方法 Account account = as.findAccountById(1); System.out.println(account); } @Test public void testSave() { Account account = new Account(); account.setName("test anno"); account.setMoney(12345f); //3.执行方法 as.saveAccount(account); } @Test public void testUpdate() { //3.执行方法 Account account = as.findAccountById(4); account.setMoney(23456f); as.updateAccount(account); } @Test public void testDelete() { //3.执行方法 as.deleteAccount(4); } }
-
2.7 AOP
2.7.1 AOP的引入
-
问题:转账操作需要添加事务,在业务层实现每个功能都要放在事务的生命周期中,而且加上事务类还有复杂的bean.xml配置,耦合度非常高
-
使用动态代理可以解决,回顾基于接口的动态代理
/** * 一个生产者 */ public class Producer { /** * 销售 * @param money */ public void saleProduct(float money){ System.out.println("销售产品,并拿到钱:"+money); } /** * 售后 * @param money */ public void afterService(float money){ System.out.println("提供售后服务,并拿到钱:"+money); } }
/** * 模拟一个消费者 */ public class Client { public static void main(String[] args) { final Producer producer = new Producer(); /** * 动态代理: * 特点:字节码随用随创建,随用随加载 * 作用:不修改源码的基础上对方法增强 * 分类: * 基于接口的动态代理 * 基于子类的动态代理 * 基于接口的动态代理: * 涉及的类:Proxy * 提供者:JDK官方 * 如何创建代理对象: * 使用Proxy类中的newProxyInstance方法 * 创建代理对象的要求: * 被代理类最少实现一个接口,如果没有则不能使用 * newProxyInstance方法的参数: * ClassLoader:类加载器 * 它是用于加载代理对象字节码的。和被代理对象使用相同的类加载器。固定写法。 * Class[]:字节码数组 * 它是用于让代理对象和被代理对象有相同方法。固定写法。 * InvocationHandler:用于提供增强的代码 * 它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。 * 此接口的实现类都是谁用谁写。 */ IProducer proxyProducer = (IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(), producer.getClass().getInterfaces(), new InvocationHandler() { /** * 作用:执行被代理对象的任何接口方法都会经过该方法 * 方法参数的含义 * @param proxy 代理对象的引用 * @param method 当前执行的方法 * @param args 当前执行方法所需的参数 * @return 和被代理对象方法有相同的返回值 * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //提供增强的代码 Object returnValue = null; //1.获取方法执行的参数 Float money = (Float)args[0]; //2.判断当前方法是不是销售 if("saleProduct".equals(method.getName())) { returnValue = method.invoke(producer, money*0.8f); } return returnValue; } }); proxyProducer.saleProduct(10000f); } }
-
基于子类的动态代理
/** * 模拟一个消费者 */ public class Client { public static void main(String[] args) { final Producer producer = new Producer(); /** * 动态代理: * 特点:字节码随用随创建,随用随加载 * 作用:不修改源码的基础上对方法增强 * 分类: * 基于接口的动态代理 * 基于子类的动态代理 * 基于子类的动态代理: * 涉及的类:Enhancer * 提供者:第三方cglib库 * 如何创建代理对象: * 使用Enhancer类中的create方法 * 创建代理对象的要求: * 被代理类不能是最终类 * create方法的参数: * Class:字节码 * 它是用于指定被代理对象的字节码。 * * Callback:用于提供增强的代码 * 它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。 * 此接口的实现类都是谁用谁写。 * 我们一般写的都是该接口的子接口实现类:MethodInterceptor */ Producer cglibProducer = (Producer)Enhancer.create(producer.getClass(), new MethodInterceptor() { /** * 执行该对象的任何方法都会经过该方法 * @param proxy * @param method * @param args * 以上三个参数和基于接口的动态代理中invoke方法的参数是一样的 * @param methodProxy :当前执行方法的代理对象 * @return * @throws Throwable */ @Override public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { //提供增强的代码 Object returnValue = null; //1.获取方法执行的参数 Float money = (Float)args[0]; //2.判断当前方法是不是销售 if("saleProduct".equals(method.getName())) { returnValue = method.invoke(producer, money*0.8f); } return returnValue; } }); cglibProducer.saleProduct(12000f); } }
-
使用基于接口的动态代理给原Service方法增加事务
public class BeanFactory { private IAccountService accountService; private TransactionManager txManager; public void setTxManager(TransactionManager txManager) { this.txManager = txManager; } public final void setAccountService(IAccountService accountService) { this.accountService = accountService; } /** * 获取Service代理对象 * @return */ public IAccountService getAccountService() { return (IAccountService)Proxy.newProxyInstance(accountService.getClass().getClassLoader(), accountService.getClass().getInterfaces(), new InvocationHandler() { /** * 添加事务的支持 * * @param proxy * @param method * @param args * @return * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if("test".equals(method.getName())){ return method.invoke(accountService,args); } Object rtValue = null; try { //1.开启事务 txManager.beginTransaction(); //2.执行操作 rtValue = method.invoke(accountService, args); //3.提交事务 txManager.commit(); //4.返回结果 return rtValue; } catch (Exception e) { //5.回滚操作 txManager.rollback(); throw new RuntimeException(e); } finally { //6.释放连接 txManager.release(); } } }); } }
-
bean.xml中配置注入
<!--配置代理的service--> <bean id="proxyAccountService" factory-bean="beanFactory" factory-method="getAccountService"></bean> <!--配置beanfactory--> <bean id="beanFactory" class="com.itheima.factory.BeanFactory"> <!-- 注入service --> <property name="accountService" ref="accountService"></property> <!-- 注入事务管理器 --> <property name="txManager" ref="txManager"></property> </bean> <!-- 配置Service --> <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"> <!-- 注入dao --> <property name="accountDao" ref="accountDao"></property> </bean>
2.7.2 AOP的相关概念
-
概念:它就是把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的技术上,对我们的已有方法进行增强。可以减少重复代码,提高开发效率,而且维护方便
-
关于代理的选择:spring中会根据目标类是否实现了接口来觉得采用哪种动态代理的方式
-
相关术语(概念性的,有利于后期自学)
-
Joinpoint(连接点):是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点。例如我们在业务层的方法都是连接点
-
Pointcut(切入点):是指我们要对哪些Joinpoint进行拦截的定义。切入点就是被增强的方法,是连接点的子集。例如对账户的增删改查
-
Advice(通知/增强):通知是指拦截到Joinpoint之后所要做的事情
-
Introduction(引介):在不修改类代码的前提下,可以在运行期为类动态地添加一些方法或Field
-
Target(目标对象):被代理对象
-
Weaving(织入):把增强应用到目标对象来创建新的代理对象的过程。spring采用动态代理织入
-
Proxy(代理):一个类被AOP织入增强后,就产生了一个结果代理类
-
Aspect(切面):是切入点和通知(引介)的结合。仔细理解
-
2.8 Spring中的AOP
2.8.1 基于xml的配置
-
学习Spring中的AOP要明确的事
-
开发阶段(我们要做的)
编写核心业务代码(开发主线):大部分程序员来做,要求熟悉业务需求。
把公用代码抽取出来,制作成通知。(开发阶段最后再做):AOP 编程人员来做。
在配置文件中,声明切入点与通知间的关系,即切面。:AOP 编程人员来做。
-
运行阶段(Spring框架完成的)
Spring 框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。
-
-
实战演示
-
编写Service方法,和一个用于记录日志的前置通知方法,对Service中的saveAccount方法进行增强
-
基于XML配置
<!--spring中基于XML的AOP配置步骤 1、把通知Bean也交给spring来管理 2、使用aop:config标签表明开始AOP的配置 3、使用aop:aspect标签表明配置切面 id属性:是给切面提供一个唯一标识 ref属性:是指定通知类bean的Id。 4、在aop:aspect标签的内部使用对应标签来配置通知的类型 我们现在示例是让printLog方法在切入点方法执行之前之前:所以是前置通知 aop:before:表示配置前置通知 method属性:用于指定Logger类中哪个方法是前置通知 pointcut属性:用于指定切入点表达式,该表达式的含义指的是对业务层中哪些方法增强 切入点表达式的写法:(pom文件中引入的aspecjweaver负责解析) 关键字:execution(表达式) 表达式: 访问修饰符 返回值 包名.包名.包名...类名.方法名(参数列表) 标准的表达式写法: public void com.itheima.service.impl.AccountServiceImpl.saveAccount() 访问修饰符可以省略 void com.itheima.service.impl.AccountServiceImpl.saveAccount() 返回值可以使用通配符,表示任意返回值 * com.itheima.service.impl.AccountServiceImpl.saveAccount() 包名可以使用通配符,表示任意包。但是有几级包,就需要写几个*. * *.*.*.*.AccountServiceImpl.saveAccount()) 包名可以使用..表示当前包及其子包 * *..AccountServiceImpl.saveAccount() 类名和方法名都可以使用*来实现通配 * *..*.*() 参数列表: 可以直接写数据类型: 基本类型直接写名称 int 引用类型写包名.类名的方式 java.lang.String 可以使用通配符表示任意类型,但是必须有参数 可以使用..表示有无参数均可,有参数可以是任意类型 全通配写法: * *..*.*(..) 实际开发中切入点表达式的通常写法: 切到业务层实现类下的所有方法 * com.itheima.service.impl.*.*(..) -->
-
四种常用通知类型+切入点表达式+环绕通知的xml配置
<aop:config> <!-- 配置切入点表达式 id属性用于指定表达式的唯一标识。expression属性用于指定表达式内容 此标签写在aop:aspect标签内部只能当前切面使用。 它还可以写在aop:aspect外面,此时就变成了所有切面可用 --> <aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut> <!--配置切面 --> <aop:aspect id="logAdvice" ref="logger"> <!-- 配置前置通知:在切入点方法执行之前执行 <aop:before method="beforePrintLog" pointcut-ref="pt1" ></aop:before>--> <!-- 配置后置通知:在切入点方法正常执行之后值。它和异常通知永远只能执行一个 <aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt1"></aop:after-returning>--> <!-- 配置异常通知:在切入点方法执行产生异常之后执行。它和后置通知永远只能执行一个 <aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing>--> <!-- 配置最终通知:无论切入点方法是否正常执行它都会在其后面执行 <aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after>--> <!-- 配置环绕通知 详细的注释请看Logger类中--> <aop:around method="aroundPringLog" pointcut-ref="pt1"></aop:around> </aop:aspect> </aop:config>
public class Logger { /** * 前置通知 */ public void beforePrintLog(){ System.out.println("前置通知Logger类中的beforePrintLog方法开始记录日志了。。。"); } /** * 后置通知 */ public void afterReturningPrintLog(){ System.out.println("后置通知Logger类中的afterReturningPrintLog方法开始记录日志了。。。"); } /** * 异常通知 */ public void afterThrowingPrintLog(){ System.out.println("异常通知Logger类中的afterThrowingPrintLog方法开始记录日志了。。。"); } /** * 最终通知 */ public void afterPrintLog(){ System.out.println("最终通知Logger类中的afterPrintLog方法开始记录日志了。。。"); } /** * 环绕通知 * 问题: * 当我们配置了环绕通知之后,切入点方法没有执行,而通知方法执行了。 * 分析: * 通过对比动态代理中的环绕通知代码,发现动态代理的环绕通知有明确的切入点方法调用,而我们的代码中没有。 * 解决: * Spring框架为我们提供了一个接口:ProceedingJoinPoint。该接口有一个方法proceed(),此方法就相当于明确调用切入点方法。 * 该接口可以作为环绕通知的方法参数,在程序执行时,spring框架会为我们提供该接口的实现类供我们使用。 * * spring中的环绕通知: * 它是spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式。 */ public Object aroundPringLog(ProceedingJoinPoint pjp){ Object rtValue = null; try{ Object[] args = pjp.getArgs();//得到方法执行所需的参数 System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。前置"); rtValue = pjp.proceed(args);//明确调用业务层方法(切入点方法) System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。后置"); return rtValue; }catch (Throwable t){ System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。异常"); throw new RuntimeException(t); }finally { System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。最终"); } } }
-
2.8.2 基于注解的配置
-
先修改bean.xml文件内容
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 配置spring创建容器时要扫描的包--> <context:component-scan base-package="com.itheima"></context:component-scan> <!-- 配置spring开启注解AOP的支持 --> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> </beans>
-
修改通知方法Logger
/** * 用于记录日志的工具类,它里面提供了公共的代码 */ @Component("logger") @Aspect//表示当前类是一个切面类 public class Logger { @Pointcut("execution(* com.itheima.service.impl.*.*(..))") private void pt1(){} /** * 前置通知 */ // @Before("pt1()") public void beforePrintLog(){ System.out.println("前置通知Logger类中的beforePrintLog方法开始记录日志了。。。"); } /** * 后置通知 */ // @AfterReturning("pt1()") public void afterReturningPrintLog(){ System.out.println("后置通知Logger类中的afterReturningPrintLog方法开始记录日志了。。。"); } /** * 异常通知 */ // @AfterThrowing("pt1()") public void afterThrowingPrintLog(){ System.out.println("异常通知Logger类中的afterThrowingPrintLog方法开始记录日志了。。。"); } /** * 最终通知 */ // @After("pt1()") public void afterPrintLog(){ System.out.println("最终通知Logger类中的afterPrintLog方法开始记录日志了。。。"); } /** * 环绕通知 * 问题: * 当我们配置了环绕通知之后,切入点方法没有执行,而通知方法执行了。 * 分析: * 通过对比动态代理中的环绕通知代码,发现动态代理的环绕通知有明确的切入点方法调用,而我们的代码中没有。 * 解决: * Spring框架为我们提供了一个接口:ProceedingJoinPoint。该接口有一个方法proceed(),此方法就相当于明确调用切入点方法。 * 该接口可以作为环绕通知的方法参数,在程序执行时,spring框架会为我们提供该接口的实现类供我们使用。 * * spring中的环绕通知: * 它是spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式。 */ @Around("pt1()") public Object aroundPringLog(ProceedingJoinPoint pjp){ Object rtValue = null; try{ Object[] args = pjp.getArgs();//得到方法执行所需的参数 System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。前置"); rtValue = pjp.proceed(args);//明确调用业务层方法(切入点方法) System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。后置"); return rtValue; }catch (Throwable t){ System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。异常"); throw new RuntimeException(t); }finally { System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。最终"); } } }
-
如果想彻底删除bean.xml文件
@Configuration
@ComponentScan(basePackages="com.itheima")
@EnableAspectJAutoProxy //添加这个
public class SpringConfiguration {
}
2.8.3 对引入中BeanFactory的改造
-
Factory是添加事务的工厂类,可以用aop代替
public class BeanFactory { private IAccountService accountService; private TransactionManager txManager; public void setTxManager(TransactionManager txManager) { this.txManager = txManager; } public final void setAccountService(IAccountService accountService) { this.accountService = accountService; } /** * 获取Service代理对象 * @return */ public IAccountService getAccountService() { return (IAccountService)Proxy.newProxyInstance(accountService.getClass().getClassLoader(), accountService.getClass().getInterfaces(), new InvocationHandler() { /** * 添加事务的支持 * * @param proxy * @param method * @param args * @return * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if("test".equals(method.getName())){ return method.invoke(accountService,args); } Object rtValue = null; try { //1.开启事务 txManager.beginTransaction(); //2.执行操作 rtValue = method.invoke(accountService, args); //3.提交事务 txManager.commit(); //4.返回结果 return rtValue; } catch (Exception e) { //5.回滚操作 txManager.rollback(); throw new RuntimeException(e); } finally { //6.释放连接 txManager.release(); } } }); } }
-
bean.xml中导入aop的约束并添加配置
<aop:config> <aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut> <aop:aspect id="logAdvice" ref="txManager"> <aop:before method="beginTransaction" pointcut-ref="pt1"></aop:before> <aop:after-returning method="commit" pointcut-ref="pt1"></aop:after-returning> <aop:after-throwing method="rollback" pointcut-ref="pt1"></aop:after-throwing> <aop:after method="release" pointcut-ref="pt1"></aop:after> </aop:aspect> </aop:config>
-
改造成注解配置
- 加上@Component(“connectUtils”)、@Autowired、@Aspect注解,删除set方法,并且把已经配好的从bean.xml中删除
- 注解必须使用环绕通知才可以保证顺序正确,使用@Around
2.9 JdbcTemplate
-
简介
-
基本使用
-
导包 jdbc和tx(事务相关)
-
先用老方法使用
public static void main(String[] args) { DriverManagerDataSource ds = new DriverManagerDataSource(); ds.setDriverClassName("com.mysql.jdbc.Driver"); ds.setUrl("jdbc:mysql://localhost:3306/eesy"); ds.setUsername("root"); ds.setPassword("root"); JdbcTemplate jt = new JdbcTemplate(); jt.setDataSource(ds); jt.execute("insert into account(name,money)values('ccc',100)"); }
-
使用IOC解耦以及属性注入
-
bean.xml文件
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"></property> </bean> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"></property> <property name="url" value="jdbc:mysql://localhost:3306/eesy"></property> <property name="username" value="root"></property> <property name="password" value="root"></property> </bean>
-
main文件
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); JdbcTemplate jt = ac.getBean("jdbcTemplate",JdbcTemplate.class); jt.execute("insert into account(name,money)values('ddd',222)");
-
-
CRUD
-
代码部分
public static void main(String[] args) { ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); JdbcTemplate jt = ac.getBean("jdbcTemplate",JdbcTemplate.class); // jt.execute("insert into account(name,money)values('ddd',222)"); //增加一行数据 jt.update("insert into account(name,money)values(?,?)","eee",111f); //删除一行数据 jt.update("delete from account where id = ?",3); //修改一条数据 jt.update("update account set name=?,money=? where id=?","qwe",1234,4); //查询所有数据 //使用BeanPropertyRowMapper就可以不用自己实现RowMapper接口了 List<Account> accounts = jt.query("select * from account where money > ?", new BeanPropertyRowMapper<Account>(Account.class), 500f); for (Account account : accounts) { System.out.println(account); } //查询一个数据 List<Account> accounts = jt.query("select * from account where id = ?", new BeanPropertyRowMapper<Account>(Account.class), 1); System.out.println(accounts.isEmpty()?"没有内容":accounts.get(0)); //查询一行一列 Integer count = jt.queryForObject("select count(*) from account where money > ?", Integer.class, 500f); System.out.println(count); }
-
分析方法
-
-
在Dao中的使用
创建接口和实现类,并且注入jdbcTemplate
//Dao层中下面两行可以抽取出来,继承spring中的JdbcDaoSupport(老师又默默地带我们手撕源码)。但是就不能在下面这行加@Autowired注解了 private JdbcTemplate jt; public void setJt(JdbcTemplate jt) { this.jt = jt; }
-
3.10 事务
-
目的:使用spring代替之前自己定义的事务方法
@Component("txManager") @Aspect public class TransactionManager { @Autowired private ConnectionUtils connectionUtils; @Pointcut("execution(* com.itheima.service.impl.*.*(..))") private void pt1(){} /** * 开启事务 */ public void beginTransaction(){ try { connectionUtils.getThreadConnection().setAutoCommit(false); }catch (Exception e){ e.printStackTrace(); } } /** * 提交事务 */ public void commit(){ try { connectionUtils.getThreadConnection().commit(); }catch (Exception e){ e.printStackTrace(); } } /** * 回滚事务 */ public void rollback(){ try { connectionUtils.getThreadConnection().rollback(); }catch (Exception e){ e.printStackTrace(); } } /** * 释放连接 */ public void release(){ try { connectionUtils.getThreadConnection().close();//还回连接池中 connectionUtils.removeConnection(); }catch (Exception e){ e.printStackTrace(); } } @Around("pt1()") public Object aroundAdvice(ProceedingJoinPoint pjp){ Object rtValue = null; try{ Object[] args = pjp.getArgs(); this.beginTransaction(); rtValue = pjp.proceed(args); this.commit(); return rtValue; }catch (Throwable e){ this.rollback(); throw new RuntimeException(e); }finally { this.release(); } } }
-
基于xml的声明式事务控制
-
在官方文档中找到并引入tx约束
<?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/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> </bean>
-
<!-- spring中基于XML的声明式事务控制配置步骤 1、配置事务管理器 2、配置事务的通知 此时我们需要导入事务的约束 tx名称空间和约束,同时也需要aop的 使用tx:advice标签配置事务通知 属性: id:给事务通知起一个唯一标识 transaction-manager:给事务通知提供一个事务管理器引用 3、配置AOP中的通用切入点表达式 4、建立事务通知和切入点表达式的对应关系 5、配置事务的属性 是在事务的通知tx:advice标签的内部 --> <!-- 配置事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 配置事务的通知--> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <!-- 配置事务的属性 isolation:用于指定事务的隔离级别。默认值是DEFAULT,表示使用数据库的默认隔离级别。 propagation:用于指定事务的传播行为。默认值是REQUIRED,表示一定会有事务,增删改的选择。查询方法可以选择SUPPORTS。 read-only:用于指定事务是否只读。只有查询方法才能设置为true。默认值是false,表示读写。 timeout:用于指定事务的超时时间,默认值是-1,表示永不超时。如果指定了数值,以秒为单位。 rollback-for:用于指定一个异常,当产生该异常时,事务回滚,产生其他异常时,事务不回滚。没有默认值。表示任何异常都回滚。 no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时事务回滚。没有默认值。表示任何异常都回滚。 --> <tx:attributes> <tx:method name="*" propagation="REQUIRED" read-only="false"/> <tx:method name="find*" propagation="SUPPORTS" read-only="true"></tx:method> </tx:attributes> </tx:advice> <!-- 配置aop--> <aop:config> <!-- 配置切入点表达式--> <aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut> <!--建立切入点表达式和事务通知的对应关系 --> <aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor> </aop:config>
-
-
基于注解的声明式事务控制
-
加入xmlns:context的约束
-
配置spring创建容器时要扫描的包
-
添加其他service和dao注解同上
-
如下
<!-- spring中基于注解 的声明式事务控制配置步骤 1、配置事务管理器 2、开启spring对注解事务的支持 3、在需要事务支持的地方使用@Transactional注解,里面可以配置属性(如果很多地方的话那么便捷性不如xml) --> <!-- 配置事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 开启spring对注解事务的支持--> <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
-
-
基于纯注解的声明式事务控制
- 一步步用注解和配置类把bean.xml代替
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 配置spring创建容器时要扫描的包--> <context:component-scan base-package="com.itheima"></context:component-scan> <!-- 配置JdbcTemplate--> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 配置数据源--> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"></property> <property name="url" value="jdbc:mysql://localhost:3306/eesy"></property> <property name="username" value="root"></property> <property name="password" value="1234"></property> </bean> <!-- spring中基于注解 的声明式事务控制配置步骤 1、配置事务管理器 2、开启spring对注解事务的支持 3、在需要事务支持的地方使用@Transactional注解 --> <!-- 配置事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 开启spring对注解事务的支持--> <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven> </beans>
- 创建一个配置类,其中的两个配置属性类以及一个连接数据库的资源文件
- 配置文件类
/** * spring的配置类,相当于bean.xml */ @Configuration @ComponentScan("com.itheima") @Import({JdbcConfig.class,TransactionConfig.class}) @PropertySource("jdbcConfig.properties") @EnableTransactionManagement public class SpringConfiguration { }
- Jdbc和数据源
public class JdbcConfig { @Value("${jdbc.driver}") private String driver; @Value("${jdbc.url}") private String url; @Value("${jdbc.username}") private String username; @Value("${jdbc.password}") private String password; /** * 创建JdbcTemplate * @param dataSource * @return */ @Bean(name="jdbcTemplate") public JdbcTemplate createJdbcTemplate(DataSource dataSource){ return new JdbcTemplate(dataSource); } /** * 创建数据源对象 * @return */ @Bean(name="dataSource") public DataSource createDataSource(){ DriverManagerDataSource ds = new DriverManagerDataSource(); ds.setDriverClassName(driver); ds.setUrl(url); ds.setUsername(username); ds.setPassword(password); return ds; } }
- 事务管理器
/** * 和事务相关的配置类 */ public class TransactionConfig { /** * 用于创建事务管理器对象 * @param dataSource * @return */ @Bean(name="transactionManager") public PlatformTransactionManager createTransactionManager(DataSource dataSource){ return new DataSourceTransactionManager(dataSource); } }
- 数据库配置文件
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/eesy jdbc.username=root jdbc.password=root
- 测试类中更改注解属性为classes
@ContextConfiguration(classes= SpringConfiguration.class)
3. SpringMVC
3.1 三层架构以及SpringMVC框架的介绍
-
三层架构
-
优势
-
清晰的角色划分: 前端控制器(DispatcherServlet) 请求到处理器映射(HandlerMapping) 处理器适配器(HandlerAdapter) 视图解析器(ViewResolver) 处理器或页面控制器(Controller) 验证器( Validator) 命令对象(Command 请求参数绑定到的对象就叫命令对象) 表单对象(Form Object 提供给表单展示和提交到的对象就叫表单对象)。
-
分工明确,而且扩展点相当灵活,可以很容易扩展,虽然几乎不需要。
-
由于命令对象就是一个 POJO,无需继承框架特定 API,可以使用命令对象直接作为业务对象。
-
和 Spring 其他框架无缝集成,是其它 Web 框架所不具备的。
-
可适配,通过 HandlerAdapter 可以支持任意的类作为处理器。
-
可定制性,HandlerMapping、ViewResolver 等能够非常简单的定制。
-
功能强大的数据验证、格式化、绑定机制。
-
利用 Spring 提供的 Mock 对象能够非常简单的进行 Web 层单元测试。
-
本地化、主题的解析的支持,使我们更容易进行国际化和主题的切换。
-
强大的 JSP 标签库,使 JSP 编写更容易。
………………还有比如RESTful风格的支持、简单的文件上传、约定大于配置的契约式编程支持、基于注解的零配置支持等等。
-
-
和Struts2的优略分析
-
共同点:它们都是表现层框架,都是基于 MVC 模型编写的。
它们的底层都离不开原始 ServletAPI。
它们处理请求的机制都是一个核心控制器。
-
区别:Spring MVC 的入口是 Servlet, 而 Struts2 是 Filter
Spring MVC 是基于方法设计的,而 Struts2 是基于类,Struts2 每次执行都会创建一个动作类。所 以 Spring MVC 会稍微比 Struts2 快些。
Spring MVC 使用更加简洁,同时还支持 JSR303, 处理 ajax 的请求更方便 (JSR303 是一套 JavaBean 参数校验的标准,它定义了很多常用的校验注解,我们可以直接将这些注 解加在我们 JavaBean 的属性上面,就可以在需要校验的时候进行校验了。)
Struts2 的 OGNL 表达式使页面的开发效率相比 Spring MVC 更高些,但执行效率并没有比 JSTL 提 升,尤其是 struts2 的表单标签,远没有 html 执行效率高。
-
3.2 入门程序
3.2.1 编码步骤
-
通过SpringMVC完成一个页面的跳转。分析
-
在index.jsp中写个跳转的超链接
-
配置web.xml
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" > <web-app> <display-name>Archetype Created Web Application</display-name> <servlet> <servlet-name>dispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- 配置Servlet的初始化参数,读取springmvc的配置文件,创建spring容器 --> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc.xml</param-value> </init-param> <!-- 配置servlet启动时加载对象 --> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcherServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
-
配置springmvc.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" 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 http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 配置spring创建容器时要扫描的包 --> <context:component-scan base-package="com.itheima"></context:component-scan> <!-- 配置视图解析器 --> <bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/pages/"></property> <property name="suffix" value=".jsp"></property> </bean> <!-- 配置spring开启注解mvc的支持,默认包含了很多组件--> <mvc:annotation-driven/> </beans>
-
编写controller代码
@Controller //这个不是MVC的注解,是扫描的注解 public class HelloController { @RequestMapping(path = "/hello") public String sayHello(){ System.out.println("Hello StringMVC"); return "success"; } }
-
在跳转到的success.jsp中写出访问成功
-
项目结构
3.2.2 流程总结
-
大概的流程
-
当启动Tomcat服务器的时候,因为配置了load-on-startup标签,所以会创建DispatcherServlet对象, 就会加载springmvc.xml配置文件
-
开启了注解扫描,那么HelloController对象就会被创建
-
从index.jsp发送请求,请求会先到达DispatcherServlet核心控制器,根据配置@RequestMapping注解 找到执行的具体方法
-
根据执行方法的返回值,再根据配置的视图解析器,去指定的目录下查找指定名称的JSP文件
-
Tomcat服务器渲染页面,做出响应
-
-
详细的流程以及组件分析
-
前端控制器(DispatcherServlet)
-
处理器映射器(HandlerMapping)
-
处理器(Handler)
-
处理器适配器(HandlAdapter)
-
视图解析器(View Resolver)
-
视图(View)
-
3.2.3 RequestMapping注解
-
RequestMapping注解的作用是建立请求URL和处理方法之间的对应关系
-
RequestMapping注解可以作用在方法和类上
- 作用在类上:第一级的访问目录
- 作用在方法上:第二级的访问目录
- 细节:路径可以不编写 / 表示应用的根目录开始
- 细节:${ pageContext.request.contextPath }也可以省略不写,但是路径上不能写
-
RequestMapping的属性
- path 指定请求路径的url
- value value属性和path属性是一样的
- method 指定该方法的请求方式
- params 指定限制请求参数的条件
- headers 发送的请求中必须包含的请求头
3.3 参数绑定
-
入门程序
-
新建param.jsp
<body> <h3>参数绑定入门程序</h3> <a href="param/testParam?username=hehe&password=123">测试参数</a> </body>
-
Controller可以通过参数接收
@Controller @RequestMapping("/param") public class ParamController { @RequestMapping("/testParam") public String testParam(String username,String password){ System.out.println("用户名是:"+ username); System.out.println("密码是:"+ password); return "success"; } }
-
-
绑定实体类型
-
页面改成表单提交
<form action="param/saveAccount" method="post"> 姓名:<input type="text" name="username" /><br> 密码:<input type="text" name="password" /><br> 金额:<input type="text" name="money" /><br> 用户姓名:<input type="text" name="user.uname" /><br> 用户年龄:<input type="text" name="user.age" /><br> <input type="submit" value="提交" /><br> </form>
-
新建Account类和User类,User类是Accont类中的属性
-
controller
@RequestMapping("/saveAccount") public String saveAccount(Account account){ System.out.println(account); return "success"; }
-
-
解决中文乱码问题
-
以前通过配置request.setCharacterEnCoding方法解决
-
现在通过在web.xml中配置过滤器
<filter> <filter-name>characterEncodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>characterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
-
-
绑定集合类型
-
Account实体类中添加List和Map集合的属性
-
表单的值修改成如下格式
用户姓名:<input type="text" name="list[0].uname" /><br> 用户年龄:<input type="text" name="list[0].age" /><br> 用户姓名:<input type="text" name="map['one'].uname" /><br> 用户年龄:<input type="text" name="user['one'].age" /><br>
-
-
自定义类型转换器
-
问题:2020-11-11识别不了,MVC只能自动转换2020/11/11类型的
-
创建一个类实现Converter接口
public class StringToDateConverter implements Converter<String,Date>{ /** * String source 传入进来字符串 * @param source * @return */ public Date convert(String source) { // 判断 if(source == null){ throw new RuntimeException("请您传入数据"); } DateFormat df = new SimpleDateFormat("yyyy-MM-dd"); try { // 把字符串转换日期 return df.parse(source); } catch (Exception e) { throw new RuntimeException("数据类型转换出现错误"); } } }
-
配置springmvc.xml
<!--配置自定义类型转换器--> <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean"> <property name="converters"> <set> <bean class="cn.itcast.utils.StringToDateConverter"/> </set> </property> </bean> <!-- 开启SpringMVC框架注解的支持 --> <mvc:annotation-driven conversion-service="conversionService"/>
-
-
获取Servlet原生的API
直接在Controller的方法里面传参数就行了
@RequestMapping("/testServlet") public String testServlet(HttpServletRequest request, HttpServletResponse response){ System.out.println("执行了..."); System.out.println(request); HttpSession session = request.getSession(); System.out.println(session); ServletContext servletContext = session.getServletContext(); System.out.println(servletContext); System.out.println(response); return "success"; }
3.4 常用注解
-
RequestParam
-
问题:之前的请求中参数是什么名称,控制器中的形式参数也必须是这个名称,如果不是,就需要使用这个参数
-
属性
- value:请求参数中的名称
- required:请求参数中是否必须提供此参数。默认值是true,表示必须提供,不然将报错
-
用在参数的前面
public String testRequestParam(@RequestParam(name="name") String username)
-
-
RequestBody
- 作用:用于获取请求体内容,直接使用得到的是key=value&key=value…结构的数据(get请求不适用)
- 属性:
- request:是否必须有请求体,默认值是true,get请求会报错,如果是false,get请求得到null
-
PathVariable
-
作用:用于绑定url中的占位符。例如:请求url中/delete/{id},这个{id}就是url占位符。url支持占位符是spring3.0之后加入的。是springmvc支持rest风格URL的一个重要标志
-
属性:
- value:用于指定url中占位符名称
- required:是否必须提供占位符
-
restful编程风格
-
在jsp和controller中的使用
<a href="anno/testPathVariable/10">RequestParam</a>
@RequestMapping(path = "/testPathVariable/{sid}") public String testPathVariable(@PathVariable("sid") String id){ System.out.println(id); //输出10 return "success"; }
-
-
HiddentHttpMethodFilter
- 浏览器form表单只支持GET和POST请求,这个过滤器可以将浏览器请求改为指定的请求方式,但是一般可以使用WenCilent类完成本功能,这个不常用
-
RequestHeader
- 获取请求消息头,但是一般不怎么用
-
CookieValue
- 获取浏览器的Cookie,不常用
-
ModelAttibute
-
作用:它可以用于修饰方法和参数,出现在方法上,表示当前的方法会在控制器的方法执行之前先执行。它可以修饰没有返回值的方法,也可以修饰有具体返回值的方法
-
属性:value:用于获取数据的key,key可以是pojo的属性名称,也可以是map结构的key
-
应用场景:当表单提交数据不是完整的实体类数据时,保证没有提交数据的字段使用数据库对象原来的数据
-
例子:用户有uname,age,date三个字段,但是表单只提交uname和age,如果想让date保持数据库原来的值可以使用这个注解
-
有返回值
@ModelAttribute public void showUser(String name){ System.out.println("showUser执行了"); User user = new User(); user.setUname(name); user.setAge(20); user.setDate(new Date()); map.put("abc",user); } @ModelAttribute public User showUser(String name){ System.out.println("showUser执行了"); User user = new User(); user.setUname(name); user.setAge(20); user.setDate(new Date()); return user; }
-
有返回值
@RequestMapping(path = "/testModelAttribute") public String testModelAttribute(@ModelAttribute("abc") User user){ System.out.println(user); return "success"; } @ModelAttribute public void showUser(String name, Map<String,User> map){ System.out.println("showUser执行了"); User user = new User(); user.setUname(name); user.setAge(20); user.setDate(new Date()); map.put("abc",user); }
-
-
-
SessionAttribute
-
作用:用于多次执行控制器方法间的参数共享
-
属性:
- value:用于指定存入的属性名称
- type:用于指定存入的数据类型
-
例子
-
创建三个超链接分别指向三个controller
-
controller
@Controller @RequestMapping("/anno") @SessionAttributes(value = {"msg"}) //只能作用到类上 public class AnnoController { @RequestMapping(path = "/testSessionAttribute") public String testSessionAttribute(Model model){ model.addAttribute("msg","美美"); return "success"; } @RequestMapping(path = "/getSessionAttribute") public String getSessionAttribute(ModelMap modelMap){ modelMap.get("msg"); return "success"; } @RequestMapping(path = "/delSessionAttribute") public String delSessionAttribute(SessionStatus status){ status.setComplete(); return "success"; } }
-
success.jsp
${msg} ${sessionScope}
-
-
3.5 响应数据类型
-
返回值是String类型
-
将查询出的对象存入request域中
@RequestMapping("/testString") public String testString(Model model){ System.out.println("testString方法执行了。。。"); //模拟从数据库中查询出User对象 User user = new User(); user.setUsername("张三"); user.setPassword("123"); user.setAge(20); model.addAttribute("user",user); return "success"; }
-
再在前端通过el表达式显示
${user.username} ${user.password}
-
-
返回值是void类型
-
默认会发送到WEB-INF目录下对应访问地址的jsp文件
-
可以按照传统方法指定显示路径
@RequestMapping("/testVoid") public void testVoid(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("testVoid方法执行了。。。"); //转发 // request.getRequestDispatcher("/WEB-INF/pages/success.jsp").forward(request,response); //重定向 // response.sendRedirect(request.getContextPath()+"/index.jsp"); //解决中文问题 response.setCharacterEncoding("UTF-8"); response.setContentType("text/html;charset=UTF-8"); //直接进行会话响应 response.getWriter().write("你好"); return; }
-
-
返回值是ModelAndView类型
-
与返回值是String类型差不多,那个其实就是以这个作为底层原理
@RequestMapping("/testModelAndView") public ModelAndView testModelAndView(){ //这个接口作为形式参数是怎么发挥作用的 System.out.println("testString方法执行了。。。"); ModelAndView mv = new ModelAndView(); //模拟从数据库中查询出User对象 User user = new User(); user.setUsername("李四"); user.setPassword("321"); user.setAge(20); mv.addObject("user",user); mv.setViewName("success");//可以使用视图解析器 return mv; }
-
-
使用使用forward和redirect进行页面跳转
-
使用关键字的方法进行转发或重定向
@RequestMapping("/testForwardAndRedirect") public String testForwardAndRedirect(){ //这个接口作为形式参数是怎么发挥作用的 System.out.println("testForwardAndRedirect。。。"); //请求转发 // return "forward:/WEB-INF/pages/success.jsp"; //重定向 return "redirect:/index.jsp"; //该jsp文件放在webapp目录下 }
-
-
过滤静态资源
-
引入jQuery,并绑定一个点击事件
<head> <title>Title</title> <script src="js/jquery.min.js"></script> <script> $(function () { $("#btn").click(function () { alert("hello btn"); }); }); </script> </head> <body> <button id="btn">点我</button> </body>
-
在springmvc.xml中设置静态资源不过滤
<!-- 设置静态资源不过滤 --> <mvc:resources location="/css/" mapping="/css/**"/> <!-- 样式 --> <mvc:resources location="/images/" mapping="/images/**"/> <!-- 图片 --> <mvc:resources location="/js/" mapping="/js/**"/> <!-- javascript -->
-
-
响应json数据之发送ajax的请求
-
用如下代码替换上面的alert
$.ajax({ url:"user/testAjax", contentType:"application/json;charset=UTF-8", data:'{"username":"hehe","password":"123","age":30}', dataType:"json", type:"post", success:function (data) { } });
-
模拟异步请求响应
@RequestMapping("/testAjax") public void testAjax(@RequestBody String body){ System.out.println("testAjax。。。"); System.out.println(body); }
-
显示结果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tiicAEH5-1605952528223)(C:\Users\chenzhijian\AppData\Roaming\Typora\typora-user-images\image-20201113162537554.png)]
-
-
响应json数据之响应json格式数据
-
导入jackson的jar包,使得json字符串和javaBean对象可以互相转换
-
将前端传过来的json数据包装到user对象中
@RequestMapping("/testAjax") public @ResponseBody User testAjax(@RequestBody User user){ System.out.println("testAjax。。。"); System.out.println(user); user.setUsername("haha"); user.setAge(40); return user; }
-
前端显示后端更改的数据
success:function (data) { alert(data); alert(data.username); alert(data.password); alert(data.age); }
-
3.6 文件上传
-
原理分析
-
代码实现
-
引入fileupload和io的pom依赖
-
配置解析器对象
<!-- 配置文件解析器对象,要求id名称必须是multipartResolver --> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="maxUploadSize" value="10485760"/> </bean>
-
创建表单
<form action="user/fileupload" method="post" enctype="multipart/form-data"> 选择文件:<input type="file" name="upload" /><br> <input type="submit" value="上传"> </form>
-
controller代码
@RequestMapping("/fileupload") public String fileUpload(HttpServletRequest request, MultipartFile upload) throws Exception { //upload这个必须与上传文件表单的name属性值一样 System.out.println("文件上传..."); //上传的位置 String path = request.getSession().getServletContext().getRealPath("/uploads/"); //判断该路径是否存在 File file = new File(path); if(!file.exists()){ file.mkdirs(); } //设置唯一文件名 String filename = upload.getOriginalFilename(); String uuid = UUID.randomUUID().toString().replace("-",""); filename = uuid+"_"+filename; //上传文件 upload.transferTo(new File(path,filename)); return "success"; }
-
3.7 跨服务器上传
-
分析
-
服务器分为:应用服务器,数据库服务器,缓存和消息服务器,文件服务器
-
演示
-
-
代码实现
-
引入jersey的pom依赖
-
创建表单
-
代码实现
@RequestMapping("/fileupload2") public String fileUpload2(MultipartFile upload) throws Exception { //upload这个必须与上传文件表单的name属性值一样 System.out.println("服务器文件上传..."); //上传的位置 String path = "http://localhost:9090/uploads/"; //设置唯一文件名 String filename = upload.getOriginalFilename(); String uuid = UUID.randomUUID().toString().replace("-",""); filename = uuid+"_"+filename; //创建客户端对象 Client client = Client.create(); //建立连接 WebResource webResource = client.resource(path+filename); //上传文件 webResource.put(upload.getBytes()); return "success"; }
-
出现403forbidden
更改tomcat配置,详细见https://blog.csdn.net/Dawn510/article/details/103915414
-
3.8 异常处理
-
Controller调用service,service调用dao,异常都是向上抛出的,最终有DispatcherServlet找异常处理器进行异常的处理
-
controller
@RequestMapping("/testException") public String fileException() throws SysException { System.out.println("Exception..."); try { int i = 10/0; } catch (Exception e) { e.printStackTrace(); throw new SysException("查询所有用户出现了错误。。。"); } return "success"; }
-
编写自定义异常类
public class SysException extends Exception{ private String message; public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public SysException(String message) { this.message = message; } }
-
编写异常处理器
public class SysExceptionResolver implements HandlerExceptionResolver { @Override public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception ex) { SysException e = null; if(ex instanceof SysException){ e = (SysException) ex; }else { e = new SysException("系统正在维护"); } ModelAndView mv = new ModelAndView(); mv.addObject("errorMsg",e.getMessage()); mv.setViewName("error"); return mv; } }
-
编写error页面
-
配置异常处理器
<bean id="sysExceptionResolver" class="cn.itcast.exception.SysExceptionResolver"> </bean>
3.9 拦截器
-
拦截器的作用
Spring MVC 的处理器拦截器类似于 Servlet 开发中的过滤器 Filter,用于对处理器进行预处理和后处理。
与过滤器的区别:
过滤器是 servlet 规范中的一部分,任何 java web 工程都可以使用。
拦截器是 SpringMVC 框架自己的,只有使用了 SpringMVC 框架的工程才能用。
过滤器在 url-pattern 中配置了/*之后,可以对所有要访问的资源拦截。
拦截器它是只会拦截访问的控制器方法,如果访问的是 jsp,html,css,image 或者 js 是不会进行拦 截的。
它也是 AOP 思想的具体应用。 我们要想自定义拦截器, 要求必须实现:HandlerInterceptor 接口。
-
拦截器类
public class MyInterceptor1 implements HandlerInterceptor{ /** * 预处理,controller方法执行前 * return true表示放行,执行下一个拦截器,如果没有,就执行controller中的方法 * return false不放行,可以用重定向方法调转到希望跳转的页面 * @param request * @param response * @param handler * @return * @throws Exception */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("拦截器方法执行了。。。"); return true; } }
-
springmvc.bean中配置拦截器
<!--配置拦截器--> <mvc:interceptors> <!--配置拦截器--> <mvc:interceptor> <!--拦截的路径--> <mvc:mapping path="/user/*"/> <!--不要拦截的路径 <mvc:exclude-mapping path=""/>--> <bean class="cn.itcast.interceptor.MyInterceptor1"></bean> </mvc:interceptor> </mvc:interceptors>
-
jsp中输出
<body> <h3>访问成功</h3> <% System.out.println("success.jsp执行了..."); %> </body>
-
结果 拦截器->controller->jsp
-
三种处理方法的总结:
- 前处理方法:可以用于登录页面,如果登录了就放行,没登录就跳转到登录页面
- 后处理方法,controller方法执行后,success.jsp执行前,如果后处理方法跳转页面,success.jsp依然会执行
- 最后处理方法,success.jsp页面执行后,该方法会执行,可以用于释放资源
-
如果配多个拦截器
3.10 SSM的整合
3.10.1 搭建环境
-
创建数据库
-
引入pom的属性和依赖
-
创建controller、service、dao、damain,并按之前的规矩写上findAll和saveAccout方法
3.10.2 编写Spring框架
-
在resources资源文件中创建spring的配置文件以及引入log4j
<?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" 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/context http://www.springframework.org/schema/context/spring-context.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"> <!-- 开启注解扫描,要扫描的是service和dao层的注解,要忽略web层注解,因为web层让SpringMVC框架 去管理 --> <context:component-scan base-package="cn.itcast"> <!-- 配置要忽略的注解 --> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan> </beans>
-
在业务层添加@Service(“accountService”)注解
-
在测试类中测试成功
@Test public void run1(){ ApplicationContext ac = new ClassPathXmlApplicationContext("classpath:applicationContext.xml"); AccountService as = (AccountService) ac.getBean("accountService"); as.findAll(); }
3.10.3 编写整合SpringMVC框架
-
先搭建SpringMVC的环境,测试能不能独立运行
-
配置web.xml
<!-- 配置前端控制器:服务器启动必须加载,需要加载springmvc.xml配置文件 --> <servlet> <servlet-name>dispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- 配置初始化参数,创建完DispatcherServlet对象,加载springmvc.xml配置文件 --> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc.xml</param-value> </init-param> <!-- 服务器启动的时候,让DispatcherServlet对象创建 --> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcherServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <!-- 配置解决中文乱码的过滤器 --> <filter> <filter-name>characterEncodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>characterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
-
配置springmvc.xml
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" 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 http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 扫描controller的注解,别的不扫描 --> <context:component-scan base-package="cn.itcast"> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan> <!-- 配置视图解析器 --> <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <!-- JSP文件所在的目录 --> <property name="prefix" value="/WEB-INF/pages/" /> <!-- 文件的后缀名 --> <property name="suffix" value=".jsp" /> </bean> <!-- 设置静态资源不过滤 --> <mvc:resources location="/css/" mapping="/css/**" /> <mvc:resources location="/images/" mapping="/images/**" /> <mvc:resources location="/js/" mapping="/js/**" /> <!-- 开启对SpringMVC注解的支持,一行配好处理器映射器,处理器适配器 --> <mvc:annotation-driven /> </beans>
-
index.jsp页面
<a href="account/findAll">测试</a>
-
Controller
@Controller("accountController") @RequestMapping("/account") public class AccountController { @RequestMapping("/findAll") public String testFindAll(){ System.out.println("表现层:找到所有列表"); return "list"; //list.jsp输出文字提示信息 } }
-
-
整合SpringMVC
-
spring的配置文件并没有加载进去,还不可以使用spring框架的方法
-
分析
-
在web.xml文件中加入监听器,从此,web.xml中把javaweb三大组件凑齐了
<!-- 配置Spring的监听器 --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- 配置加载类路径的配置文件 --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </context-param>
-
controller调用service的findAll()方法
@Autowired private AccountServiceImpl accountService;
-
结果
-
3.10.4 编写整合MyBatis框架
-
先搭建Mybatis环境
-
创建主配置文件sqlMapConfig.xml
<?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> <environments default="mysql"> <environment id="mysql"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql:///ssm"/> <property name="username" value="root"/> <property name="password" value="root"/> </dataSource> </environment> </environments> <!-- 使用的是注解 --> <mappers> <!-- <mapper class="cn.itcast.dao.AccountDao"/> --> <!-- 该包下所有的dao接口都可以使用 --> <package name="cn.itcast.dao"/> </mappers> </configuration>
-
用注解写两个方法的sql语句
-
测试查询
@Test public void run1() throws Exception { // 加载配置文件 InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml"); // 创建工厂 SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream); // 创建sqlSession对象 SqlSession session = factory.openSession(); // 获取代理对象 AccountDao dao = session.getMapper(AccountDao.class); // 调用查询的方法 List<Account> list = dao.findAll(); for (Account account : list) { System.out.println(account); } // 释放资源 session.close(); inputStream.close(); }
-
测试保存(增删改需要自己提交事务)
@Test public void run2() throws Exception { Account account = new Account(); account.setName("熊大"); account.setMoney(300d); // 加载配置文件 InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml"); // 创建工厂 SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream); // 创建sqlSession对象 SqlSession session = factory.openSession(); // 获取代理对象 AccountDao dao = session.getMapper(AccountDao.class); // 调用查询的方法 dao.saveAccount(account); //提交事务 session.commit(); // 释放资源 session.close(); inputStream.close(); }
-
-
整合MyBatis框架
-
将MyBatis配置类中的内容移到spring的配置类applicationContext.xml中,进入ioc容器,完成后可以删除sqlMapConfig.xml
<!-- 配置C3P0的连接池对象 --> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql:///ssm" /> <property name="username" value="root" /> <property name="password" value="root" /> </bean> <!-- 配置SqlSession的工厂 --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> </bean> <!-- 配置扫描dao的包 --> <bean id="mapperScanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="cn.itcast.dao"/> </bean>
-
在Service中注入Dao,并且调用其方法,返回
@Override public List<Account> findAll() { System.out.println("业务层:查询所有的账户信息"); return accountDao.findAll(); }
-
Controller存入域对象中,让页面能够读取
@RequestMapping("/findAll") public String findAll(Model model){ System.out.println("表现层:查询所有的账户信息"); List<Account> list = accountService.findAll(); model.addAttribute("list",list); return "list"; }
-
jsp页面显示出数据
<c:forEach items="${list}" var="account"> ${account.name} </c:forEach>
-
-
配置事务,写保存账户方法
-
配置Spring框架声明式事务管理
<!--配置Spring框架声明式事务管理--> <!--配置事务管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!--配置事务通知,dataSource是别人写的比较通用的,要把自己的结构配置一下--> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="find*" read-only="true"/> <tx:method name="*" isolation="DEFAULT"/> //尽可能避开整体扫描 </tx:attributes> </tx:advice> <!--配置AOP增强--> <aop:config> <aop:advisor advice-ref="txAdvice" pointcut="execution(public * cn.itcast.service..ServiceImpl.*(..))"/> </aop:config>
-
写一个提交表单
<form action="account/save" method="post"> 姓名:<input type="text" name="name"><br/> 金额:<input type="text" name="money"><br/> <input type="submit" value="保存"><br/> </form>
-
Controller调Service,Service调Dao
@RequestMapping("/save") public void save(Account account, HttpServletRequest request, HttpServletResponse response) throws IOException { System.out.println("表现层:保存账户信息"); accountService.saveAccount(account); response.sendRedirect(request.getContextPath()+"/account/findAll"); return; }
-
-
完成
1. MyBatis
1.1 基本概念
- 三层架构
- 表现层:用于展示数据
- 业务层:处理业务需求
- 持久层:和数据库交互
-
持久层技术解决方案
- JDBC技术:Connection,PreparedStatement,ResultSet
- Spring的JdbcTemplate:Spring中对jdbc的简单封装
- Apache的DBUtils:和2很想,也是简单封装
- 以上都不是框架,JDBC是规范,JdbcTemplate和DBUtils都只是工具类
-
mybatis的概述
- mybatis是一个持久层框架,用java编写的。它封装了jdbc操作的很多细节,使开发者只需要关注sql语句本身,而无需关注注册驱动,创建连接等繁杂过程,它使用了ORM思想实现了结果集的封装
- ORM:Object Relational Mapping 对象关系映射
- 就是把数据库表和实体类及实体类的属性对应起来,让我们可以操作实体类就实现操作数据库表
1.2 入门案例
- mybatis的环境搭建
- 准备好数据库
- pom配置,用jar包打包,引入依赖
- mybatis:官网的入门部分找
- mysql:连接数据库
- log4j:日志信息
- junit:单元测试
- 创建实体类,实现Serializable接口,今天要求属性名与数据库对应名称一致,创建dao的接口
- 创建Mybatis的主配置文件SqlMapConfig.xml
- 创建映射配置文件IUserDao.xml
-
注意事项
- 命名中包含Dao是与之前的知识保持一致,也可交做Mapper
- directory和package是不一样的
- mabatis的配置文件位置必须和dao接口的包结构相同
- 映射配置文件的mapper标签namespace属性的取值必须是dao接口的全限定类名
- 映射配置文件的操作配置(select),id属性的取值必须是dao接口的方法名
当我们遵从了第三四五点后,我们在开发中就无需再写dao的实现类
- mybatis的入门
- 读取配置文件
- 创建SqlSessionFactory工厂
- 创建SqlSession
- 创建Dao接口的代理对象
- 执行dao中的方法
- 释放资源
- 注意:不要忘记在映射配置中告知mybatis要封装到哪个实体类中,指定实体类的权限定类名
- mybatis基于注解的入门案例:
- 把IUserDao.xml移除,在dao接口的方法上使用@Select注解,并且指定SQL语句,同时需要在SqlMapConfig.xml中的mapper配置时,使用calss属性指定dao接口的全限定类名。
- 明确:我们为了简便一般不写dao实现类,但是实际上是可以写的
- 入门案例的分析
- 测试类中main方法详解
这些方法都后面都可以封装的,只是现在细一点讲底层原理,而且都写出来的话灵活性更大,参数什么的可以自由调配。
mybatis在使用代理dao的方式实现增删改查时做什么事
-
创建代理对象
-
在代理对象中调用selectList
让这两件事都串起来,每个接口和类都各司其职,以下是分析
- 手撕源码
1.3 完成CRUD
-
在接口中申明方法
public interface IUserDao { List<User> findAll(); void saveUser(User user); void updateUser(User user); void deleteUser(Integer userId); User findById(Integer userId); List<User> findByName(String userName); Integer findTotal(); List<User> findUserByVo(QueryVo vo); }
-
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.itheima.dao.IUserDao">
<select id="findAll" resultType="com.itheima.domain.User">
select * from user
</select>
<insert id="saveUser" parameterType="com.itheima.domain.User">
<selectKey keyProperty="id" keyColumn="id" resultType="int" order="AFTER">
select last_insert_id();
</selectKey>
insert into user(username,address,sex,birthday)values(#{username},#{address},#{sex},#{birthday});
</insert>
<update id="updateUser" parameterType="com.itheima.domain.User">
update user set username=#{username},address=#{address},sex=#{sex},birthday=#{birthday} where id=#{id};
</update>
<delete id="deleteUser" parameterType="java.lang.Integer">
delete from user where id=#{uid};
</delete>
<select id="findById" parameterType="INT" resultType="com.itheima.domain.User">
select * from user where id=#{uid};
</select>
<select id="findByName" parameterType="String" resultType="com.itheima.domain.User">
select * from user where username like #{username};
</select>
<select id="findTotal" resultType="int">
select count(id) from user;
</select>
//根据queryVo中的条件查询用户
<select id="findUserByVo" parameterType="com.itheima.domain.QueryVo" resultType="com.itheima.domain.User">
select * from user where username like #{user.username};
</select>
</mapper>
- QueryVo类,可以添加其他条件
public class QueryVo {
private User user;
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
-
OGNL表达式:Object Graphic Navigation Language(对象 图 导航 语言)
它是通过对象的取值方法来获取数据,在写法上把get给省略了
类中的写法:user.getUsername() -> user.username
mybatis中可以直接写username,因为在parameterType中已经提供了属性所属的类,所以此时不需要写对象名
- Test写对查询到的数据的处理
public class MybatisTest {
private InputStream in = null;
private SqlSession session = null;
private IUserDao userDao = null;
@Before
public void init()throws Exception{
//1.读取配置文件
in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.创建SqlSessionFactory工厂
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(in);
//3.使用工厂生产SqlSession对象
session = factory.openSession();
//4.使用SqlSession创建Dao接口的代理对象
userDao = session.getMapper(IUserDao.class);
}
@After
public void destroy() throws Exception{
session.commit();
//6.释放资源
session.close();
in.close();
}
@Test
public void testFindAll(){
//5.使用代理对象执行方法
List<User> users = userDao.findAll();
for(User user : users){
System.out.println(user);
}
}
@Test
public void testSave(){
User user = new User();
user.setUsername("Mybatis lastsaveuser");
user.setAddress("湖北武汉");
user.setSex("男");
user.setBirthday(new Date());
System.out.println(user);
//5.使用代理对象添加方法
userDao.saveUser(user);
System.out.println(user);
}
@Test
public void testUpdate(){
User user = new User();
user.setId(49);
user.setUsername("Mybatis");
user.setAddress("中国北京");
user.setSex("女");
user.setBirthday(new Date());
//5.使用代理对象更新方法
userDao.updateUser(user);
}
@Test
public void testDelete(){
userDao.deleteUser(49);
}
@Test
public void testFindOne(){
User user = userDao.findById(45);
System.out.println(user);
}
@Test
public void testFindByName(){
List<User> user = userDao.findByName("%王%");
for (User users : user) {
System.out.println(users);
}
}
@Test
public void testTotal(){
int count = userDao.findTotal();
System.out.println(count);
}
@Test
public void testFindByVo(){
User user = new User();
QueryVo vo = new QueryVo();
user.setUsername("%王%");
vo.setUser(user);
List<User> users = userDao.findUserByVo(vo);
for (User u : users) {
System.out.println(u);
}
}
}
-
注意:此时我们的数据库名称和pojo类属性名称是保持一致的,如果不一致,有两种解决方法
- 在sql语句中起别名,如:select id as userId
- 执行速度最快,但是费时间
- 采用配置的方式
- 要读一次xml文件,速度慢一点,但是开发很方便
<resultMap id="userMap" type="com.itheima.domain.User"> <!-- 主键字段的对应--> <id property="userId" column="id"></id> <!-- 非主键字段的对应--> <result property="userName" column="username"></result> <result property="userAddress" column="address"></result> <result property="userSex" column="sex"></result> <result property="userBirthday" column="birthday"></result> </resultMap> <!-- 还要把resultType的属性改为resultMap="userMap"-->
- 在sql语句中起别名,如:select id as userId
使用Mybatis完成DAO层的开发先跳
1.4 连接池及事务
-
连接池:我们在实际开发中都会使用连接池,因为他可以减少我们获取连接所消耗的时间
-
mybatis连接池提供了3种方式的配置
-
位置:主配置文件SqlMapConfig.xml钟的dataSource标签,type属性就是表示采用何种连接池方式
-
type属性的取值:
-
POOLED 采用传统的javax.sql.DataSource规范中的连接池,mybatis中有针对规范的实现
-
UNPOOLED 采用传统的获取连接的方式,虽然也实现Javax.sql.DataSource接口,但是并没有使用池的思想
-
JNDI 采用服务器提供的JNDI技术实现,来获取DataSource对象,不同的服务器所能拿到DataSource是不一样的
- 注意:如果不是web或者maven的war工程,是不能使用的,我们课程中是使用的是tomcat服务器,采用连接池就是dbcp连接池
-
-
-
mybatis中的事务
- 注意面试中常问的问题
- 它是通过sqlsession对象的commit方法和rollback方法实现事务的提交和回滚
- 在openSession方法里的参数添加true,即可自动提交事务,不用在代码中手动提交,不过只有每次执行一个对数据库的crud操作才可以用这种方式
1.5 动态sql
-
if标签
- 如果不知道在User类里面提供了什么信息作为条件,可以使用标签
//查找的是已有条件的交集 <select id="findUserByCondition" parameterType="com.itheima.domain.User" resultType="com.itheima.domain.User"> select * from user where 1 = 1 <if test="username!=null"> and username = #{username} </if> <if test="sex!=null"> and sex = #{sex} </if> </select>
@Test public void testFindUserByCondition(){ User user = new User(); user.setUsername("老王"); user.setSex("女"); List<User> users = userDao.findUserByCondition(user); for (User u : users) { System.out.println(u); } }
-
where标签
- 如果不想出现where 1 = 1这个条件,可以把下面的内容用标签包裹
-
foreach和sql标签
- foreach用于提供sql语句查询多个条件数据的结果,如查id为42,45,46的那些用户的所有数据
<select id="findUserInIds" parameterType="com.itheima.domain.QueryVo" resultType="com.itheima.domain.User"> select * from user <where> <if test="ids!=null and ids.size()>0"> //字符串的拼接 <foreach collection="ids" open="and id in (" close=")" item="id" separator=","> #{id} </foreach> </if> </where> </select>
@Test public void testFindUserInIds(){ QueryVo queryVo = new QueryVo(); List<Integer> list = new ArrayList<Integer>(); list.add(42); list.add(45); list.add(46); queryVo.setIds(list); List<User> users = userDao.findUserInIds(queryVo); for (User user : users) { System.out.println(user); } }
- xml中的#{}是将参数调过来处理,参数类型是parameterType,然后根据resultType将结果传回去
1.6 Mybatis的多表操作
-
完成account的一对一操作,当查询账户时,可以同时得到账户的所属用户信息
- 数据库和domain都建立两张表:用户表(user),账户表(account),用各自的格式表示出一对多的关系
- 数据库用外键约束
- 账户表添加用户表为属性,加上getter,setter方法,并在xml中设置resultMap标签
<resultMap id="accountUserMap" type="account"> //可以直接写account是因为在主配置文件中用typeAliases配置别名,它智能配置domain中类的别名 <id property="id" column="aid"></id> //aid是a.id的别名,在sql语句中体现 <result property="uid" column="uid"></result> <result property="money" column="money"></result> <association property="user" column="uid" javaType="user"> <id property="id" column="id"></id> <result property="username" column="username"></result> <result property="address" column="address"></result> <result property="sex" column="sex"></result> <result property="birthday" column="birthday"></result> </association> </resultMap>
- 测试类中通过调用account.getUser()输出user表的内容
- 数据库和domain都建立两张表:用户表(user),账户表(account),用各自的格式表示出一对多的关系
-
完成user的一对多查询操作,查询用户时,可以同时得到用户下所包含的账户信息
- User类中添加private List accounts,并加上getter,setter方法
- 在xml中设置resultMap标签
<mapper namespace="com.itheima.dao.IUserDao"> <resultMap id="userAccountMap" type="user"> <id property="id" column="id"></id> <result property="username" column="username"></result> <result property="address" column="address"></result> <result property="sex" column="sex"></result> <result property="birthday" column="birthday"></result> <collection property="accounts" ofType="account"> <id property="id" column="aid"></id> <result property="uid" column="uid"></result> <result property="money" column="money"></result> </collection> </resultMap> <select id="findAll" resultMap="userAccountMap"> SELECT * FROM USER u LEFT OUTER JOIN account a ON a.`UID` = u.id; </select> </mapper>
- 测试类中输出
@Test public void testFindAll(){ List<User> users = userDao.findAll(); for (User user : users) { System.out.println(user); System.out.println(user.getAccounts()); } }
-
多对多操作
-
建立两张表:用户表,角色表;让两张表具有多对多的关系,需要使用中间表,中间表包含各种的主键,在中间表是外键
-
建立两个实体类:用户实体类和角色实体类,让两个实体类能体现出来多对多的关系,各自包含对方的一个集合引用
- 如在User表中 private List roles;并生成getter,setter方法
-
建立两个映射配置文件:用户的和角色的配置文件(以角色配置文件举例)
<?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.itheima.dao.IRoleDao"> <resultMap id="roleMap" type="role"> <id property="roleId" column="id"></id> <result property="roleName" column="role_name"></result> <result property="roleDesc" column="role_desc"></result> <collection property="users" ofType="user"> <id property="id" column="id"></id> <result property="username" column="username"></result> <result property="address" column="address"></result> <result property="sex" column="sex"></result> <result property="birthday" column="birthday"></result> </collection> </resultMap> <select id="findAll" resultMap="roleMap"> <!-- 多行sql语句最好在每个后面加一个空格,防止前后连接在一起导致错误--> SELECT * FROM role r LEFT OUTER JOIN user_role ur ON r.`ID`=ur.`RID` LEFT OUTER JOIN USER u ON u.`id`=ur.`UID`; </select> </mapper>
-
实现配置:当我们查询用户时,可以同时得到用户所包含的角色信息,查询角色时,能同时得到角色所属的用户信息。
@Test public void testFindAll(){ List<Role> roles = roleDao.findAll(); for (Role role : roles) { System.out.println(role); System.out.println(role.getUsers()); } }
-
总结:多表操作的步骤都类似,按照流程走就行了
1.7 JNDI
是SUN公司推出的一套规范,属于JavaEE技术之一,目的是模范windows系统的注册表
注册表里面为什么同一个key名称可以有不同的value,因为这些key处在不同的目录中,实际上不是同一个key
1.8 延迟加载
-
概念:在真正使用数据时才发起查询,按需加载(懒加载)
-
什么时候使用:
- 一对一,多对多:通常用延迟加载
- 多对一,一对一:通常用立即加载
- 举例:一个用户有100个账户
-
如何使用:
mybatis第三天实现多表操作时,我们使用了resultMap来实现一对一,一对多,多对多关系的操作。主要 是通过 association、collection 实现一对一及一对多映射。association、collection 具备延迟加载功能
-
一对一实现延迟加载,找到账户表以及对应的用户
-
在之前一对一工程基础上改造
-
配置account的映射配置文件,user的配置文件不变
<?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.itheima.dao.IAccountDao"> <resultMap id="accountUserMap" type="account"> <id property="id" column="id"></id> <result property="uid" column="uid"></result> <result property="money" column="money"></result> <!-- 一对一的关系映射:配置封装user的内容 select属性指定的内容:查询用户的唯一标识 column属性指定的内容:用户根据id查询时,所需要的参数的值 --> <association property="user" column="uid" javaType="user" select="com.itheima.dao.IUserDao.findById">//uid不能省略,要传到findById里面 </association> </resultMap> <select id="findAll" resultMap="accountUserMap"> select * from account; </select> </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"> <mapper namespace="com.itheima.dao.IUserDao"> <resultMap id="userAccountMap" type="user"> <id property="id" column="id"></id> <result property="username" column="username"></result> <result property="address" column="address"></result> <result property="sex" column="sex"></result> <result property="birthday" column="birthday"></result> <collection property="accounts" ofType="account"> <id property="id" column="aid"></id> <result property="uid" column="uid"></result> <result property="money" column="money"></result> </collection> </resultMap> <select id="findAll" resultMap="userAccountMap"> SELECT * FROM user u left outer join account a on u.id = a.uid </select> <select id="findById" resultType="user"> SELECT * FROM user where id = #{uid} </select> </mapper>
-
主配置文件添加setting配置,打开延迟加载开关
<settings> <setting name="lazyLoadingEnabled" value="true"/> <setting name="aggressiveLazyLoading" value="false"></setting> </settings>
- 完成读一个加载一个
-
-
一对多实现延迟加载
- 将之前对account做的对user再做一遍就行,xml中的collection和association用哪个好像无所谓,里面的select属性调用另一个里面的find方法
1.9 缓存
-
概念:存在于内存中的临时数据
-
为什么使用:减少和数据库的交互次数
-
什么时候使用:
- 适用于缓存
- 经常查询并且不经常改变的
- 数据的正确与否对最终结果影响不大的(商品库存,银行汇率,股市牌价)
- 适用于缓存
-
一级缓存和二级缓存
-
一级缓存:指的是Mybatis中SqlSession对象的缓存,当我们执行查询之后,查询的结果会同时存入到SqlSession为我们提供的一块区域中,该区域的结构是一个Map。当SqlSession对象消失时,mybatis的以及缓存也就消失了
sqlSession = factory.close(); //再次获取SqlSession对象 sqlSession = factory.openSession(); userDao = sqlSession.getMapper(IUserDao.class);
sqlSession.clearCache();//此方法也可以清空缓存 userDao = sqlSession.getMapper(IUserDao.class);
- 一级缓存是SqlSession范围的缓存,当调用SqlSession的修改,添加,删除,commit(),close()等方法时,就会清空一级缓存。
-
二级缓存:指的是Mybatis中SqlSessionFacrory对象的缓存。由同一个SqlSessionFactory对象创建的SqlSession共享其缓存。
使用步骤
- 让Mybatis框架支持二级缓存(在SqlMapConfig.xml中配置)
- 让当前的映射文件支持二级缓存(在IUserDao.xml中配置)
- 让当前的操作支持二级缓存(在select标签中配置)
-
1.10 注解开发
-
为什么注解开发可以代替映射配置文件
-
建立实体类属性和数据库列的对应关系,用Results和ResultMap注解,如下
public interface IUserDao { @Select("select * from user") @Results(id = "userMap",value = { @Result(id = true,column = "id",property = "userId"), @Result(column = "username",property = "userName"), @Result(column = "address",property = "userAddress"), @Result(column = "sex",property = "userSex"), @Result(column = "birthday",property = "userBirthday"), }) List<User> findAll(); @Select("select * from user where id=#{id}") @ResultMap("userMap") User findById(Integer userId); @Select("select * from user where username like '%${value}%' ") @ResultMap("userMap") List<User> findByName(String userName); }
-
注解开发一对一的查询配置
-
建立account表的javaBean并且添加private User user;
-
注解
public interface IAccountDao { @Select("select * from account") @Results(id = "accountMap",value = { @Result(id = true,column = "id",property = "id"), @Result(column = "uid",property = "uid"), @Result(column = "money",property = "money"), //下面这行是精华,并且同时实现了延迟加载 @Result(property = "user",column = "uid",one = @One(select = "com.itheima.dao.IUserDao.findById",fetchType = FetchType.EAGER)) }) List<Account> findAll(); }
-
-
注解开发一对多的查询配置
-
在user表中添加private List accounts;
-
IAccountDao中添加查询方法
@Select("select * from account where uid=#{userId}") List<Account> findByUid(Integer userId);
-
IUserDao注解加一行
@Result(property = "accounts",column = "id",many = @Many(select = "com.itheima.dao.IAccountDao.findByUid",fetchType = FetchType.LAZY))
-
-
使用二级缓存
- 一级二级都是默认开启的,如果不想开启二级缓存,可以通过settings设置关闭
2. Spring
2.1 概述
-
概念:Spring是分层的 Java SE/EE应用 full-stack 轻量级开源框架,以 IoC(Inverse Of Control: 反转控制)和 AOP(Aspect Oriented Programming:面向切面编程)为内核,提供了展现层 Spring MVC 和持久层 Spring JDBC 以及业务层事务管理等众多的企业级应用技术
-
优点:方便解耦,简化开发、AOP编程的支持 、声明式事务的支持 、方便程序的测试 、方便集成各种优秀框架、降低 JavaEE API的使用难度 、Java源码是经典学习范例
-
体系结构
2.2 程序间耦合
2.2.1 问题及手动解决方法
-
耦合:程序间的依赖关系,包括:类之间的依赖,方法间的依赖
-
解耦:降低程序间的依赖关系
- 实际开发中应该做到:编译器不依赖,运行时才依赖
- 使用反射来创建对象,而避免使用new关键字
- 通过读取配置文件来获取要创建的对象的全限定类名
-
例子:
- JDBC的注册驱动
//DriverManager.registerDriver(new com.mysql.jdbc.Driver());没有导入jar包编译器就会报错 Class.forName("com.mysql.jdbc.Driver");//写死了,还是不太行
- 曾经写的javaWeb中也出现过这个问题,表现层调业务层,业务层调持久层
-
使用工厂模式解耦
-
创建一个Bean对象的工厂,它就是创建我们的service和dao对象的
- 需要一个配置文件来配置我们的service和dao
- 配置的内容:唯一标识=全限定类名(key)
- 配置文件可以是xml也可以是properties
- 通过读取配置文件中配置的内容,反射创建对象
- Bean:在计算机英语中,有可重用组件的含义
- javabean:用java语言编写的可重用组件。(javabean>实体类)
public class BeanFactory { private static Properties props; static { try { //实例化对象 props = new Properties(); //获取properties文件的流对象 InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties"); props.load(in); }catch(Exception e){ throw new ExceptionInInitializerError("初始化properties失败!"); } } /** * 根据bean的名称获取对象 * @param beanName * @return */ public static Object getBean(String beanName){ Object bean = null; try{ String beanPath = props.getProperty(beanName); bean = Class.forName(beanPath).newInstance(); }catch (Exception e){ e.printStackTrace(); } return bean; } }
- 需要一个配置文件来配置我们的service和dao
-
bean.properties配置文件内容
accountService=com.itheima.service.impl.AccountServiceImpl accountDao=com.itheima.dao.impl.AccountDaoImpl
-
更改之前通过new创建下层示例
//IAccountService as = new AccountServiceImpl();废弃 IAccountService as = (IAccountService) BeanFactory.getBean("accountService");
//private IAccountDao accountDao = new AccountDaoImpl();废弃 private IAccountDao accountDao = (IAccountDao) BeanFactory.getBean("accountDao");
-
-
工厂模式解耦的升级版
- 存在的问题:工厂创建的对象是多例的,因为每次都会调用newInstance(),由于我们一般都不会在方法中修改属性的值,没必要用多例,所以改成单例更快。
- 改变方法
public class BeanFactory { //定义一个Properties对象 private static Properties props; //定义一个Map,用于存放我们要创建的对象。我们把它称之为容器 private static Map<String,Object> beans; //使用静态代码块为Properties对象赋值 static { try { //实例化对象 props = new Properties(); //获取properties文件的流对象 InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties"); props.load(in); //实例化容器 beans = new HashMap<String,Object>(); //取出配置文件中所有的Key Enumeration keys = props.keys(); //遍历枚举 while (keys.hasMoreElements()){ //取出每个Key String key = keys.nextElement().toString(); //根据key获取value String beanPath = props.getProperty(key); //反射创建对象 Object value = Class.forName(beanPath).newInstance(); //把key和value存入容器中 beans.put(key,value); } }catch(Exception e){ throw new ExceptionInInitializerError("初始化properties失败!"); } } /** * 根据bean的名称获取对象 * @param beanName * @return */ public static Object getBean(String beanName){ return beans.get(beanName); } }
2.2.2 IOC控制反转
-
概念:把创建对象的权力交给框架,是框架的重要特征,并非面向对象编程的专用术语。它包括依赖注入DI和依赖查找DL,之前用的BeanFactory类就使用了这个思想,可以消减计算机程序的耦合。它只能解决程序间的依赖关系,其他什么都做不了
2.3 使用spring的IOC解决程序耦合
2.3.1 配置方法
-
使用spring代替自己写的BeanFactory类
-
pom中引入spring的依赖
-
配置文件bean.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"> <bean id="acountService" class="com.itheima.service.impl.AccountServiceImpl"> <!-- collaborators and configuration for this bean go here --> </bean> <bean id="acountDao" class="com.itheima.dao.impl.AccountDaoImpl"> <!-- collaborators and configuration for this bean go here --> </bean> </beans>
-
客户端实现
public class Client { public static void main(String[] args) { /** * 获取spring的Ioc核心容器,并根据id获取对象 * * ApplicationContext的三个常用实现类: * ClassPathXmlApplicationContext:它可以加载类路径下的配置文件,要求配置文件必须在类路径下。不在的话,加载不了。(更常用) * FileSystemXmlApplicationContext:它可以加载磁盘任意路径下的配置文件(必须有访问权限) * AnnotationConfigApplicationContext:它是用于读取注解创建容器的,是下次的内容 * * 核心容器的两个接口引发出的问题: * ApplicationContext: 单例对象使用 * 它在构建核心容器时,创建对象采取的策略是采用立即加载的方式。也就是说,只要一读取完配置文件马上就创建配置文件中配置的对象 * BeanFactory: 多例对象使用 * 它在构建核心容器时,创建对象采取的策略是采用延迟加载的方式。也就是说,什么时候根据id获取对象了,什么时候才真正的创建对象 */ ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); //2.根据id获取bean对象 IAccountService as = (IAccountService)ac.getBean("acountService"); IAccountDao adao = ac.getBean("accountDao", IAccountDao.class); System.out.println(as); System.out.println(adao); } }
2.3.2 bean的管理细节
-
创建bean的三种方式
-
使用默认构造函数创建:在spring的配置文件中使用bean标签,配以id和class属性之后,且没有其他属性和标签时,采用的就是默认构造函数创建bean对象,此时如果类中没有默认构造函数,则对象无法创造。
-
使用普通工厂中的方法创建对象(使用某个类中的方法创建对象,并存入spring容器)
-
使用工厂中的静态方法创建对象(使用某个类中的静态方法创建对象,并存入spring容器)
-
-
bean的作用范围调整:bean标签的scope属性:
-
作用:用于指定bean的作用范围
-
取值:常用的就是单例的和多礼的
-
singleton:单例的(默认值)
-
prototype:多例的
-
request:作用于web应用的请求范围
-
session:作用于web应用的会话范围
-
global-session:作用于集群环境的会话范围(全局会话范围),当不是集群环境时,它就是session
-
-
-
bean的生命周期
-
单例对象
- 出生:当容器创建时对象出生
- 活着:只要容器还在,对象一直活着
- 死亡:容器销毁,对象消亡(如果在main函数中需要手动销毁,使用.close方法,而且不能用多态,因为接口中没有)
- 总结:单例对象的生命周期和容器相同
-
多例对象
- 出生:当我们使用对象时spring框架为我们创建
- 活着:对象只要是在使用过程中就一直活着
- 死亡:当对象长时间不用,且没有别的对象引用时,由Java的垃圾回收器回收,spring不能主动销毁,容易出问题
-
xml中的标签定义
//init和destroy方法已经在实现类中定义好了 <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl" scope="prototype" init-method="init" destroy-method="destroy"></bean>
-
2.3.3 依赖注入
-
概念:在当前类需要用到其他类的对象,由spring为我们提供,以后依赖关系的管理都交给spring来维护,我们只需要在配置文件中说明。依赖关系的维护就称之为依赖注入
-
能注入的数据:
-
基本类型和String
-
其它bean类型(在配置文件中或者注解配置过的bean,就是使用ref的这个)
-
复杂类型/集合类型
-
用于给List结构集合注入的标签:list array set
-
用于给Map结构集合注入的标签:map props
-
结构相同,标签可以互换,如
<bean id="accountService3" class="com.itheima.service.impl.AccountServiceImpl3" <property name="myStrs"> <set> <value>AAA</value> <value>BBB</value> <value>CCC</value> </set> </property> </bean>
-
-
-
注入的方式:
-
使用构造函数提供(把数据传入构造方法的参数)
-
使用方法
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"> <constructor-arg name="name" value="test"></constructor-arg>//String类型 <constructor-arg name="age" value="18"></constructor-arg>//Interger类型 <constructor-arg name="birthday" ref="now"></constructor-arg>//Date类型 </bean> <bean id="now" class="java.util.Date"></bean>
-
优势:在获取bean对象时,注入数据是必须的操作,否则对象无法创建成功
-
弊端:改变了bean对象的实例化方式,是我们在创建对象时如果用不到这些数据也必须提供
-
-
使用set方法提供(get方法没必要) 更常用
-
使用方法
<bean id="accountService2" class="com.itheima.service.impl.AccountServiceImpl2"> <property name="name" value="test></property> <property name="age" value="21"></property> <property name="birthday" ref="now></property> </bean>
-
优势:创建对象时没有明确的限制,可以直接使用默认构造函数
-
弊端:如果有某个成员必须有值,则获取对象时有可能set方法没有执行
-
-
使用注解提供
-
2.4 基于注解的IOC配置
-
XML和注解对比
曾经的XML的配置:
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"> scope=" init-method="" destroy-method=""> <property name="" value|ref=""></property> </bean>
-
用于创建对象的:作用和XML配置文件中编写一个标签实现的功能是一样的
-
在要被创建的对象的类上写上Component注解
//作用:用于把当前类对象存入spring容器中 //属性:value:用于指定bean的id。不写的时候默认是当前类名,且首字母改小写 @Component("accountService") public class AccountServiceImpl implements IAccountService { private IAccountDao accountDao = new AccountDaoImpl(); public AccountServiceImpl() { System.out.println("对象创建了"); } public void saveAccount() { accountDao.saveAccount(); } }
-
现在还不能扫描找到,需要在官网找到xml的配置代码
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> //这行自己配 <context:component-scan base-package="com.itheima"></context:component-scan> </beans>
-
现在在客户端类中可以正常使用了,控制台输出:“对象创建了”
public class Client { public static void main(String[] args) { ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); //根据id获取bean对象 IAccountService as = (IAccountService)ac.getBean("accountService"); System.out.println(as); } }
-
其他用于创建对象的注解
-
Controller:一般用在表现层
-
Service:一般用在业务层
-
Repository:一般用在持久层
以上三个注解的作用和属性于Component一模一样,是spring框架为我们提供明确的三层使用的注解,使我们的三层对象更加清晰
-
-
-
用于注入数据的:作用就和xml配置文件中的bean标签中写一个标签的作用是一样的
-
Autowired:
-
作用:自动按照类型注入。只要容器中有唯一的一个bean对象类型和要注入的变量类型匹配,就可以注入成功
如果ioc容器中没有任何bean的类型和要注入的变量类型匹配,则报错。
-
出现位置:可以是变量上,也可以是方法上
-
细节:在使用注解注入时,set方法就不是必须的了
-
实现原理:
-
-
Qualifier:
- 作用:在按照类中注入的基础之上再按照名称注入(解决上图中的问题)。它在给类成员注入时不能单独使用,但是在给方法参数注入时可以
- 属性:value:用于指定注入bean的id
-
Resource
- 作用:直接按照bean的id注入。它可以独立使用
- 属性:name:用于指定bean的id
- 以上三个注入都只能注入其他bean类型的数据,而基本类型和String类型无法使用上述注解实现。另外,集合类型的注入只能通过xml来实现
- Value
- 作用:用于注入基本类型和String类型的数据
- 属性:value:用于指定数据的值。它可以使用spring中SpEL(也就是spring的el表达式),SpEL的写法:${表达式}。区别这个表达式是谁的表达式就看它定义的位置
-
-
用于改变作用范围的:作用就和在bean标签中使用scope属性实现的功能是一样的
- Scope
- 作用:用于指定bean的作用范围
- 属性:value:指定范围的取值。常用取值:singleton Prototype
- Scope
-
和生命周期相关(了解):作用就和在bean标签中使用init-method和destroy-method的作用是一样的
- PreDestroy:用于指定销毁方法
- PostConstruct:用于指定初始化方法
-
2.5 IOC案例
-
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"> //如何创建bean对象 <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"> //如何注入数据,注入的两种类型之其它bean类型 <property name="accountDao" ref="accountDao"></property> </bean> <bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl"> //注入数据的两种方式之set方法注入 <property name="runner" ref="runner"></property> </bean> <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"> 注入数据的两种方式之构造函数注入 <constructor-arg name="ds" ref="dataSource"></constructor-arg> </bean> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> //注入的两种类型之基本类型和String <property name="driverClass" value="com.mysql.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/eesy"></property> <property name="user" value="root"></property> <property name="password" value="root"></property> </bean> </beans>
-
在每个测试方法前面加上获取bean对象,其他操作同之前不变,如:
@Test public void testFindAll(){ //之后下面这两行可以在spring整合junit中解决 ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); //根据id获取bean对象 IAccountService as = ac.getBean("accountService", IAccountService.class); List<Account> accounts = as.findAllAccount(); for (Account account : accounts) { System.out.println(account); } }
-
用注解改造
@Service("accountService") public class AccountServiceImpl implements IAccountService { @Autowired private AccountDaoImpl accountDao;
@Repository("accountDao") public class AccountDaoImpl implements IAccountDao { @Autowired private QueryRunner runner;
并且在bean对象中添加扫描属性以及修改成context头
2.6 Spring的新注解
-
用配置类代替bean.xml文件(这节课有点偷懒了)
package config; /** * 该类是一个配置类,它的作用和bean.xml是一样的 * spring中的新注解 * Configuration * 作用:指定当前类是一个配置类 * 细节:当配置类作为AnnotationConfigApplicationContext对象创建的参数时,该注解可以不写。 * ComponentScan * 作用:用于通过注解指定spring在创建容器时要扫描的包 * 属性: * value:它和basePackages的作用是一样的,都是用于指定创建容器时要扫描的包。 * 我们使用此注解就等同于在xml中配置了: * <context:component-scan base-package="com.itheima"></context:component-scan> * Bean * 作用:用于把当前方法的返回值作为bean对象存入spring的ioc容器中 * 属性: * name:用于指定bean的id。当不写时,默认值是当前方法的名称 * 细节: * 当我们使用注解配置方法时,如果方法有参数,spring框架会去容器中查找有没有可用的bean对象。 * 查找的方式和Autowired注解的作用是一样的 * Import * 作用:用于导入其他的配置类 * 属性: * value:用于指定其他配置类的字节码。 * 当我们使用Import的注解之后,有Import注解的类就父配置类,而导入的都是子配置类 * PropertySource * 作用:用于指定properties文件的位置 * 属性: * value:指定文件的名称和路径。 * 关键字:classpath,表示类路径下 */ //@Configuration @ComponentScan("com.itheima") @Import(JdbcConfig.class) @PropertySource("classpath:jdbcConfig.properties") public class SpringConfiguration { //这是共同的配置类 }
-
子配置类(针对一些特定的配置)
public class JdbcConfig { //读取properties配置文件内容的一个好方法 @Value("${jdbc.driver}") private String driver; @Value("${jdbc.url}") private String url; @Value("${jdbc.username}") private String username; @Value("${jdbc.password}") private String password; /** * 用于创建一个QueryRunner对象 * @param dataSource * @return */ @Bean(name="runner") @Scope("prototype") //Qualifier可以用在参数位置上,选择对象具体的哪个实现类 public QueryRunner createQueryRunner(@Qualifier("ds2") DataSource dataSource){ return new QueryRunner(dataSource); } /** * 创建数据源对象 * @return */ @Bean(name="ds2") public DataSource createDataSource(){ try { ComboPooledDataSource ds = new ComboPooledDataSource(); //不用写死了 ds.setDriverClass(driver); ds.setJdbcUrl(url); ds.setUser(username); ds.setPassword(password); return ds; }catch (Exception e){ throw new RuntimeException(e); } } //一个对象有多个实现类的情况 @Bean(name="ds1") public DataSource createDataSource1(){ try { ComboPooledDataSource ds = new ComboPooledDataSource(); ds.setDriverClass(driver); ds.setJdbcUrl("jdbc:mysql://localhost:3306/eesy02"); ds.setUser(username); ds.setPassword(password); return ds; }catch (Exception e){ throw new RuntimeException(e); } } }
-
使用xml配置还是注解配置
- 公司用什么就用什么
- 如果类写好了存在jar包,推荐用xml,如果要自己写类,推荐用注解
- 一般来说部分xml部分更简洁
-
spring整合junit
-
整合的思路
- 应用程序的入口
main方法 - junit单元测试中,没有main方法也能执行
junit集成了一个main方法
该方法就会判断当前测试类中哪些方法有 @Test注解
junit就让有Test注解的方法执行 - junit不会管我们是否采用spring框架
在执行测试方法时,junit根本不知道我们是不是使用了spring框架
所以也就不会为我们读取配置文件/配置类创建spring核心容器 - 由以上三点可知
当测试方法执行时,没有Ioc容器,就算写了Autowired注解,也无法实现注入
- 应用程序的入口
-
整合的步骤
/** * 使用Junit单元测试:测试我们的配置 * Spring整合junit的配置 * 1、导入spring整合junit的jar(坐标) * 2、使用Junit提供的一个注解把原有的main方法替换了,替换成spring提供的 * @Runwith * 3、告知spring的运行器,spring和ioc创建是基于xml还是注解的,并且说明位置 * @ContextConfiguration * locations:指定xml文件的位置,加上classpath关键字,表示在类路径下 * classes:指定注解类所在地位置 * * 当我们使用spring 5.x版本的时候,要求junit的jar必须是4.12及以上 */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = SpringConfiguration.class) public class AccountServiceTest { @Autowired private IAccountService as = null; @Test public void testFindAll() { //3.执行方法 List<Account> accounts = as.findAllAccount(); for(Account account : accounts){ System.out.println(account); } } @Test public void testFindOne() { //3.执行方法 Account account = as.findAccountById(1); System.out.println(account); } @Test public void testSave() { Account account = new Account(); account.setName("test anno"); account.setMoney(12345f); //3.执行方法 as.saveAccount(account); } @Test public void testUpdate() { //3.执行方法 Account account = as.findAccountById(4); account.setMoney(23456f); as.updateAccount(account); } @Test public void testDelete() { //3.执行方法 as.deleteAccount(4); } }
-
2.7 AOP
2.7.1 AOP的引入
-
问题:转账操作需要添加事务,在业务层实现每个功能都要放在事务的生命周期中,而且加上事务类还有复杂的bean.xml配置,耦合度非常高
-
使用动态代理可以解决,回顾基于接口的动态代理
/** * 一个生产者 */ public class Producer { /** * 销售 * @param money */ public void saleProduct(float money){ System.out.println("销售产品,并拿到钱:"+money); } /** * 售后 * @param money */ public void afterService(float money){ System.out.println("提供售后服务,并拿到钱:"+money); } }
/** * 模拟一个消费者 */ public class Client { public static void main(String[] args) { final Producer producer = new Producer(); /** * 动态代理: * 特点:字节码随用随创建,随用随加载 * 作用:不修改源码的基础上对方法增强 * 分类: * 基于接口的动态代理 * 基于子类的动态代理 * 基于接口的动态代理: * 涉及的类:Proxy * 提供者:JDK官方 * 如何创建代理对象: * 使用Proxy类中的newProxyInstance方法 * 创建代理对象的要求: * 被代理类最少实现一个接口,如果没有则不能使用 * newProxyInstance方法的参数: * ClassLoader:类加载器 * 它是用于加载代理对象字节码的。和被代理对象使用相同的类加载器。固定写法。 * Class[]:字节码数组 * 它是用于让代理对象和被代理对象有相同方法。固定写法。 * InvocationHandler:用于提供增强的代码 * 它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。 * 此接口的实现类都是谁用谁写。 */ IProducer proxyProducer = (IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(), producer.getClass().getInterfaces(), new InvocationHandler() { /** * 作用:执行被代理对象的任何接口方法都会经过该方法 * 方法参数的含义 * @param proxy 代理对象的引用 * @param method 当前执行的方法 * @param args 当前执行方法所需的参数 * @return 和被代理对象方法有相同的返回值 * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //提供增强的代码 Object returnValue = null; //1.获取方法执行的参数 Float money = (Float)args[0]; //2.判断当前方法是不是销售 if("saleProduct".equals(method.getName())) { returnValue = method.invoke(producer, money*0.8f); } return returnValue; } }); proxyProducer.saleProduct(10000f); } }
-
基于子类的动态代理
/** * 模拟一个消费者 */ public class Client { public static void main(String[] args) { final Producer producer = new Producer(); /** * 动态代理: * 特点:字节码随用随创建,随用随加载 * 作用:不修改源码的基础上对方法增强 * 分类: * 基于接口的动态代理 * 基于子类的动态代理 * 基于子类的动态代理: * 涉及的类:Enhancer * 提供者:第三方cglib库 * 如何创建代理对象: * 使用Enhancer类中的create方法 * 创建代理对象的要求: * 被代理类不能是最终类 * create方法的参数: * Class:字节码 * 它是用于指定被代理对象的字节码。 * * Callback:用于提供增强的代码 * 它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。 * 此接口的实现类都是谁用谁写。 * 我们一般写的都是该接口的子接口实现类:MethodInterceptor */ Producer cglibProducer = (Producer)Enhancer.create(producer.getClass(), new MethodInterceptor() { /** * 执行该对象的任何方法都会经过该方法 * @param proxy * @param method * @param args * 以上三个参数和基于接口的动态代理中invoke方法的参数是一样的 * @param methodProxy :当前执行方法的代理对象 * @return * @throws Throwable */ @Override public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { //提供增强的代码 Object returnValue = null; //1.获取方法执行的参数 Float money = (Float)args[0]; //2.判断当前方法是不是销售 if("saleProduct".equals(method.getName())) { returnValue = method.invoke(producer, money*0.8f); } return returnValue; } }); cglibProducer.saleProduct(12000f); } }
-
使用基于接口的动态代理给原Service方法增加事务
public class BeanFactory { private IAccountService accountService; private TransactionManager txManager; public void setTxManager(TransactionManager txManager) { this.txManager = txManager; } public final void setAccountService(IAccountService accountService) { this.accountService = accountService; } /** * 获取Service代理对象 * @return */ public IAccountService getAccountService() { return (IAccountService)Proxy.newProxyInstance(accountService.getClass().getClassLoader(), accountService.getClass().getInterfaces(), new InvocationHandler() { /** * 添加事务的支持 * * @param proxy * @param method * @param args * @return * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if("test".equals(method.getName())){ return method.invoke(accountService,args); } Object rtValue = null; try { //1.开启事务 txManager.beginTransaction(); //2.执行操作 rtValue = method.invoke(accountService, args); //3.提交事务 txManager.commit(); //4.返回结果 return rtValue; } catch (Exception e) { //5.回滚操作 txManager.rollback(); throw new RuntimeException(e); } finally { //6.释放连接 txManager.release(); } } }); } }
-
bean.xml中配置注入
<!--配置代理的service--> <bean id="proxyAccountService" factory-bean="beanFactory" factory-method="getAccountService"></bean> <!--配置beanfactory--> <bean id="beanFactory" class="com.itheima.factory.BeanFactory"> <!-- 注入service --> <property name="accountService" ref="accountService"></property> <!-- 注入事务管理器 --> <property name="txManager" ref="txManager"></property> </bean> <!-- 配置Service --> <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"> <!-- 注入dao --> <property name="accountDao" ref="accountDao"></property> </bean>
2.7.2 AOP的相关概念
-
概念:它就是把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的技术上,对我们的已有方法进行增强。可以减少重复代码,提高开发效率,而且维护方便
-
关于代理的选择:spring中会根据目标类是否实现了接口来觉得采用哪种动态代理的方式
-
相关术语(概念性的,有利于后期自学)
-
Joinpoint(连接点):是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点。例如我们在业务层的方法都是连接点
-
Pointcut(切入点):是指我们要对哪些Joinpoint进行拦截的定义。切入点就是被增强的方法,是连接点的子集。例如对账户的增删改查
-
Advice(通知/增强):通知是指拦截到Joinpoint之后所要做的事情
-
Introduction(引介):在不修改类代码的前提下,可以在运行期为类动态地添加一些方法或Field
-
Target(目标对象):被代理对象
-
Weaving(织入):把增强应用到目标对象来创建新的代理对象的过程。spring采用动态代理织入
-
Proxy(代理):一个类被AOP织入增强后,就产生了一个结果代理类
-
Aspect(切面):是切入点和通知(引介)的结合。仔细理解
-
2.8 Spring中的AOP
2.8.1 基于xml的配置
-
学习Spring中的AOP要明确的事
-
开发阶段(我们要做的)
编写核心业务代码(开发主线):大部分程序员来做,要求熟悉业务需求。
把公用代码抽取出来,制作成通知。(开发阶段最后再做):AOP 编程人员来做。
在配置文件中,声明切入点与通知间的关系,即切面。:AOP 编程人员来做。
-
运行阶段(Spring框架完成的)
Spring 框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。
-
-
实战演示
-
编写Service方法,和一个用于记录日志的前置通知方法,对Service中的saveAccount方法进行增强
-
基于XML配置
<!--spring中基于XML的AOP配置步骤 1、把通知Bean也交给spring来管理 2、使用aop:config标签表明开始AOP的配置 3、使用aop:aspect标签表明配置切面 id属性:是给切面提供一个唯一标识 ref属性:是指定通知类bean的Id。 4、在aop:aspect标签的内部使用对应标签来配置通知的类型 我们现在示例是让printLog方法在切入点方法执行之前之前:所以是前置通知 aop:before:表示配置前置通知 method属性:用于指定Logger类中哪个方法是前置通知 pointcut属性:用于指定切入点表达式,该表达式的含义指的是对业务层中哪些方法增强 切入点表达式的写法:(pom文件中引入的aspecjweaver负责解析) 关键字:execution(表达式) 表达式: 访问修饰符 返回值 包名.包名.包名...类名.方法名(参数列表) 标准的表达式写法: public void com.itheima.service.impl.AccountServiceImpl.saveAccount() 访问修饰符可以省略 void com.itheima.service.impl.AccountServiceImpl.saveAccount() 返回值可以使用通配符,表示任意返回值 * com.itheima.service.impl.AccountServiceImpl.saveAccount() 包名可以使用通配符,表示任意包。但是有几级包,就需要写几个*. * *.*.*.*.AccountServiceImpl.saveAccount()) 包名可以使用..表示当前包及其子包 * *..AccountServiceImpl.saveAccount() 类名和方法名都可以使用*来实现通配 * *..*.*() 参数列表: 可以直接写数据类型: 基本类型直接写名称 int 引用类型写包名.类名的方式 java.lang.String 可以使用通配符表示任意类型,但是必须有参数 可以使用..表示有无参数均可,有参数可以是任意类型 全通配写法: * *..*.*(..) 实际开发中切入点表达式的通常写法: 切到业务层实现类下的所有方法 * com.itheima.service.impl.*.*(..) -->
-
四种常用通知类型+切入点表达式+环绕通知的xml配置
<aop:config> <!-- 配置切入点表达式 id属性用于指定表达式的唯一标识。expression属性用于指定表达式内容 此标签写在aop:aspect标签内部只能当前切面使用。 它还可以写在aop:aspect外面,此时就变成了所有切面可用 --> <aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut> <!--配置切面 --> <aop:aspect id="logAdvice" ref="logger"> <!-- 配置前置通知:在切入点方法执行之前执行 <aop:before method="beforePrintLog" pointcut-ref="pt1" ></aop:before>--> <!-- 配置后置通知:在切入点方法正常执行之后值。它和异常通知永远只能执行一个 <aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt1"></aop:after-returning>--> <!-- 配置异常通知:在切入点方法执行产生异常之后执行。它和后置通知永远只能执行一个 <aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing>--> <!-- 配置最终通知:无论切入点方法是否正常执行它都会在其后面执行 <aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after>--> <!-- 配置环绕通知 详细的注释请看Logger类中--> <aop:around method="aroundPringLog" pointcut-ref="pt1"></aop:around> </aop:aspect> </aop:config>
public class Logger { /** * 前置通知 */ public void beforePrintLog(){ System.out.println("前置通知Logger类中的beforePrintLog方法开始记录日志了。。。"); } /** * 后置通知 */ public void afterReturningPrintLog(){ System.out.println("后置通知Logger类中的afterReturningPrintLog方法开始记录日志了。。。"); } /** * 异常通知 */ public void afterThrowingPrintLog(){ System.out.println("异常通知Logger类中的afterThrowingPrintLog方法开始记录日志了。。。"); } /** * 最终通知 */ public void afterPrintLog(){ System.out.println("最终通知Logger类中的afterPrintLog方法开始记录日志了。。。"); } /** * 环绕通知 * 问题: * 当我们配置了环绕通知之后,切入点方法没有执行,而通知方法执行了。 * 分析: * 通过对比动态代理中的环绕通知代码,发现动态代理的环绕通知有明确的切入点方法调用,而我们的代码中没有。 * 解决: * Spring框架为我们提供了一个接口:ProceedingJoinPoint。该接口有一个方法proceed(),此方法就相当于明确调用切入点方法。 * 该接口可以作为环绕通知的方法参数,在程序执行时,spring框架会为我们提供该接口的实现类供我们使用。 * * spring中的环绕通知: * 它是spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式。 */ public Object aroundPringLog(ProceedingJoinPoint pjp){ Object rtValue = null; try{ Object[] args = pjp.getArgs();//得到方法执行所需的参数 System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。前置"); rtValue = pjp.proceed(args);//明确调用业务层方法(切入点方法) System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。后置"); return rtValue; }catch (Throwable t){ System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。异常"); throw new RuntimeException(t); }finally { System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。最终"); } } }
-
2.8.2 基于注解的配置
-
先修改bean.xml文件内容
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 配置spring创建容器时要扫描的包--> <context:component-scan base-package="com.itheima"></context:component-scan> <!-- 配置spring开启注解AOP的支持 --> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> </beans>
-
修改通知方法Logger
/** * 用于记录日志的工具类,它里面提供了公共的代码 */ @Component("logger") @Aspect//表示当前类是一个切面类 public class Logger { @Pointcut("execution(* com.itheima.service.impl.*.*(..))") private void pt1(){} /** * 前置通知 */ // @Before("pt1()") public void beforePrintLog(){ System.out.println("前置通知Logger类中的beforePrintLog方法开始记录日志了。。。"); } /** * 后置通知 */ // @AfterReturning("pt1()") public void afterReturningPrintLog(){ System.out.println("后置通知Logger类中的afterReturningPrintLog方法开始记录日志了。。。"); } /** * 异常通知 */ // @AfterThrowing("pt1()") public void afterThrowingPrintLog(){ System.out.println("异常通知Logger类中的afterThrowingPrintLog方法开始记录日志了。。。"); } /** * 最终通知 */ // @After("pt1()") public void afterPrintLog(){ System.out.println("最终通知Logger类中的afterPrintLog方法开始记录日志了。。。"); } /** * 环绕通知 * 问题: * 当我们配置了环绕通知之后,切入点方法没有执行,而通知方法执行了。 * 分析: * 通过对比动态代理中的环绕通知代码,发现动态代理的环绕通知有明确的切入点方法调用,而我们的代码中没有。 * 解决: * Spring框架为我们提供了一个接口:ProceedingJoinPoint。该接口有一个方法proceed(),此方法就相当于明确调用切入点方法。 * 该接口可以作为环绕通知的方法参数,在程序执行时,spring框架会为我们提供该接口的实现类供我们使用。 * * spring中的环绕通知: * 它是spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式。 */ @Around("pt1()") public Object aroundPringLog(ProceedingJoinPoint pjp){ Object rtValue = null; try{ Object[] args = pjp.getArgs();//得到方法执行所需的参数 System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。前置"); rtValue = pjp.proceed(args);//明确调用业务层方法(切入点方法) System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。后置"); return rtValue; }catch (Throwable t){ System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。异常"); throw new RuntimeException(t); }finally { System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。最终"); } } }
-
如果想彻底删除bean.xml文件
@Configuration
@ComponentScan(basePackages="com.itheima")
@EnableAspectJAutoProxy //添加这个
public class SpringConfiguration {
}
2.8.3 对引入中BeanFactory的改造
-
Factory是添加事务的工厂类,可以用aop代替
public class BeanFactory { private IAccountService accountService; private TransactionManager txManager; public void setTxManager(TransactionManager txManager) { this.txManager = txManager; } public final void setAccountService(IAccountService accountService) { this.accountService = accountService; } /** * 获取Service代理对象 * @return */ public IAccountService getAccountService() { return (IAccountService)Proxy.newProxyInstance(accountService.getClass().getClassLoader(), accountService.getClass().getInterfaces(), new InvocationHandler() { /** * 添加事务的支持 * * @param proxy * @param method * @param args * @return * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if("test".equals(method.getName())){ return method.invoke(accountService,args); } Object rtValue = null; try { //1.开启事务 txManager.beginTransaction(); //2.执行操作 rtValue = method.invoke(accountService, args); //3.提交事务 txManager.commit(); //4.返回结果 return rtValue; } catch (Exception e) { //5.回滚操作 txManager.rollback(); throw new RuntimeException(e); } finally { //6.释放连接 txManager.release(); } } }); } }
-
bean.xml中导入aop的约束并添加配置
<aop:config> <aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut> <aop:aspect id="logAdvice" ref="txManager"> <aop:before method="beginTransaction" pointcut-ref="pt1"></aop:before> <aop:after-returning method="commit" pointcut-ref="pt1"></aop:after-returning> <aop:after-throwing method="rollback" pointcut-ref="pt1"></aop:after-throwing> <aop:after method="release" pointcut-ref="pt1"></aop:after> </aop:aspect> </aop:config>
-
改造成注解配置
- 加上@Component(“connectUtils”)、@Autowired、@Aspect注解,删除set方法,并且把已经配好的从bean.xml中删除
- 注解必须使用环绕通知才可以保证顺序正确,使用@Around
2.9 JdbcTemplate
-
简介
-
基本使用
-
导包 jdbc和tx(事务相关)
-
先用老方法使用
public static void main(String[] args) { DriverManagerDataSource ds = new DriverManagerDataSource(); ds.setDriverClassName("com.mysql.jdbc.Driver"); ds.setUrl("jdbc:mysql://localhost:3306/eesy"); ds.setUsername("root"); ds.setPassword("root"); JdbcTemplate jt = new JdbcTemplate(); jt.setDataSource(ds); jt.execute("insert into account(name,money)values('ccc',100)"); }
-
使用IOC解耦以及属性注入
-
bean.xml文件
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"></property> </bean> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"></property> <property name="url" value="jdbc:mysql://localhost:3306/eesy"></property> <property name="username" value="root"></property> <property name="password" value="root"></property> </bean>
-
main文件
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); JdbcTemplate jt = ac.getBean("jdbcTemplate",JdbcTemplate.class); jt.execute("insert into account(name,money)values('ddd',222)");
-
-
CRUD
-
代码部分
public static void main(String[] args) { ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); JdbcTemplate jt = ac.getBean("jdbcTemplate",JdbcTemplate.class); // jt.execute("insert into account(name,money)values('ddd',222)"); //增加一行数据 jt.update("insert into account(name,money)values(?,?)","eee",111f); //删除一行数据 jt.update("delete from account where id = ?",3); //修改一条数据 jt.update("update account set name=?,money=? where id=?","qwe",1234,4); //查询所有数据 //使用BeanPropertyRowMapper就可以不用自己实现RowMapper接口了 List<Account> accounts = jt.query("select * from account where money > ?", new BeanPropertyRowMapper<Account>(Account.class), 500f); for (Account account : accounts) { System.out.println(account); } //查询一个数据 List<Account> accounts = jt.query("select * from account where id = ?", new BeanPropertyRowMapper<Account>(Account.class), 1); System.out.println(accounts.isEmpty()?"没有内容":accounts.get(0)); //查询一行一列 Integer count = jt.queryForObject("select count(*) from account where money > ?", Integer.class, 500f); System.out.println(count); }
-
分析方法
-
-
在Dao中的使用
创建接口和实现类,并且注入jdbcTemplate
//Dao层中下面两行可以抽取出来,继承spring中的JdbcDaoSupport(老师又默默地带我们手撕源码)。但是就不能在下面这行加@Autowired注解了 private JdbcTemplate jt; public void setJt(JdbcTemplate jt) { this.jt = jt; }
-
3.10 事务
-
目的:使用spring代替之前自己定义的事务方法
@Component("txManager") @Aspect public class TransactionManager { @Autowired private ConnectionUtils connectionUtils; @Pointcut("execution(* com.itheima.service.impl.*.*(..))") private void pt1(){} /** * 开启事务 */ public void beginTransaction(){ try { connectionUtils.getThreadConnection().setAutoCommit(false); }catch (Exception e){ e.printStackTrace(); } } /** * 提交事务 */ public void commit(){ try { connectionUtils.getThreadConnection().commit(); }catch (Exception e){ e.printStackTrace(); } } /** * 回滚事务 */ public void rollback(){ try { connectionUtils.getThreadConnection().rollback(); }catch (Exception e){ e.printStackTrace(); } } /** * 释放连接 */ public void release(){ try { connectionUtils.getThreadConnection().close();//还回连接池中 connectionUtils.removeConnection(); }catch (Exception e){ e.printStackTrace(); } } @Around("pt1()") public Object aroundAdvice(ProceedingJoinPoint pjp){ Object rtValue = null; try{ Object[] args = pjp.getArgs(); this.beginTransaction(); rtValue = pjp.proceed(args); this.commit(); return rtValue; }catch (Throwable e){ this.rollback(); throw new RuntimeException(e); }finally { this.release(); } } }
-
基于xml的声明式事务控制
-
在官方文档中找到并引入tx约束
<?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/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> </bean>
-
<!-- spring中基于XML的声明式事务控制配置步骤 1、配置事务管理器 2、配置事务的通知 此时我们需要导入事务的约束 tx名称空间和约束,同时也需要aop的 使用tx:advice标签配置事务通知 属性: id:给事务通知起一个唯一标识 transaction-manager:给事务通知提供一个事务管理器引用 3、配置AOP中的通用切入点表达式 4、建立事务通知和切入点表达式的对应关系 5、配置事务的属性 是在事务的通知tx:advice标签的内部 --> <!-- 配置事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 配置事务的通知--> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <!-- 配置事务的属性 isolation:用于指定事务的隔离级别。默认值是DEFAULT,表示使用数据库的默认隔离级别。 propagation:用于指定事务的传播行为。默认值是REQUIRED,表示一定会有事务,增删改的选择。查询方法可以选择SUPPORTS。 read-only:用于指定事务是否只读。只有查询方法才能设置为true。默认值是false,表示读写。 timeout:用于指定事务的超时时间,默认值是-1,表示永不超时。如果指定了数值,以秒为单位。 rollback-for:用于指定一个异常,当产生该异常时,事务回滚,产生其他异常时,事务不回滚。没有默认值。表示任何异常都回滚。 no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时事务回滚。没有默认值。表示任何异常都回滚。 --> <tx:attributes> <tx:method name="*" propagation="REQUIRED" read-only="false"/> <tx:method name="find*" propagation="SUPPORTS" read-only="true"></tx:method> </tx:attributes> </tx:advice> <!-- 配置aop--> <aop:config> <!-- 配置切入点表达式--> <aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut> <!--建立切入点表达式和事务通知的对应关系 --> <aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor> </aop:config>
-
-
基于注解的声明式事务控制
-
加入xmlns:context的约束
-
配置spring创建容器时要扫描的包
-
添加其他service和dao注解同上
-
如下
<!-- spring中基于注解 的声明式事务控制配置步骤 1、配置事务管理器 2、开启spring对注解事务的支持 3、在需要事务支持的地方使用@Transactional注解,里面可以配置属性(如果很多地方的话那么便捷性不如xml) --> <!-- 配置事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 开启spring对注解事务的支持--> <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
-
-
基于纯注解的声明式事务控制
- 一步步用注解和配置类把bean.xml代替
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 配置spring创建容器时要扫描的包--> <context:component-scan base-package="com.itheima"></context:component-scan> <!-- 配置JdbcTemplate--> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 配置数据源--> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"></property> <property name="url" value="jdbc:mysql://localhost:3306/eesy"></property> <property name="username" value="root"></property> <property name="password" value="1234"></property> </bean> <!-- spring中基于注解 的声明式事务控制配置步骤 1、配置事务管理器 2、开启spring对注解事务的支持 3、在需要事务支持的地方使用@Transactional注解 --> <!-- 配置事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 开启spring对注解事务的支持--> <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven> </beans>
- 创建一个配置类,其中的两个配置属性类以及一个连接数据库的资源文件
- 配置文件类
/** * spring的配置类,相当于bean.xml */ @Configuration @ComponentScan("com.itheima") @Import({JdbcConfig.class,TransactionConfig.class}) @PropertySource("jdbcConfig.properties") @EnableTransactionManagement public class SpringConfiguration { }
- Jdbc和数据源
public class JdbcConfig { @Value("${jdbc.driver}") private String driver; @Value("${jdbc.url}") private String url; @Value("${jdbc.username}") private String username; @Value("${jdbc.password}") private String password; /** * 创建JdbcTemplate * @param dataSource * @return */ @Bean(name="jdbcTemplate") public JdbcTemplate createJdbcTemplate(DataSource dataSource){ return new JdbcTemplate(dataSource); } /** * 创建数据源对象 * @return */ @Bean(name="dataSource") public DataSource createDataSource(){ DriverManagerDataSource ds = new DriverManagerDataSource(); ds.setDriverClassName(driver); ds.setUrl(url); ds.setUsername(username); ds.setPassword(password); return ds; } }
- 事务管理器
/** * 和事务相关的配置类 */ public class TransactionConfig { /** * 用于创建事务管理器对象 * @param dataSource * @return */ @Bean(name="transactionManager") public PlatformTransactionManager createTransactionManager(DataSource dataSource){ return new DataSourceTransactionManager(dataSource); } }
- 数据库配置文件
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/eesy jdbc.username=root jdbc.password=root
- 测试类中更改注解属性为classes
@ContextConfiguration(classes= SpringConfiguration.class)
3. SpringMVC
3.1 三层架构以及SpringMVC框架的介绍
-
三层架构
-
优势
-
清晰的角色划分: 前端控制器(DispatcherServlet) 请求到处理器映射(HandlerMapping) 处理器适配器(HandlerAdapter) 视图解析器(ViewResolver) 处理器或页面控制器(Controller) 验证器( Validator) 命令对象(Command 请求参数绑定到的对象就叫命令对象) 表单对象(Form Object 提供给表单展示和提交到的对象就叫表单对象)。
-
分工明确,而且扩展点相当灵活,可以很容易扩展,虽然几乎不需要。
-
由于命令对象就是一个 POJO,无需继承框架特定 API,可以使用命令对象直接作为业务对象。
-
和 Spring 其他框架无缝集成,是其它 Web 框架所不具备的。
-
可适配,通过 HandlerAdapter 可以支持任意的类作为处理器。
-
可定制性,HandlerMapping、ViewResolver 等能够非常简单的定制。
-
功能强大的数据验证、格式化、绑定机制。
-
利用 Spring 提供的 Mock 对象能够非常简单的进行 Web 层单元测试。
-
本地化、主题的解析的支持,使我们更容易进行国际化和主题的切换。
-
强大的 JSP 标签库,使 JSP 编写更容易。
………………还有比如RESTful风格的支持、简单的文件上传、约定大于配置的契约式编程支持、基于注解的零配置支持等等。
-
-
和Struts2的优略分析
-
共同点:它们都是表现层框架,都是基于 MVC 模型编写的。
它们的底层都离不开原始 ServletAPI。
它们处理请求的机制都是一个核心控制器。
-
区别:Spring MVC 的入口是 Servlet, 而 Struts2 是 Filter
Spring MVC 是基于方法设计的,而 Struts2 是基于类,Struts2 每次执行都会创建一个动作类。所 以 Spring MVC 会稍微比 Struts2 快些。
Spring MVC 使用更加简洁,同时还支持 JSR303, 处理 ajax 的请求更方便 (JSR303 是一套 JavaBean 参数校验的标准,它定义了很多常用的校验注解,我们可以直接将这些注 解加在我们 JavaBean 的属性上面,就可以在需要校验的时候进行校验了。)
Struts2 的 OGNL 表达式使页面的开发效率相比 Spring MVC 更高些,但执行效率并没有比 JSTL 提 升,尤其是 struts2 的表单标签,远没有 html 执行效率高。
-
3.2 入门程序
3.2.1 编码步骤
-
通过SpringMVC完成一个页面的跳转。分析
-
在index.jsp中写个跳转的超链接
-
配置web.xml
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" > <web-app> <display-name>Archetype Created Web Application</display-name> <servlet> <servlet-name>dispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- 配置Servlet的初始化参数,读取springmvc的配置文件,创建spring容器 --> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc.xml</param-value> </init-param> <!-- 配置servlet启动时加载对象 --> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcherServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
-
配置springmvc.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" 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 http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 配置spring创建容器时要扫描的包 --> <context:component-scan base-package="com.itheima"></context:component-scan> <!-- 配置视图解析器 --> <bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/pages/"></property> <property name="suffix" value=".jsp"></property> </bean> <!-- 配置spring开启注解mvc的支持,默认包含了很多组件--> <mvc:annotation-driven/> </beans>
-
编写controller代码
@Controller //这个不是MVC的注解,是扫描的注解 public class HelloController { @RequestMapping(path = "/hello") public String sayHello(){ System.out.println("Hello StringMVC"); return "success"; } }
-
在跳转到的success.jsp中写出访问成功
-
项目结构
3.2.2 流程总结
-
大概的流程
-
当启动Tomcat服务器的时候,因为配置了load-on-startup标签,所以会创建DispatcherServlet对象, 就会加载springmvc.xml配置文件
-
开启了注解扫描,那么HelloController对象就会被创建
-
从index.jsp发送请求,请求会先到达DispatcherServlet核心控制器,根据配置@RequestMapping注解 找到执行的具体方法
-
根据执行方法的返回值,再根据配置的视图解析器,去指定的目录下查找指定名称的JSP文件
-
Tomcat服务器渲染页面,做出响应
-
-
详细的流程以及组件分析
-
前端控制器(DispatcherServlet)
-
处理器映射器(HandlerMapping)
-
处理器(Handler)
-
处理器适配器(HandlAdapter)
-
视图解析器(View Resolver)
-
视图(View)
-
3.2.3 RequestMapping注解
-
RequestMapping注解的作用是建立请求URL和处理方法之间的对应关系
-
RequestMapping注解可以作用在方法和类上
- 作用在类上:第一级的访问目录
- 作用在方法上:第二级的访问目录
- 细节:路径可以不编写 / 表示应用的根目录开始
- 细节:${ pageContext.request.contextPath }也可以省略不写,但是路径上不能写
-
RequestMapping的属性
- path 指定请求路径的url
- value value属性和path属性是一样的
- method 指定该方法的请求方式
- params 指定限制请求参数的条件
- headers 发送的请求中必须包含的请求头
3.3 参数绑定
-
入门程序
-
新建param.jsp
<body> <h3>参数绑定入门程序</h3> <a href="param/testParam?username=hehe&password=123">测试参数</a> </body>
-
Controller可以通过参数接收
@Controller @RequestMapping("/param") public class ParamController { @RequestMapping("/testParam") public String testParam(String username,String password){ System.out.println("用户名是:"+ username); System.out.println("密码是:"+ password); return "success"; } }
-
-
绑定实体类型
-
页面改成表单提交
<form action="param/saveAccount" method="post"> 姓名:<input type="text" name="username" /><br> 密码:<input type="text" name="password" /><br> 金额:<input type="text" name="money" /><br> 用户姓名:<input type="text" name="user.uname" /><br> 用户年龄:<input type="text" name="user.age" /><br> <input type="submit" value="提交" /><br> </form>
-
新建Account类和User类,User类是Accont类中的属性
-
controller
@RequestMapping("/saveAccount") public String saveAccount(Account account){ System.out.println(account); return "success"; }
-
-
解决中文乱码问题
-
以前通过配置request.setCharacterEnCoding方法解决
-
现在通过在web.xml中配置过滤器
<filter> <filter-name>characterEncodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>characterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
-
-
绑定集合类型
-
Account实体类中添加List和Map集合的属性
-
表单的值修改成如下格式
用户姓名:<input type="text" name="list[0].uname" /><br> 用户年龄:<input type="text" name="list[0].age" /><br> 用户姓名:<input type="text" name="map['one'].uname" /><br> 用户年龄:<input type="text" name="user['one'].age" /><br>
-
-
自定义类型转换器
-
问题:2020-11-11识别不了,MVC只能自动转换2020/11/11类型的
-
创建一个类实现Converter接口
public class StringToDateConverter implements Converter<String,Date>{ /** * String source 传入进来字符串 * @param source * @return */ public Date convert(String source) { // 判断 if(source == null){ throw new RuntimeException("请您传入数据"); } DateFormat df = new SimpleDateFormat("yyyy-MM-dd"); try { // 把字符串转换日期 return df.parse(source); } catch (Exception e) { throw new RuntimeException("数据类型转换出现错误"); } } }
-
配置springmvc.xml
<!--配置自定义类型转换器--> <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean"> <property name="converters"> <set> <bean class="cn.itcast.utils.StringToDateConverter"/> </set> </property> </bean> <!-- 开启SpringMVC框架注解的支持 --> <mvc:annotation-driven conversion-service="conversionService"/>
-
-
获取Servlet原生的API
直接在Controller的方法里面传参数就行了
@RequestMapping("/testServlet") public String testServlet(HttpServletRequest request, HttpServletResponse response){ System.out.println("执行了..."); System.out.println(request); HttpSession session = request.getSession(); System.out.println(session); ServletContext servletContext = session.getServletContext(); System.out.println(servletContext); System.out.println(response); return "success"; }
3.4 常用注解
-
RequestParam
-
问题:之前的请求中参数是什么名称,控制器中的形式参数也必须是这个名称,如果不是,就需要使用这个参数
-
属性
- value:请求参数中的名称
- required:请求参数中是否必须提供此参数。默认值是true,表示必须提供,不然将报错
-
用在参数的前面
public String testRequestParam(@RequestParam(name="name") String username)
-
-
RequestBody
- 作用:用于获取请求体内容,直接使用得到的是key=value&key=value…结构的数据(get请求不适用)
- 属性:
- request:是否必须有请求体,默认值是true,get请求会报错,如果是false,get请求得到null
-
PathVariable
-
作用:用于绑定url中的占位符。例如:请求url中/delete/{id},这个{id}就是url占位符。url支持占位符是spring3.0之后加入的。是springmvc支持rest风格URL的一个重要标志
-
属性:
- value:用于指定url中占位符名称
- required:是否必须提供占位符
-
restful编程风格
-
在jsp和controller中的使用
<a href="anno/testPathVariable/10">RequestParam</a>
@RequestMapping(path = "/testPathVariable/{sid}") public String testPathVariable(@PathVariable("sid") String id){ System.out.println(id); //输出10 return "success"; }
-
-
HiddentHttpMethodFilter
- 浏览器form表单只支持GET和POST请求,这个过滤器可以将浏览器请求改为指定的请求方式,但是一般可以使用WenCilent类完成本功能,这个不常用
-
RequestHeader
- 获取请求消息头,但是一般不怎么用
-
CookieValue
- 获取浏览器的Cookie,不常用
-
ModelAttibute
-
作用:它可以用于修饰方法和参数,出现在方法上,表示当前的方法会在控制器的方法执行之前先执行。它可以修饰没有返回值的方法,也可以修饰有具体返回值的方法
-
属性:value:用于获取数据的key,key可以是pojo的属性名称,也可以是map结构的key
-
应用场景:当表单提交数据不是完整的实体类数据时,保证没有提交数据的字段使用数据库对象原来的数据
-
例子:用户有uname,age,date三个字段,但是表单只提交uname和age,如果想让date保持数据库原来的值可以使用这个注解
-
有返回值
@ModelAttribute public void showUser(String name){ System.out.println("showUser执行了"); User user = new User(); user.setUname(name); user.setAge(20); user.setDate(new Date()); map.put("abc",user); } @ModelAttribute public User showUser(String name){ System.out.println("showUser执行了"); User user = new User(); user.setUname(name); user.setAge(20); user.setDate(new Date()); return user; }
-
有返回值
@RequestMapping(path = "/testModelAttribute") public String testModelAttribute(@ModelAttribute("abc") User user){ System.out.println(user); return "success"; } @ModelAttribute public void showUser(String name, Map<String,User> map){ System.out.println("showUser执行了"); User user = new User(); user.setUname(name); user.setAge(20); user.setDate(new Date()); map.put("abc",user); }
-
-
-
SessionAttribute
-
作用:用于多次执行控制器方法间的参数共享
-
属性:
- value:用于指定存入的属性名称
- type:用于指定存入的数据类型
-
例子
-
创建三个超链接分别指向三个controller
-
controller
@Controller @RequestMapping("/anno") @SessionAttributes(value = {"msg"}) //只能作用到类上 public class AnnoController { @RequestMapping(path = "/testSessionAttribute") public String testSessionAttribute(Model model){ model.addAttribute("msg","美美"); return "success"; } @RequestMapping(path = "/getSessionAttribute") public String getSessionAttribute(ModelMap modelMap){ modelMap.get("msg"); return "success"; } @RequestMapping(path = "/delSessionAttribute") public String delSessionAttribute(SessionStatus status){ status.setComplete(); return "success"; } }
-
success.jsp
${msg} ${sessionScope}
-
-
3.5 响应数据类型
-
返回值是String类型
-
将查询出的对象存入request域中
@RequestMapping("/testString") public String testString(Model model){ System.out.println("testString方法执行了。。。"); //模拟从数据库中查询出User对象 User user = new User(); user.setUsername("张三"); user.setPassword("123"); user.setAge(20); model.addAttribute("user",user); return "success"; }
-
再在前端通过el表达式显示
${user.username} ${user.password}
-
-
返回值是void类型
-
默认会发送到WEB-INF目录下对应访问地址的jsp文件
-
可以按照传统方法指定显示路径
@RequestMapping("/testVoid") public void testVoid(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("testVoid方法执行了。。。"); //转发 // request.getRequestDispatcher("/WEB-INF/pages/success.jsp").forward(request,response); //重定向 // response.sendRedirect(request.getContextPath()+"/index.jsp"); //解决中文问题 response.setCharacterEncoding("UTF-8"); response.setContentType("text/html;charset=UTF-8"); //直接进行会话响应 response.getWriter().write("你好"); return; }
-
-
返回值是ModelAndView类型
-
与返回值是String类型差不多,那个其实就是以这个作为底层原理
@RequestMapping("/testModelAndView") public ModelAndView testModelAndView(){ //这个接口作为形式参数是怎么发挥作用的 System.out.println("testString方法执行了。。。"); ModelAndView mv = new ModelAndView(); //模拟从数据库中查询出User对象 User user = new User(); user.setUsername("李四"); user.setPassword("321"); user.setAge(20); mv.addObject("user",user); mv.setViewName("success");//可以使用视图解析器 return mv; }
-
-
使用使用forward和redirect进行页面跳转
-
使用关键字的方法进行转发或重定向
@RequestMapping("/testForwardAndRedirect") public String testForwardAndRedirect(){ //这个接口作为形式参数是怎么发挥作用的 System.out.println("testForwardAndRedirect。。。"); //请求转发 // return "forward:/WEB-INF/pages/success.jsp"; //重定向 return "redirect:/index.jsp"; //该jsp文件放在webapp目录下 }
-
-
过滤静态资源
-
引入jQuery,并绑定一个点击事件
<head> <title>Title</title> <script src="js/jquery.min.js"></script> <script> $(function () { $("#btn").click(function () { alert("hello btn"); }); }); </script> </head> <body> <button id="btn">点我</button> </body>
-
在springmvc.xml中设置静态资源不过滤
<!-- 设置静态资源不过滤 --> <mvc:resources location="/css/" mapping="/css/**"/> <!-- 样式 --> <mvc:resources location="/images/" mapping="/images/**"/> <!-- 图片 --> <mvc:resources location="/js/" mapping="/js/**"/> <!-- javascript -->
-
-
响应json数据之发送ajax的请求
-
用如下代码替换上面的alert
$.ajax({ url:"user/testAjax", contentType:"application/json;charset=UTF-8", data:'{"username":"hehe","password":"123","age":30}', dataType:"json", type:"post", success:function (data) { } });
-
模拟异步请求响应
@RequestMapping("/testAjax") public void testAjax(@RequestBody String body){ System.out.println("testAjax。。。"); System.out.println(body); }
-
显示结果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BiGLMf15-1605952530252)(C:\Users\chenzhijian\AppData\Roaming\Typora\typora-user-images\image-20201113162537554.png)]
-
-
响应json数据之响应json格式数据
-
导入jackson的jar包,使得json字符串和javaBean对象可以互相转换
-
将前端传过来的json数据包装到user对象中
@RequestMapping("/testAjax") public @ResponseBody User testAjax(@RequestBody User user){ System.out.println("testAjax。。。"); System.out.println(user); user.setUsername("haha"); user.setAge(40); return user; }
-
前端显示后端更改的数据
success:function (data) { alert(data); alert(data.username); alert(data.password); alert(data.age); }
-
3.6 文件上传
-
原理分析
-
代码实现
-
引入fileupload和io的pom依赖
-
配置解析器对象
<!-- 配置文件解析器对象,要求id名称必须是multipartResolver --> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="maxUploadSize" value="10485760"/> </bean>
-
创建表单
<form action="user/fileupload" method="post" enctype="multipart/form-data"> 选择文件:<input type="file" name="upload" /><br> <input type="submit" value="上传"> </form>
-
controller代码
@RequestMapping("/fileupload") public String fileUpload(HttpServletRequest request, MultipartFile upload) throws Exception { //upload这个必须与上传文件表单的name属性值一样 System.out.println("文件上传..."); //上传的位置 String path = request.getSession().getServletContext().getRealPath("/uploads/"); //判断该路径是否存在 File file = new File(path); if(!file.exists()){ file.mkdirs(); } //设置唯一文件名 String filename = upload.getOriginalFilename(); String uuid = UUID.randomUUID().toString().replace("-",""); filename = uuid+"_"+filename; //上传文件 upload.transferTo(new File(path,filename)); return "success"; }
-
3.7 跨服务器上传
-
分析
-
服务器分为:应用服务器,数据库服务器,缓存和消息服务器,文件服务器
-
演示
-
-
代码实现
-
引入jersey的pom依赖
-
创建表单
-
代码实现
@RequestMapping("/fileupload2") public String fileUpload2(MultipartFile upload) throws Exception { //upload这个必须与上传文件表单的name属性值一样 System.out.println("服务器文件上传..."); //上传的位置 String path = "http://localhost:9090/uploads/"; //设置唯一文件名 String filename = upload.getOriginalFilename(); String uuid = UUID.randomUUID().toString().replace("-",""); filename = uuid+"_"+filename; //创建客户端对象 Client client = Client.create(); //建立连接 WebResource webResource = client.resource(path+filename); //上传文件 webResource.put(upload.getBytes()); return "success"; }
-
出现403forbidden
更改tomcat配置,详细见https://blog.csdn.net/Dawn510/article/details/103915414
-
3.8 异常处理
-
Controller调用service,service调用dao,异常都是向上抛出的,最终有DispatcherServlet找异常处理器进行异常的处理
-
controller
@RequestMapping("/testException") public String fileException() throws SysException { System.out.println("Exception..."); try { int i = 10/0; } catch (Exception e) { e.printStackTrace(); throw new SysException("查询所有用户出现了错误。。。"); } return "success"; }
-
编写自定义异常类
public class SysException extends Exception{ private String message; public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public SysException(String message) { this.message = message; } }
-
编写异常处理器
public class SysExceptionResolver implements HandlerExceptionResolver { @Override public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception ex) { SysException e = null; if(ex instanceof SysException){ e = (SysException) ex; }else { e = new SysException("系统正在维护"); } ModelAndView mv = new ModelAndView(); mv.addObject("errorMsg",e.getMessage()); mv.setViewName("error"); return mv; } }
-
编写error页面
-
配置异常处理器
<bean id="sysExceptionResolver" class="cn.itcast.exception.SysExceptionResolver"> </bean>
3.9 拦截器
-
拦截器的作用
Spring MVC 的处理器拦截器类似于 Servlet 开发中的过滤器 Filter,用于对处理器进行预处理和后处理。
与过滤器的区别:
过滤器是 servlet 规范中的一部分,任何 java web 工程都可以使用。
拦截器是 SpringMVC 框架自己的,只有使用了 SpringMVC 框架的工程才能用。
过滤器在 url-pattern 中配置了/*之后,可以对所有要访问的资源拦截。
拦截器它是只会拦截访问的控制器方法,如果访问的是 jsp,html,css,image 或者 js 是不会进行拦 截的。
它也是 AOP 思想的具体应用。 我们要想自定义拦截器, 要求必须实现:HandlerInterceptor 接口。
-
拦截器类
public class MyInterceptor1 implements HandlerInterceptor{ /** * 预处理,controller方法执行前 * return true表示放行,执行下一个拦截器,如果没有,就执行controller中的方法 * return false不放行,可以用重定向方法调转到希望跳转的页面 * @param request * @param response * @param handler * @return * @throws Exception */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("拦截器方法执行了。。。"); return true; } }
-
springmvc.bean中配置拦截器
<!--配置拦截器--> <mvc:interceptors> <!--配置拦截器--> <mvc:interceptor> <!--拦截的路径--> <mvc:mapping path="/user/*"/> <!--不要拦截的路径 <mvc:exclude-mapping path=""/>--> <bean class="cn.itcast.interceptor.MyInterceptor1"></bean> </mvc:interceptor> </mvc:interceptors>
-
jsp中输出
<body> <h3>访问成功</h3> <% System.out.println("success.jsp执行了..."); %> </body>
-
结果 拦截器->controller->jsp
-
三种处理方法的总结:
- 前处理方法:可以用于登录页面,如果登录了就放行,没登录就跳转到登录页面
- 后处理方法,controller方法执行后,success.jsp执行前,如果后处理方法跳转页面,success.jsp依然会执行
- 最后处理方法,success.jsp页面执行后,该方法会执行,可以用于释放资源
-
如果配多个拦截器
3.10 SSM的整合
3.10.1 搭建环境
-
创建数据库
-
引入pom的属性和依赖
-
创建controller、service、dao、damain,并按之前的规矩写上findAll和saveAccout方法
3.10.2 编写Spring框架
-
在resources资源文件中创建spring的配置文件以及引入log4j
<?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" 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/context http://www.springframework.org/schema/context/spring-context.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"> <!-- 开启注解扫描,要扫描的是service和dao层的注解,要忽略web层注解,因为web层让SpringMVC框架 去管理 --> <context:component-scan base-package="cn.itcast"> <!-- 配置要忽略的注解 --> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan> </beans>
-
在业务层添加@Service(“accountService”)注解
-
在测试类中测试成功
@Test public void run1(){ ApplicationContext ac = new ClassPathXmlApplicationContext("classpath:applicationContext.xml"); AccountService as = (AccountService) ac.getBean("accountService"); as.findAll(); }
3.10.3 编写整合SpringMVC框架
-
先搭建SpringMVC的环境,测试能不能独立运行
-
配置web.xml
<!-- 配置前端控制器:服务器启动必须加载,需要加载springmvc.xml配置文件 --> <servlet> <servlet-name>dispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- 配置初始化参数,创建完DispatcherServlet对象,加载springmvc.xml配置文件 --> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc.xml</param-value> </init-param> <!-- 服务器启动的时候,让DispatcherServlet对象创建 --> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcherServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <!-- 配置解决中文乱码的过滤器 --> <filter> <filter-name>characterEncodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>characterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
-
配置springmvc.xml
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" 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 http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 扫描controller的注解,别的不扫描 --> <context:component-scan base-package="cn.itcast"> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan> <!-- 配置视图解析器 --> <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <!-- JSP文件所在的目录 --> <property name="prefix" value="/WEB-INF/pages/" /> <!-- 文件的后缀名 --> <property name="suffix" value=".jsp" /> </bean> <!-- 设置静态资源不过滤 --> <mvc:resources location="/css/" mapping="/css/**" /> <mvc:resources location="/images/" mapping="/images/**" /> <mvc:resources location="/js/" mapping="/js/**" /> <!-- 开启对SpringMVC注解的支持,一行配好处理器映射器,处理器适配器 --> <mvc:annotation-driven /> </beans>
-
index.jsp页面
<a href="account/findAll">测试</a>
-
Controller
@Controller("accountController") @RequestMapping("/account") public class AccountController { @RequestMapping("/findAll") public String testFindAll(){ System.out.println("表现层:找到所有列表"); return "list"; //list.jsp输出文字提示信息 } }
-
-
整合SpringMVC
-
spring的配置文件并没有加载进去,还不可以使用spring框架的方法
-
分析
-
在web.xml文件中加入监听器,从此,web.xml中把javaweb三大组件凑齐了
<!-- 配置Spring的监听器 --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- 配置加载类路径的配置文件 --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </context-param>
-
controller调用service的findAll()方法
@Autowired private AccountServiceImpl accountService;
-
结果
-
3.10.4 编写整合MyBatis框架
-
先搭建Mybatis环境
-
创建主配置文件sqlMapConfig.xml
<?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> <environments default="mysql"> <environment id="mysql"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql:///ssm"/> <property name="username" value="root"/> <property name="password" value="root"/> </dataSource> </environment> </environments> <!-- 使用的是注解 --> <mappers> <!-- <mapper class="cn.itcast.dao.AccountDao"/> --> <!-- 该包下所有的dao接口都可以使用 --> <package name="cn.itcast.dao"/> </mappers> </configuration>
-
用注解写两个方法的sql语句
-
测试查询
@Test public void run1() throws Exception { // 加载配置文件 InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml"); // 创建工厂 SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream); // 创建sqlSession对象 SqlSession session = factory.openSession(); // 获取代理对象 AccountDao dao = session.getMapper(AccountDao.class); // 调用查询的方法 List<Account> list = dao.findAll(); for (Account account : list) { System.out.println(account); } // 释放资源 session.close(); inputStream.close(); }
-
测试保存(增删改需要自己提交事务)
@Test public void run2() throws Exception { Account account = new Account(); account.setName("熊大"); account.setMoney(300d); // 加载配置文件 InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml"); // 创建工厂 SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream); // 创建sqlSession对象 SqlSession session = factory.openSession(); // 获取代理对象 AccountDao dao = session.getMapper(AccountDao.class); // 调用查询的方法 dao.saveAccount(account); //提交事务 session.commit(); // 释放资源 session.close(); inputStream.close(); }
-
-
整合MyBatis框架
-
将MyBatis配置类中的内容移到spring的配置类applicationContext.xml中,进入ioc容器,完成后可以删除sqlMapConfig.xml
<!-- 配置C3P0的连接池对象 --> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql:///ssm" /> <property name="username" value="root" /> <property name="password" value="root" /> </bean> <!-- 配置SqlSession的工厂 --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> </bean> <!-- 配置扫描dao的包 --> <bean id="mapperScanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="cn.itcast.dao"/> </bean>
-
在Service中注入Dao,并且调用其方法,返回
@Override public List<Account> findAll() { System.out.println("业务层:查询所有的账户信息"); return accountDao.findAll(); }
-
Controller存入域对象中,让页面能够读取
@RequestMapping("/findAll") public String findAll(Model model){ System.out.println("表现层:查询所有的账户信息"); List<Account> list = accountService.findAll(); model.addAttribute("list",list); return "list"; }
-
jsp页面显示出数据
<c:forEach items="${list}" var="account"> ${account.name} </c:forEach>
-
-
配置事务,写保存账户方法
-
配置Spring框架声明式事务管理
<!--配置Spring框架声明式事务管理--> <!--配置事务管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!--配置事务通知,dataSource是别人写的比较通用的,要把自己的结构配置一下--> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="find*" read-only="true"/> <tx:method name="*" isolation="DEFAULT"/> //尽可能避开整体扫描 </tx:attributes> </tx:advice> <!--配置AOP增强--> <aop:config> <aop:advisor advice-ref="txAdvice" pointcut="execution(public * cn.itcast.service..ServiceImpl.*(..))"/> </aop:config>
-
写一个提交表单
<form action="account/save" method="post"> 姓名:<input type="text" name="name"><br/> 金额:<input type="text" name="money"><br/> <input type="submit" value="保存"><br/> </form>
-
Controller调Service,Service调Dao
@RequestMapping("/save") public void save(Account account, HttpServletRequest request, HttpServletResponse response) throws IOException { System.out.println("表现层:保存账户信息"); accountService.saveAccount(account); response.sendRedirect(request.getContextPath()+"/account/findAll"); return; }
-
-
完成