- 框架部分
1.1 Hibernate
1.1.1说一下Hibernate开发流程
- Hibernate开发环境搭建
- 导入Hibernate相关jar包即数据库的驱动包
- 新建数据库
- 在数据库中新建一个表用来做测试
- 编写POJO类
- 配置文件
- Hibernate.cfg.xml(hibernate核心配置文件),主要配置数据库连接信息,连接池信息,方言,缓存,映射文件等。
- User.hbm.xml(映射文件),主要是配置对象和表中的映射信息
- 编写SessionFactory工具类
- 通过工具类获取SessionFactory
- 通过SessionFactory创建Session
- 获取到Session意味着和数据库建立连接
- 开启事务
- 操作数据库
- 提交事务
- 关闭session
- Session关闭后连接释放到连接池
- Hibernate中对象的三种状态
- Session关闭后连接释放到连接池
- 瞬时状态(Transient)/临时状态/自由状态:
对于刚创建的一个对象,如果session中和数据库中都不存在该对象,那么该对象就是瞬时对象。
- 持久(Persistent)
已经持久化(数据库中有)并且已经交个session托管(session缓存中有),持久化对象在事务提交的时会把session中托管的对象和持久化对象进行比较,如果不同就发送一条update语句,否则不会发送update语句。
- 脱管(Detached)
数据库存在该对象,但是该对象又没有被session所托管。
-
-
- Hibernate缓存机制
-
Hibernate缓存有3中,一级缓存,二级缓存,查询缓存。
- 一级缓存
- Hibernate的一级缓存是Session的缓存,Session内置的缓存是不能被卸载的
- 的表示号)
- 一级缓存存放的是数据库数据的拷贝。
- 二级缓存
- Hibernate的二级缓存是SessionFactory级别的缓存,所有session共享一个二级缓存。
- Hibernate对二级缓存只是提供了一个接口,具体的需要第三方来实现,比如Ehcache
- Hibernate的二级缓存默认是不启用的,二级缓存存放元数据和预定义的SQL。
- 查询缓存
- 查询缓存主要是针对HQL的查询
- 查询缓存要依赖于二级缓存
- 查询缓存中缓存的是预定义的SQL语句
-
- Hibernate中的查询方式有哪些?
-
Hibernate中有四中查询方式,分别是主键查询,HQL,QBC,原生态SQL查询。
- 主键查询
User user = (User) session.get(User.class, userId); // 立即加载 User user1 = (User) session.load(User.class, userId); // 支持懒加载 |
- HQL查询
Query query2 = session.createQuery("from User u where u.id = :id"); |
需要注意的是HQL是面向对象的查询方式,里面写的都是对象或属性是区分大小写的。
- 原生态的SQL查询
SQLQuery sqlQuery = session.createSQLQuery("select * from t_user u where u.id = :id") |
原生态的SQL查询,适合使用复杂的查询,或者不想使用HQL或者criteria查询,可以使用本地SQL查询,缺点是不能跨越数据库,一般不适用,除非遇到复杂的sql语句才使用。
- Criteria查询
Criteria criteria = session.createCriteria(User.class); List<User> list = criteria.list(); |
Criteria查询叫做条件查询也称QBC(Query By Criteria)查询,它完全面向对象的查询方式,里面把排序和分组都封装了方法。
-
-
- mybatis和hibernate的区别
-
- MyBatis
- 优点
- 可以由开发者灵活的控制SQL语句,减少查询字段。
- 所有的SQL语句都放在统一配置文件中方便维护,管理,减少SQL和程序的耦合
- MyBatis是一个半自动化的ORM框架,对象数据以及对象实际关系仍然需要通过手写sql来实 现和管理
- 支持动态编写SQL
- 缺点
- 数据库移植性很弱,不同的数据库要编写不同的SQL语句
- DAO层的方法不支持方法的重载
- 不支持级联更新和删除
- Hibernate
- 优点
-
- Hibernate中的方言可以方便的做到数据库的移植
- Hibernate中提供了一级缓存,二级缓存,查询缓存来提高查询效率,MyBatis中的缓存不佳
- Hibernate是个全自动化科技,Dao层的开发比较简单,直接调用save方法即可。
-
- 缺点
-
- 多表关联的时候比较复杂,使用成本不低
- SQL语句都是自动生成,对于SQL的优化,修改比较困难
-
- 总结
两个框架各有千秋,如果项目中很少存在多表关联查询(比如10张表以上)那可以选择使用Hiberate,否则还是选择MyBatis,具体选择哪个框架还是要根据项目来定。
mybatis:小巧、方便、高效、简单、直接、半自动
hibernate:强大、方便、高效、复杂、绕弯子、全自动
-
-
- Hibernate和 JDBC优缺点对比
-
- 相同点
- 两者都是JAVA的数据库操作中间件。
- 两者对于数据库进行直接操作的对象都不是线程安全的,都需要及时关闭。
- 两者都可以对数据库的更新操作进行显式的事务处理。
- 不同点
- 使用的SQL语言不同:JDBC使用的是基于关系型数据库的标准SQL语言,Hibernate使用的是HQL(Hibernate query language)语言
- 操作的对象不同:JDBC操作的是数据,将数据通过SQL语句直接传送到数据库中执行,Hibernate操作的是持久化对象,由底层持久化对象的数据更新到数据库中。
- 数据状态不同:JDBC操作的数据是“瞬时”的,变量的值无法与数据库中的值保持一致,而Hibernate操作的数据是可持久的,即持久化对象的数据属性的值是可以跟数据库中的值保持一致的。
- JDBC与Hibernate读取性能
- JDBC仍然是最快的访问方式,不论是Write还是Read操作,都是JDBC快。
- Hibernate使用uuid.hex构造主键,性能稍微有点损失,但是不大。
- Create操作,JDBC在使用批处理的方式下速度比Hibernate快,使用批处理方式耗用JVM内存比不使用批处理方式要多得多。
- 读取数据,Hibernate的Iterator速度非常缓慢,因为他是每次next的时候才去数据库取数据,这一点从观察任务管理器的java进程占用内存的变化也可以看得很清楚,内存是几十K几十K的增加。
- 读取数据,Hibernate的List速度很快,因为他是一次性把数据取完,这一点从观察任务管理器的java进程占用内存的变化也可以看得很清楚,内存几乎是10M的10M的增加。
- JDBC读取数据的方式和Hibernate的List方式是一样的(这跟JDBC驱动有很大关系,不同的JDBC驱动,结果会很不一样),这 从观察java进程内存变化可以判断出来,由于JDBC不需要像Hibernate那样构造一堆Cat对象实例,所以占用JVM内存要比 Hibernate的List方式大概少一半左右。
- Hibernate的Iterator方式并非一无是处,它适合于从大的结果集中选取少量的数据,即不需要占用很多内存,又可以迅速得到结果。另外Iterator适合于使用JCS缓冲。
-
- Hibernate的ORM原理和实现
-
- Hibernate的ORM原理和实现
ORM的全称是Object Relational Mapping,即对象关系映射。它的实现思想就是将关系数据库中表的数据映射成为对象,以对象的形式展现,这样开发人员就可以把对数据库的操作转化为对这些对象的操作。因此它的目的是为了方便开发人员以面向对象的思想来实现对数据库的操作。
- Hibernate是如何实现映射的
在使用Hibernate实现ORM功能的时候,主要的文件有:映射类(*.java)、映射文件(*.hbm.xml)以及数据库配置文件(*.cfg.xml),它们各自的作用如下:
- 映射类
它的作用是描述数据库表的结构,表中的字段在类中被描述成属性,将来就可以实现把表中的记录映射成为该类的对象。
- 映射文件
它的作用是指定数据库表和映射类之间的关系,包括映射类和数据库表的对应关系、表字段和类属性类型的对应关系以及表字段和类属性名称的对应关系等。
- 数据库配置文件
它的作用是指定与数据库连接时需要的连接信息,比如连接哪中数据库、登录用户名、登录密码以及连接字符串等。
-
-
- get和load的区别
-
- get
- 调用后立即发送SQL语句查询,返回的实体对象。
- 查询一个不存在的数据返回null
- get的查询顺序是先到一级缓存à二级缓存à数据库
- load
- 是懒加载,返回的是个代理对象
- 查询一个不存在的数据抛出异常
- 调用load后不会发送sql语句,返回的对象是个代理的,这个对象中只有id属性有值,只有调用该对象的其他属性时才会发送sql语句查询。
- Load默认是懒加载的机制,这种机制是可以通过设置class节点中的lazy属性修改。
-
- 如何进行Hibernate优化
-
很多时候我们是在效率与安全/准确性上找一个平衡点,无论如何,优化都不是一个纯技术的问题,你应该对你的应用和业务特征有足够的了解,一般的,优化方案应在架构设计期就基本确定,否则可能导致没必要的返工,致使项目延期,而作为架构师和用Hibernate与用Jdbc性能相差十几倍很正常,如果不及早调整,很可能影响整个项目的进度。大体上,对于Hibernate性能调优的主要考虑点如下:
- 数据库设计
- 降低关联的复杂性
- 尽量不使用联合主键
- ID的生成机制,不同的数据库所提供的机制并不完全一样
- 适当的冗余数据,不过分追求高范式
- HQL优化
HQL如果抛开它同Hibernate本身一些缓存机制的关联,HQL的优化技巧同普通的SQL优化技巧一样。
- 主配置
- 查询缓存,同下面讲的缓存不太一样,它是针对HQL语句的缓存,即完全一样的语句再次执行时可以利用缓存数据。但是,查询缓存在一个交易系统(数据变更频繁,查询条件相同的机率并不大)中可能会起反作用:它会白白耗费大量的系统资源但却难以派上用场。
- batch_size,同JDBC的相关参数作用类似,参数并不是越大越好,而应根据业务特征去设置
- 生产系统中,切记要关掉SQL语句打印。
- 缓存
- SESSION缓存:在一个Hibernate Session有效,这级缓存的可干预性不强,大多于HIBERNATE自动管理,但它提供清除缓存的方法,这在大批量增加/更新操作是有效的。比如,同时增加十万条记录,按常规方式进行,很可能会发现OutofMemeroy的异常,这时可能需要手动清除这一级缓存:Session.evict以及Session.clear
- 缓存有几种形式,可以在映射文件中配置:read-only(只读,适用于很少变更的静态数据/历史数据),nonstrict-read-write,read-write(比较普遍的形式,效率一般),transactional(JTA中,且支持的缓存产品较少)
- 延迟加载
- 实体延迟加载:通过使用动态代理实现
- 集合延迟加载:通过实现自有的SET/LIST
- 属性延迟加载:通过load
- 事务控制
- 如果不涉及多个事务管理器事务的话,不需要使用JTA,只有Jdbc的事务控制就可以。
- 使用标准的SQL事务隔离级别
- 批量操作
- 批量操作的时候直接使用JDBC
项目经理,还要面对开发人员可能的抱怨,必竟,我们对用户需求更改的控制力不大,但技术/架构风险是应该在初期意识到并制定好相关的对策。应用层的缓存只是锦上添花,永远不要把它当救命稻草,应用的根基(数据库设计,算法,高效的操作语句,恰当API的选择等)才是最重要的。
-
-
- 什么是Hibernate延迟加载
-
延迟加载,也叫懒加载,它是Hibernate为提高程序执行效率而提供的一种机制,即只有真正使用该对象的数据时才会创建。
Hibernate中主要是通过代理机制来实现延迟加载。它的具体过程:Hibernate丛数据库获取某一个对象数据时、获取某一个对象的集合属性值时,或获取某一个对象所关联的另一个对象时,由于没有使用该对象的数据,hibernate并不是数据库加载真正的数据,而只是为该对象创建一个代理对象(cglib生成)来代表这个对象,这个对象上的所有属性都是默认值;只有在真正需要使用该对象的数据时才创建这个真实对象,真正从数据库中加载它的数据,这样在某些情况下,就可以提高查询效率。
举个例子:加载某对象X时,并不会立即从数据库中返回该对象所有的属性值,而是采用代理机制生成X对象的代理,当访问该代理对象的属性时才从数据库中加载该属性值。
Hibernate的延迟加载提供了三大方面其中分别是:实体关联、集合类、属性的延迟加载。
-
-
- NoSession的问题原因及解决方案
-
- 问题产生的原因
因为Hibernate中调用load懒加载的查询,当调完load后session就关闭了,因为我们的session只是配置到了dao层,表现层获取不到,所以在表现层调用的时候session就已经关闭,就爬出了NoSession异常。
- 解决方案
- 可以配置关联关系时设置lazy属性=false,立即加载方法,也可以提前使用数据,使其自动加载。
- 利用Spring提供的OpenSessionInViewFilter解决no session问题
- 说说session缓存的作用
- 减少访问数据库的频率。应用程序从缓存中读取持久化对象的速度显然要比数据库中检索数据的速度块。从而session缓存可以提高查询效率。
- 事务提交的时候会隐式调用flush(),保证了缓存中的数据和数据库数据同步。
- 当缓存中持久化对象的状态发生改变时,Session不会立即执行相关的SQL语句,这使得Session能够把几条相关的SQL语句合并为一条SQL语句,以减少数据库的访问次数,提高访问效率。
-
- session的清理和清空有什么区别
-
- 清理缓存
- 清理缓存调用的是session.flush()方法
- 清理缓存是指按照缓存中对象的状态的变化来同步更新数据库,但不清空缓存
- 清空缓存
- 清空缓存调用的是session.clear()方法
- 清空是把session的缓存置空,但不同步更新数据库。
- 清空缓存后也就意味着关闭session
- Relish
- 根据数据库的变化来同步更新session缓存
- Hibernate中session的特点有哪些?
- 根据数据库的变化来同步更新session缓存
- 线程不安全,每个线程都应该从一个SessionFactory获取自己的session实例。
- Session的主要功能是提供对映射的实体类实例的创建,读取和删除操作。
- 如果其持久化对象类是可序列化的,则Session实例也是可序列化的。
-
- Hibernate三种检索策略的优缺点对比
-
- 立即检索
采用立即检索策略,会将被检索的对象,以及和这个对象关联的一对多对象都加载到缓存中。Session的get方法就使用的立即检索策略。
- 优点:频繁使用的关联对象能够被加载到缓存中。
- 缺点
-
- 占用内存。
- Select语句过多。
-
- 延迟检索
采用延迟检索策略,就不会加载关联对象的内容。直到第一次调用关联对象时,才去加载关联对象。在不涉及关联类操作时,延迟检索策略只适用于Session的load方法。
在类级别操作时,延迟检索策略,只加载类的OID不加载类的其他属性,只用当第一次访问其他属性时,才回访问数据库去加载内容。(这里使用了CGLIB生成了类的代理类)
在关联级别操作时,延迟检索策略,只加载类本身,不加载关联类,直到第一次调用关联对象时,才去加载关联对象程序模式都是用延迟加载策略。如果需要指定使用延迟加载策略。在配置文件中设置<class>的lazy=true,<set>的lazy=true或extra(增强延迟)<many-to-one>的lazy=proxy和no-proxy。
- 优点:由程序决定加载哪些类和内容,避免了大量无用的sql语句和内存消耗。
- 缺点:在Session关闭后,就不能访问关联类对象了。需要确保在Session.close方法前,调用关联对象。
- 迫切左外连接检索
采用左外连接检索,能够使用Sql的外连接查询,将需要加载的关联对象加载在缓存中。
<set>fetch设置为join,<many-to-one>的fetch设置为 join。
- 优点:
- 对应用程序完全透明,不管对象处于持久化状态,还是游离状态,应用程序都可以方便的从一个对象导航到与它关联的对象。
- 使用了外连接,select语句数目少。
- 缺点:
-
- 可能会加载应用程序不需要访问的对象,白白浪费许多内存空间。
- 复杂的数据库表连接也会影响检索性能。
-
-
-
- 什么是悲观锁和乐观锁?
-
- 什么是锁
为什么需要锁?在多用户环境中,在同一时间可能会有多个用户更新相同的记录,这会产生冲突,这就是著名的并发性问题。而锁就可以解决这个问题,即给我们选定的目标数据上锁,使其无法被其他程序修改。
- 悲观锁
悲观锁是指假设并发更新冲突会发生,所以不管冲突是否真的发生,都会使用锁机制。悲观锁会锁住读取的记录,防止其它事务读取和更新这些记录。其它事务会一直阻塞,直到这个事务结束。悲观锁是在使用了数据库的事务隔离功能的基础上,独享占用的资源,以此保证读取数据一致性,避免修改丢失。
数据库悲观锁实现
select * from t_user where id = 1 for update |
这条 sql语句锁定了 t_user表中id为1的记录。本次事务提交之前(事务提交时会释放事务过程中的锁),外界无法修改这些记录。
Hibernate的悲观锁,也是基于数据库的锁机制实现。下面的代码实现了对查询记录的加锁。
主键查询加锁:
User user= (User)session.get(User.class, 1, LockOptions.UPGRADE); |
Hql查询加锁:
Query query = session.createQuery("from User u where u.id = 1"); query.setLockOptions(LockOptions.UPGRADE); |
- 乐观锁
总是认为不会产生并发问题,每次去取数据的时候总认为不会有其他线程对数据进行修改,因此不会上锁,但是在更新时会判断其他线程在这之前有没有对数据进行修改,一般会使用版本号机制或CAS操作实现。
乐观锁的工作原理:读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。
Hibernate中实现:表中添加一个version字段,对象中添加一个version属性,在映射文件中建立它们之间的关系就可以实现,这个version字段由hibernate帮我们维护。
-
-
- 什么ORM?
-
ORM概念
ORM,即Object-Relational Mapping(对象关系映射),它的作用是在关系型数据库和业务实体对象之间作一个映射,这样,我们在具体的操作业务对象的时候,就不需要再去和复杂的SQL语句打交道,只需简单的操作对象的属性和方法。
先从项目中数据流存储形式这个角度说起.简单拿MVC这种分层模式.来说. Model作为数据承载实体. 在用户界面层和业务逻辑层之间数据实现面向对象的形式传递. 当我们需要通过Controller层分发请求把数据持久化时我们会发现,内存中的面向对象如何持久化成关系型数据中存储一条实际数据记录呢?面向对象是从软件工程基本原则(如耦合、聚合、封装)的基础上发展起来的,而关系数据库则是从数学理论发展而来的. 两者之间是不匹配的.而ORM作为项目中间件形式实现数据在不同场景下数据关系映射. 对象关系映射(Object Relational Mapping,简称ORM)是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术.ORM就是这样而来的。
ORM优缺点
优点:
第一:隐藏了数据访问细节,“封闭”的通用数据库交互,ORM的核心。他使得我们的通用数据库交互变得简单易行,并且完全不用考虑该死的SQL语句。快速开发,由此而来。
第二:ORM使我们构造固化数据结构变得简单易行。在ORM年表的史前时代,我们需要将我们的对象模型转化为一条一条的SQL语句,通过直连或是DB helper在关系数据库构造我们的数据库体系。而现在,基本上所有的ORM框架都提供了通过对象模型构造关系数据库结构的功能。这,相当不错。
缺点:
第一:无可避免的,自动化意味着映射和关联管理,代价是牺牲性能。现在的各种ORM框架都在尝试使用各种方法来减轻这块(LazyLoad,Cache),效果还是很显著的。
第二:面向对象的查询语言(X-QL)作为一种数据库与对象之间的过渡,虽然隐藏了数据层面的业务抽象,但并不能完全的屏蔽掉数据库层的设计,并且无疑将增加学习成本.
第三:对于复杂查询,ORM仍然力不从心。
世上没有驴是不吃草的(又想好又想巧,买个老驴不吃草),任何优势的背后都隐藏着缺点,这是不可避免的。问题在于,我们是否能容忍缺点。
常用的ORM框架
-
- Hibernate全自动需要些hql语句
- MyBatis半自动自己写sql语句,可操作性强,小巧
- EclipseLink 一个可扩展的支持JPA的ORM框架,供强大的缓存功能,缓存支持集群。
- Apache OJB
1.2 MyBatis
1.2.1 MyBatis中#和$符号区别
- #{name}
- 被解析为jdbc的预编译语句每个#{}代表一个占位符,比如 where name =?
- 这种方式不会出现SQL注入的问题
- 比较常用
举个例子:
<select id="getUserById" resultMap="userMap"> select * from t_User where id = #{id} </select> |
解析后的SQL如下:
- ${name}
- 将内容直接写到SQL语句中,比如:where name = admin
- 这种方式无法防止SLQ注入的问题
- 一般用于动态表或者动态字段排序
举个列子:
<select id="getUserById" resultMap="userMap"> select * from t_User where id = ${id} </select> |
解析后的SQL如下:
1.2.2 MyBatis开发流程
- 导入Mybatis相关jar包
- 配置文件
- MyBatis核心配置文件:主要配置连接数据库参数,连接池,插件,Mapper文件等。
- Mapper文件:主要配置调用的sql
- 创建SqlSessionFactoryBuilder
- 通过SqlSessionFactoryBuilder来创建SqlSessionFactory
- 通过SqlSessionFactory创建SqlSession
- 通过SqlSession操作数据库
- 调用sqlSession.commit()提交事务
- 关闭sqlSession.close()关闭session
1.2.3 JDBC和MyBatis的对比?
JDBC是Java提供的一个操作数据库的API,我们平时使用jdbc进行编程,需要如下几个步骤。
- 使用jdbc编程需要连接数据库,注册驱动和数据库信息
- 操作Connection,打开Statement对象
- 通过Statement对象执行SQL,返回结果到ResultSet对象
- 使用ResultSet读取数据,然后通过代码转化为具体的POJO对象
- 关闭数据库相关的资源
- JDBC缺点
- 工作量比较大,需要连接数据库,然后处理jdbc底层事务,处理数据类型,还需要操作Connection,Statement对象和ResultSet对象最后还需要关闭它们。
- 频繁的创建和释放连接造成资源浪费从而影响性能
- SQL语句写在代码中造成代码不易维护,修改SQL需要改变Java代码。
- 对结果集的解析麻烦,需要手动转化为POJO对象
- 向preparedStatement中设置参数,对占位符号位置和设置参数值,硬编码在java代码中
因为JDBC实在不好用,于是出现了ORM对Jdbc进行封装,MyBatis对JDBC的封装很好,几乎可以取代Jdbc。
- MyBatis解决JDBC的以下缺点
- MyBatis使用SqlSessionFactoryBuilder来连接完成JDBC需要代码完成的数据库获取和连接,减少了代码的重复。
- MyBatis可以将SQL代码写入xml中,易于修改和维护。
- MyBatis的mapper会自动将执行后的结果映射到对应的Java对象中
- MyBatis的编码风格统一优雅、性能高,灵活性好。
- jdbc,mybatis,hibernate的区别
-
- 从层次上看
JDBC是较底层的持久层操作方式,而Hibernate和MyBatis都是在JDBC的基础上进行了封装使其更加方便程序员对持久层的操作。
-
- 从功能上看
JDBC就是简单的建立数据库连接,然后创建statement,将sql语句传给statement去执行,如果是有返回结果的查询语句,会将查询结果放到ResultSet对象中,通过对ResultSet对象的遍历操作来获取数据;Hibernate是将数据库中的数据表映射为持久层的Java对象,对sql语句进行修改和优化比较困难;MyBatis是将sql语句中的输入参数和输出参数映射为java对象,sql修改和优化比较方便.
-
- 从使用上看
如果进行底层编程,而且对性能要求极高的话,应该采用JDBC的方式;如果要对数据库进行完整性控制的话建议使用Hibernate;如果要灵活使用sql语句的话建议采用MyBatis框架。
1.2.3使用MyBatis中Mapper接口使用?
- Mapper接口方法名和mapper.xml中定义的每个sql的id相同
- Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql 的parameterType的类型相同
- Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同
- Mapper.xml文件中的namespace即是mapper接口的类路径。
- Mapper文件中的slq标签的id尽量不要重复。
1.2.4 MyBatis中的一级缓存和二级缓存
-
- 一级缓存
一级缓存是SqlSession级别的缓存。在操作数据库时需要构造sqlSession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的,一级缓存默认是开启的。
一级缓存有如下特点:
- 第一次发起查询先去找缓存中是否有,如果没有,从数据库查询,然后将查询结果存储到一级缓存中。
- 如果中间sqlSession去执行commit操作(执行插入、更新、删除),则会清空SqlSession中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。
- 第二次发起查询数据,先去找缓存中是否,缓存中有,直接从缓存中获取数据返回。
- 二级缓存
二级缓存的原理和一级缓存原理一样,第一次查询,会将数据放入缓存中,然后第二次查询则会直接去缓存中取。但是一级缓存是基于 sqlSession 的,而 二级缓存是基于 mapper文件的namespace的,也就是说多个sqlSession可以共享一个mapper中的二级缓存区域,并且如果两个mapper的namespace相同,即使是两个mapper,那么这两个mapper中执行sql查询到的数据也将存在相同的二级缓存区域中。
-
-
-
- 二级缓存的特点如下:
-
-
- 二级缓存需要手动开启
- 放在二级缓存中的对象需要实现Serializable接口(因为二级缓存数据存储介质多种多样,不一定只存在内存中,有可能存在硬盘中,如果我们要再取这个缓存的话,就需要反序列化了。所以mybatis中的pojo都去实现Serializable接口)。
- 执行commit操作二级缓存数据将会清空。
- 默认事务提交的时候会清空二级缓存数据
-
-
-
- useCache和flushCache
-
-
userCache是用来设置是否禁用二级缓存的,在statement中设置useCache=false可以禁用当前select语句的二级缓存,即每次查询都会发出sql去查询,默认情况是true,即该sql使用二级缓存。
<select id="getUserById" resultMap="userMap" useCache="true"> Select * from t_User where id = #{id} </select |
在mapper的同一个namespace中,如果有其它insert、update、delete操作数据后需要刷新缓存,如果不执行刷新缓存会出现脏读。设置statement配置中的flushCache=”true” 属性,默认情况下为true,即刷新缓存,如果改成false则不会刷新。使用缓存时如果手动修改数据库表中的查询数据会出现脏读。
<update id="updateUser" flushCache="true"> update t_user set name = #{username} where id = #{id} </update> |
1.2.5 MyBatis插入如何返回主键?
-
- MySQl用法
<insert id="addUser" useGeneratedKeys="true" keyProperty="userId"> insert into t_user (username,password) values(#{username},#{password}); </insert> |
useGeneratedKeys:取值范围true|false(默认值),设置是否使用JDBC的getGenereatedKeys方法获取主键并赋值到keyProperty设置的领域模型属性中。
keyProperty :用户设置主键回填的到那个属性。以上述例子为列,如果调用addUser方法时传递的是user对象则把主键设置到user对象的userId属性中(没有属性则不设置),如果传递的是个Map则给Map中添加一个键值对,key是userId,value是主键值。
-
- Oracle用法
<insert id="updateUser" > <selectKey resultType="INTEGER" order="BEFORE" keyProperty="userId"> SELECT SEQ_USER.NEXTVAL as userId from DUAL </selectKey> insert into t_user (user_id, user_name) values (#{userId}, #{userName}) </insert> |
由于Oracle没有自增长一说法,只有序列这种模仿自增的形式,所以不能再使用“useGeneratedKeys”属性。
1.2.6 MyBatis参数的传递有几种?常用有哪些
- 采用map的方式传递
- 采用注解的方式
- 采用对象的方式
- 采用索引获取
如果方法的形参超过3个建议使用Map或者封装成对象,如果形参少于3个就使用注解,按照索引获取不推荐使用,因为可读性不强。
1.2.7说说MyBatis中ResultMap都是怎么用的?
ResultMap标签一般用在三种地方,
第一种:对象属性和表中字段不一致可以用ResultMap建立映射关系。
第二种:级联查询。
第三种:多个ResultMap可以继承。
1.3 SpringMVC
1.3.1 SpringMVC工作原理
SpringMVC原理:
SpringMVC执行流程:
- 用户发送请求至前端控制器DispatcherServlet。
- DispatcherServlet收到请求调用HandlerMapping映射器。
- 映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。
- DispatcherServlet调用HandlerAdapter处理器。
- HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)。
- Controller执行完成返回ModelAndView。
- HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet。
- DispatcherServlet将ModelAndView传给ViewReslover视图解析器。
- ViewReslover解析后返回具体View。
- DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。
- DispatcherServlet响应用户。
SpringMVC中的重要组件:
- 前端控制器DispatcherServlet:作用是:接收请求、响应结果,相当于转发器;
DispatcherServlet相当于中央处理器,有了它就减少了其他组件之间的耦合。
- 处理器映射器HandlerMapping:作用是:根据请求的url查找Handler。
- 处理器适配器HandlerAdapter:作用是:按照特定的规则(HandlerAdapter要求的规则)去执行Handler。
- 视图解析器ViewResolver:作用是:根据逻辑视图名解析真正的视图(View)。
- 视图 View:View是一个接口,实现类支持不同的View类型(jsp、freemarker等)。
1.3.2 SpringMVC的常用注解有哪些?
- 组件行型注解
注解 | 说明 |
@Component | 类定义之前添加@Component注解,他会被spring容器识别,并转为bean |
@Repository | 对Dao实现类进行注解(特殊的@Component) |
@Service | 用于对业务逻辑层进行注解(特殊的@Component) |
@Controller | 用于控制层注解(特殊的@Component) |
以上四种注解都是注解在类上的,被注解的类将被spring初始话为一个bean,然后统一管理。
- 请求和参数型注解
- @RequestMapping:用于处理请求地址映射,可以作用于类和方法上。
-
- value:定义request请求的映射地址
- method:请求的方式,默认接受get请求,如果请求方式和定义的方式不一样则请求无法成功。
- params:定义request请求中必须包含的参数值。
- headers:定义request请求中必须包含某些指定的请求头,如:RequestMapping(value = "/something", headers = "content-type=text/*")说明请求中必须要包含"text/html", "text/plain"这中类型的Content-type头,才是一个匹配的请求。
- consumes:定义请求提交内容的类型。
- produces:指定返回的内容类型
-
- @RequestMapping:用于处理请求地址映射,可以作用于类和方法上。
@RequestMapping(value = "/test.do", params = {"name=qf"}, headers = { "Accept-Encoding=gzip" },method =RequestMethod.GET) public String test() { System.out.println("请求成功"); return "index"; } |
-
- @RequestParam:用于获取传入参数的值
-
- value:参数的名称
- required:定义该传入参数是否必须
- defaultValue:默认值
-
- @RequestParam:用于获取传入参数的值
@RequestMapping("/test2.do") public String test2(@RequestParam(value = "name", required = false,defaultValue="qf") String username) { System.out.println("name = " + username); return "index"; } |
-
- @PathViriable:用于定义路径参数值
-
- value:参数的名称
-
- @PathViriable:用于定义路径参数值
@RequestMapping("/getUserById/{id}") public String test3(@PathVariable(value = "id") Integer id){ System.out.println("id = "+id); return "index"; } |
-
- @ResponseBody:作用于方法上,可以将整个返回结果以某种格式返回,如json或xml格式。
@RequestMapping("/test4.do") @ResponseBody public String test4() { return "hello"; } |
-
- @CookieValue:用于获取请求的Cookie值
@RequestMapping("/requestParams.do") public String requestParams(@CookieValue("JSESSIONID") String cookie) { return "index"; } |
-
- @ModelAttribute:用于把参数保存到model中,可以注解方法或参数,注解在方法上的时候,该方法将在处理器方法执行之前执行,然后把返回的对象存放在 session(前提时要有@SessionAttributes注解) 或模型属性中,@ModelAttribute(“attributeName”) 在标记方法的时候指定,若未指定,则使用返回类型的类名称(首字母小写)作为属性名称。
// 方法的返回值放到request作用域,key为customer // 该方法将在处理器方法执行之前执行 @ModelAttribute("customer") public Customer getUser() { Customer customer = new Customer(); customer.setName("test姓名"); return customer; }
// 自动注入request作用域中key为customer的数据 @RequestMapping("/testMode1.do") public String getUsers(@ModelAttribute("customer") Customer customer,ModelMap map) { System.out.println("CustomerController.getUsers() :"+customer.getName()); map.put("msg", "hello"); return "index"; } |
-
- @SessionAttributes
默认情况下Spring MVC将模型中的数据存储到request域中。当一个请求结束后,数据就失效了。如果要跨页面使用。那么需要使用到session。而@SessionAttributes注解就可以使得模型中的数据存储一份到session域中。配合@ModelAttribute("user")使用的时候,会将对应的名称的model值存到session中,
@Controller @RequestMapping(value = "customerController/") // 加入SessionAttributes注解后request作用域中key为customer,msg的数据会储存一份到session作用域中 @SessionAttributes(value={"customer","msg"}) public class CustomerController {
// 方法的返回值放到request作用域,key为customer // 该方法将在处理器方法执行之前执行 @ModelAttribute("customer") public Customer getUser() { Customer customer = new Customer(); customer.setName("test姓名"); return customer; }
// 自动注入request作用域中key为customer的数据 @RequestMapping("/testMode1.do") public String getUsers(@ModelAttribute("customer") Customer customer,ModelMap map) { System.out.println("CustomerController.getUsers() :"+customer.getName()); map.put("msg", "hello"); return "index"; } } |
-
- @RequestBody
该注解常用来处理Content-Type: 不是application/x-www-form-urlencoded编码的内容,例如application/json, application/xml等,接收HTTP请求的JSON数据,将JOSN数据转为Java对象。
@RequestMapping(value="addUser") public String addUser(@RequestBody Customer customer){ System.out.println("DemoController.addUser():"+customer); return "index"; } |
var obj = new Object(); obj.id =1; obj.name = '张三';
$.ajax({ url: "addUser", type:"POST", contentType:"application/json", data:JSON.stringify(obj), success: function(data){ } }); |
-
- @InitBinder:属性编辑器,在Controller之前执行,可以对用户输入数据进行处理。
@InitBinder public void initBinder(WebDataBinder dataBinder) { dataBinder.registerCustomEditor(Date.class, new PropertyEditorSupport() { public void setAsText(String value) { try { setValue(new SimpleDateFormat("yyyy-MM-dd").parse(value)); } catch (Exception e) { setValue(null); } } }); } |
-
- @DateTimeFormat:时间格式化
@DateTimeFormat(pattern="yyyy-MM-dd") private Date birthday; |
注意:该注解起作用需要开启注解驱动: <mvc:annotation-driven/>
- 自动注入注解
- @Autowired
该注解是按照类型(byType)装配依赖对象,默认情况下它要求依赖对象必须存在,如果允许null值,可以设置它的required属性为false。如果我们想使用按照名称(byName)来装配,可以结合@Qualifier注解一起使用。
-
- @Resource
该默认按照ByName自动注入,由J2EE提供, @Resource有两个重要的属性:name和type,如果使用name属性,则使用byName的自动注入策略,而使用type属性时则使用byType自动注入策略。如果既不制定name也不制定type属性,这时将通过反射机制使用byName自动注入策略。
注:两者都可以写在字段和setter方法上。两者如果都写在字段上,那么就不需要再写setter方法。最好是将@Resource放在setter方法上,因为这样更符合面向对象的思想,通过set、get去操作属性,而不是直接去操作属性。
1.3.3如何解决Get和Post乱码
-
-
-
- 解决post乱码
-
-
SpringMVC中提供了一个CharacterEncodingFilter来解决乱码的问题,只需要在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> |
-
-
-
- 解决get乱码
-
-
解决get乱码有两种方式第一种是修改tomcat配置文件编码与工程编码一致,修改tomcat的config文件夹中的server.xml文件。
<Connector URIEncoding="utf-8" connectionTimeout="20000" port="80" protocol="HTTP/1.1" redirectPort="8443"/> |
另外一种方法对参数进行重新编码,ISO8859-1是tomcat默认编码,需要将tomcat编码后的内容按utf-8编码。
String name = new String(request.getParameter("name").getBytes("iso-8859-1"),"utf-8"); |
1.3.4 什么是RestFUL的风格
RESTful风格只是一种架构风格,是一种种思想,http是这个风格下的产物。它主要用于客户端和服务器交互类的软件。基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。讲RestFul下面两个概念一定要说。
-
-
-
- 资源的定位:从url可以知道用户的访问的是那块资源。
-
-
-
-
-
- 资源的操作:从请求方式可以知道用户对资源进行一个什么样的操作。
-
-
操作 | 添加 | 修改 | 删除 | 查询 |
请求方式(method) | POST | PUT | DELETE | GET |
请求地址(url) | /addUser?name=zhang | /updateUser?name=admin&id=12 | /deleteUser/12 | /findUser/12 |
说明 | 调用insert操作 | 调用update操作 | 调用delete操作 | 调用select操作 |
1.3.5 Spring容器和SpirngMVC容器有什么关系
首先 springmvc和spring它俩都是容器,容器就是管理对象的地方,例如Tomcat,就是管理servlet对象的,而springMVC容器和spring容器,就是管理bean对象的地方,再说的直白点,springmvc就是管理controller对象的容器,spring就是管理service和dao的容器,所以我们在springmvc的配置文件里配置的扫描路径就是controller,而spring的配置文件里自然配的就是service和dao。
Spring-mvc.xml
<context:component-scan base-package="com.qf.controller"></context:component-scan> |
applicationContext.xml
<context:component-scan base-package="com.qf" use-default-filters="false"> <context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/> <context:include-filter type="annotation" expression="org.springframework.stereotype.Repository"/> </context:component-scan> |
至于它是怎么管理起来的,又是怎么注入属性的,这就涉及到他们底层的实现技术了,其次, spring容器和springmvc容器的关系是父子容器的关系。spring容器是父容器,springmvc是子容器。在子容器里可以访问父容器里的对象,但是在父容器里不可以访问子容器的对象,说的通俗点就是,在controller里可以访问service对象,但是在service里不可以访问controller对象,项目中用的bean很多,都交给那个容器管理呢?看下面
- SpringMVC容器管理的Bean
-
- Controller
- SpringMVC相关的bean,比如事务解析器,文件上传bean
-
- Spring容器管理的Bean
- 读取配置文件
- 数据源
- 事务管理相关的bean
- Service
- dao
- Spring和其他框架整合的bean
1.3.6 SpirngMVC对异常的处理方式有哪些?
SpringMVC中对异常的处理有三种方式分别如下:
- 使用Spring MVC提供的简单异常处理器SimpleMappingExceptionResolver。
这种方式需要在springmvc-servlet.xml中配置。
<bean class = "org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"> <!-- 定义默认的异常处理页面,当该异常类型的注册时使用 --> <property name="defaultErrorView" value="error"> </property> <!-- 定义异常处理页面用来获取异常信息的变量名,默认名为exception --> <property name = "exceptionAttribute" value="ex"> </property> <!-- 定义需要特殊处理的异常--> <property name="exceptionMappings"> <props> <!-- 用类名或完全路径名作为key,异常页面名字作为值 --> <prop key = "org.apache.shiro.authz.UnauthorizedException"> common/unauthorized </prop> </props> </property> </bean> |
- 实现Spring的异常处理接口HandlerExceptionResolver 自定义自己的异常处理器。
@Component public class MyExceptionHandler implements HandlerExceptionResolver {
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { Map<String, Exception> map = new HashMap<String, Exception>(); map.put("ex", ex);
// 根据获取的Exception参数进行view跳转 if (ex instanceof RuntimeException) { return new ModelAndView("error-my", map); } else { return new ModelAndView("error", map); } } } |
- 使用@ExceptionHandler进行处理。
使用@ExceptionHandler进行处理有一个不好的地方是进行异常处理的方法必须与出错的方法在同一个Controller里面。
@Controller public class DemoController {
@ExceptionHandler({ Exception.class }) public String exception(Exception e) { System.out.println("DemoController.exception()"); System.out.println(e.getMessage()); e.printStackTrace(); return "error"; }
@RequestMapping(value = "test") public String test() { System.out.println("DemoController.test()"); int i =10/0; return "index"; }
} |
-
- Spring
1.4.1谈谈你对Spring的理解
首先Spring 是一个开源框架,是为了解决企业应用程序开发复杂性而创建的。框架的主要优势之一就是其分层架构,分层架构允许你选择使用哪一个组件,同时为 J2EE 应用程序开发提供集成的框架。Spring为简化企业级应用开发而生,使用Spring可以使简单的JavaBean实现以前只有EJB才能实现的功能。Spring是一个IOC和AOP容器框架。
Spring 框架是一个分层架构,由 7 个定义良好的模块组成。Spring 模块构建在核心容器之上,核心容器定义了创建、配置和管理 bean 的方式。
组成 Spring 框架的每个模块(或组件)都可以单独存在,或者与其他一个或多个模块联合实现。每个模块的功能如下:
核心容器:核心容器提供 Spring 框架的基本功能。核心容器的主要组件是 BeanFactory,它是工厂模式的实现。BeanFactory 使用控制反转 (IOC) 模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。
Spring上下文:Spring 上下文是一个配置文件,向 Spring 框架提供上下文信息。Spring 上下文包括企业服务,例如 JNDI、EJB、电子邮件、国际化、校验和调度功能。
Spring AOP:通过配置管理特性,Spring AOP 模块直接将面向方面的编程功能集成到了 Spring 框架中。所以,可以很容易地使 Spring 框架管理的任何对象支持 AOP。Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖 EJB 组件,就可以将声明性事务管理集成到应用程序中。
Spring DAO:JDBC DAO 抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量(例如打开和关闭连接)。Spring DAO 的面向 JDBC 的异常遵从通用的 DAO 异常层次结构。
Spring ORM:Spring 框架插入了若干个 ORM 框架,从而提供了 ORM 的对象关系工具,其中包括 JDO、Hibernate 和 iBatis SQL Map。所有这些都遵从 Spring 的通用事务和 DAO 异常层次结构。
Spring Web 模块:Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。所以,Spring 框架支持与 Jakarta Struts 的集成。Web 模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。
Spring MVC 框架:MVC 框架是一个全功能的构建 Web 应用程序的 MVC 实现。通过策略接口,MVC 框架变成为高度可配置的,MVC 容纳了大量视图技术,其中包括 JSP、Velocity、Tiles、iText 和 POI。
Spring主要核心是:
IOC:Inversion of Control(控制反转) 平常的java开发中,程序员在某个类中需要依赖其它类的方法,则通常是new一个依赖类再调用类实例的方法,这种开发存在的问题是new的类实例不好统一管理,spring提出了依赖注入的思想,即依赖类不由程序员实例化,而
是通过 spring 容器帮我们 new 指定实例并且将实例注入到需要该对象的类中。依赖注入的另一种说法是“控制反转”,通俗的理解是:平常我们 new 一个实例,这个实例的控制权是我们程序员,而控制反转是指 new 实例工作不由我们程序员来做而是交给spring容器来做。
DI:Dependency Injection,即“依赖注入”。其实IOC和DI本就是同一个概念的两种不同的表述,应用程序依赖容器提供的外部对象,容器将其依赖的外部资源在运行期注入到应用程序中;某个对象被调用时,其所依赖的对象由容器注入。
AOP: Aspect-OrientedProgrammin(面向切面编程)在面向对象编程(OOP)思想中,我们将事物纵向抽象成一个个的对象。而在面向切面编程中,我们将一个个对象某些类似的方面横向抽象成一个切面,对这个切面进行一些如权限验证,事物管理,记录日志等公用操作处理的过程就是面向切面编程的思想。
1.4.2 Spring中用到的设计模式有哪些?
- 工厂模式
将对象的创建和使用相分离,采用工厂模式,即应用程序将对象的创建及初始化职责交给Bean工厂对象。
- 单例模式
Spring下默认的bean均为singleton,可通过scope属性来指定bean的作用域。
- 代理模式
在Spring Aop的实现中用到了JDK或Cglib来实现的动态代理。
- 观察者
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。spring中Observer模式常用的地方是listener的实现。如ApplicationListener。
- 适配器
在Spring的Aop中,使用的Advice(通知)来增强被代理类的功能。Spring实现这一AOP功能的原理就使用代理模式对类进行方法级别的切面增强,即生成被代理类的代理类, 并在代理类的方法前,设置拦截器,通过执行拦截器重的内容增强了代理方法的功能,实现的面向切面编程。
- 策略模式
加载资源文件的方式,使用了不同的方法,比如:ClassPathResourece,FileSystemResource,ServletContextResource,UrlResource但他们都有共同的借口Resource。
1.4.3 Spring中的常用注解
- @Autowired: 可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作。
- qequired:是否必须注入,默认是true。
该注解可以消除get,set方法,是根据类型进行自动转配的。该注解根据bean 类型从spring 上线文中进行查找,注册类型必须唯一,否则报异常。@Autowired(required=false) 表示,如果spring 上下文中没有找到该类型的bean 时,才会使用new SoftPMServiceImpl()。
@Autowired 标注作用于Map类型时,如果 Map 的 key 为 String 类型,则 Spring 会将容器中所有类型符合 Map 的 value 对应的类型的 Bean 增加进来,用 Bean 的 id 或 name 作为 Map 的 key。
@Autowired private Map<String, IUserDao> map;
@Autowired private IUserDao userDao; |
- @Qualifier
使用 @Autowired时,如果找到多个同一类型的bean,则会抛异常,此时可以使用 @Qualifier("beanName"),明确指定bean的名称进行注入,此时与 @Resource指定name属性作用相同。
@Autowired @Qualifier(value="userDao") private IUserDao userDao; |
- @Resource
该注解默认按bean的name进行查找,如果没有找到会按type进行查找,此时与 @Autowired类似。如果按照类型匹配到多个可以通过name属性指定织入的bean名称。
@Resource private IUserDao userDao; |
- @Scope:该注解用来定义一个bean的作用范围。
该注解中可以指定如下值:
-
- singleton:定义bean的范围为每个spring容器一个实例(默认值)
- prototype:定义bean可以被多次实例化(使用一次就创建一次)
- request: 定义bean的范围是http请求(springMVC中有效)
- session: 定义bean的范围是http会话(springMVC中有效)
@Scope("session") @Repository public class UserDaoImpl {} |
- @PostConstruct
在方法上加上注解 @PostConstruct,这个方法就会在Bean初始化之后被Spring容器执行。执行顺序是Constructor >> @Autowired >> @PostConstruct
- @PreDestroy
在方法上加上注解 @PreDestroy ,这个方法就会在Bean 被销毁前被Spring 容器执行。
- @Required
@Required负责检查一个bean在初始化时其声明的set方法是否被执行,当某个被标注了 @Required的Setter方法没有被调用,则Spring在解析的时候会抛出异常,以提醒开发者对相应属性进行设置。@Required注解只能标注在Setter方法之上。因为依赖注入的本质是检查Setter方法是否被调用了,而不是真的去检查属性是否赋值了以及赋了什么样的值。如果将该注解标注在非setXxxx()类型的方法则被忽略。
其他注解请看1.3.2内容。
1.4.4简单介绍下SpringBean的生命周期
在说明前可以思考一下Servlet的生命周期:实例化,初始init,接收请求service,销毁destroy;Spring上下文中的Bean也类似,如下:
- 实例化一个Bean--也就是我们常说的new;
- 按照Spring上下文对实例化的Bean进行配置--也就是IOC注入;
- 如果这个Bean已经实现了BeanNameAware接口,会调用它实现的setBeanName(String)方法,此处传递的就是Spring配置文件中Bean的id值
- 如果这个Bean已经实现了BeanFactoryAware接口,会调用它实现的setBeanFactory(setBeanFactory(BeanFactory)传递的是Spring工厂自身(可以用这个方式来获取其它Bean,只需在Spring配置文件中配置一个普通的Bean就可以);
- 如果这个Bean已经实现了ApplicationContextAware接口,会调用setApplicationContext(ApplicationContext)方法,传入Spring上下文(同样这个方式也可以实现步骤4的内容,但比4更好,因为ApplicationContext是BeanFactory的子接口,有更多的实现方法);
- 如果这个Bean关联了BeanPostProcessor接口,将会调用postProcessBeforeInitialization(Object obj, String s)方法,BeanPostProcessor经常被用作是Bean内容的更改,并且由于这个是在Bean初始化结束时调用那个的方法,也可以被应用于内存或缓存技术;
- 如果Bean在Spring配置文件中配置了init-method属性会自动调用其配置的初始化方法。
- 如果这个Bean关联了BeanPostProcessor接口,将会调用postProcessAfterInitialization(Object obj, String s)方法、;注:以上工作完成以后就可以应用这个Bean了,那这个Bean是一个Singleton的,所以一般情况下我们调用同一个id的Bean会是在内容地址相同的实例,当然在Spring配置文件中也可以配置非Singleton,这里我们不做赘述。
- 当Bean不再需要时,会经过清理阶段,如果Bean实现了DisposableBean这个接口,会调用那个其实现的destroy()方法;
- 最后,如果这个Bean的Spring配置中配置了destroy-method属性,会自动调用其配置的销毁方法。
1.4.5 Spring有什么优点?能帮我做什么?
- 优点
- 使用Spring的IOC容器,将对象之间的依赖关系交给Spring,降低组件之间的耦合性,让我们更专注于应用逻辑
- 可以提供众多服务,事务管理,WS等。
- AOP的很好支持,方便面向切面编程。
- 对主流的框架提供了很好的集成支持,如hibernate,Struts2,JPA等
- Spring DI机制降低了业务对象替换的复杂性。
- Spring属于低侵入,代码污染极低。
- Spring的高度可开放性,并不强制依赖于Spring,开发者可以自由选择Spring部分或全部。
- Spring自己也提供了JDBC模板来操作数据库。
- Spring能方便的和JavaEE(邮件发送,任务调度)整合
1.4.6说下Spring对事务的控制
作为企业级应用程序框架,Spring在不同的事务管理API之上定义了一个抽象层。而应用程序开发人员不必了解底层的事务管理API,就可以使用Spring的事务管理机制。Spring支持编程式事务管理和声明式的事务管理。
编程式事务管理:将事务管理代码嵌入到业务方法中来控制事务的提交和回滚,在编程式事务中,必须在每个业务操作中包含额外的事务管理代码。
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = {"classpath:applicationContext.xml" }) public class test { @Resource private PlatformTransactionManager txManager; @Resource private DataSource dataSource; @Test public void testdelivery() { // 1.定义事务隔离级别,传播行为, DefaultTransactionDefinition def = new DefaultTransactionDefinition(); def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED); def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
// 2.获取事务 // 事务状态类,通过PlatformTransactionManager的getTransaction方法根据事务定义获取; // 获取事务状态后,Spring根据传播行为来决定如何开启事务 TransactionStatus status = txManager.getTransaction(def);
// 3.创建JdbcTemplate JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); try { // 4.操作数据库 jdbcTemplate.update("insert into t_user(name) values(?)", "达老师"); // 5.手动提交事务 txManager.commit(status); // 提交status中绑定的事务 } catch (RuntimeException e) { // 6.手动回滚事务 txManager.rollback(status); // 回滚 } } } |
声明式事务管理:大多数情况下比编程式事务管理更好用。它将事务管理代码从业务方法中分离出来,事务管理作为一种横切关注点,通过AOP思想实现事务控制。
<!-- 5.事务管理器 --> <bean id="tx" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean>
<!-- 6.事务的策略 --> <tx:advice transaction-manager="tx" id="txAdvices"> <tx:attributes>
<!-- 查询的操作 --> <tx:method name="get*" isolation="DEFAULT" propagation="REQUIRED" read-only="true"/> <tx:method name="find*" isolation="DEFAULT" propagation="REQUIRED" read-only="true"/>
<!-- 更新操作 --> <tx:method name="add*" isolation="DEFAULT" propagation="REQUIRED" read-only="false"/> <tx:method name="update*" isolation="DEFAULT" propagation="REQUIRED" read-only="false"/> <tx:method name="delete*" isolation="DEFAULT" propagation="REQUIRED" read-only="false"/> </tx:attributes> </tx:advice>
<!-- 7.AOP的配置 --> <aop:config> <aop:pointcut expression="execution(* com.qf.service.*.*(..))" id="p1"/> <aop:advisor advice-ref="txAdvices" pointcut-ref="p1" /> </aop:config> |
Spring中和事务相关的三个顶级接口:
接口名称 | 说明 |
PlatformTransactionManager | 最核心的一个接口,定义了获取事务,提交事务,回滚事务等操作。 |
TransactionDefinition | 定义了事务的属性,比如:事务隔离级别,传播行为,是否只读等操作。 |
TransactionStatus | 定义了事务的保存点,是否为新事物,是否为回滚等操作。 |
Spring提供了许多内置事务管理器实现:
- DataSourceTransactionManager:位于org.springframework.jdbc.datasource包中,数据源事务管理器,提供对单个javax.sql.DataSource事务管理,用于Spring JDBC抽象框架、iBATIS或MyBatis框架的事务管理。
- JdoTransactionManager:位于org.springframework.orm.jdo包中,提供对单个javax.jdo.PersistenceManagerFactory事务管理,用于集成JDO框架时的事务管理。
- JpaTransactionManager:位于org.springframework.orm.jpa包中,提供对单个javax.persistence.EntityManagerFactory事务支持,用于集成JPA实现框架时的事务管理。
- HibernateTransactionManager:位于org.springframework.orm.hibernate3包中,提供对单个org.hibernate.SessionFactory事务支持,用于集成Hibernate框架时的事务管理;该事务管理器只支持Hibernate3+版本,且Spring3.0+版本只支持Hibernate 3.2+版本。
- JtaTransactionManager:位于org.springframework.transaction.jta包中,提供对分布式事务管理的支持,并将事务管理委托给Java EE应用服务器事务管理器。
- OC4JjtaTransactionManager:位于org.springframework.transaction.jta包中,Spring提供的对OC4J10.1.3+应用服务器事务管理器的适配器,此适配器用于对应用服务器提供的高级事务的支持。
- WebSphereUowTransactionManager:位于org.springframework.transaction.jta包中,Spring提供的对WebSphere 6.0+应用服务器事务管理器的适配器,此适配器用于对应用服务器提供的高级事务的支持。
- WebLogicJtaTransactionManager:位于org.springframework.transaction.jta包中,Spring提供的对WebLogic 8.1+应用服务器事务管理器的适配器,此适配器用于对应用服务器提供的高级事务的支持。
1.4.7解释SpringJDBC,SpringORM,SpringWeb模块作用
- SpringJDBC
Spring 的JDBC 模块负责数据库资源管理和错误处理,大大简化了开发人员对数据库的操作,使得开发人员可以从繁琐的数据库操作中解脱出来,从而将更多的精力投入到编写业务逻辑中。SpringJDBC提供了一个模板类可以直接操作数据,还提供了一个JdbcDaoSupport,这样可以对我们的Dao层进行扩展。
- SpringORM
SpringORM模块是Spring集成ORM框架技术(hibernate,JPA,TopLink,MyBatis)而提供的,对于这些技术Spring每一个都提供了集成类,并平稳的和Spring事务进行集成。
对于每一种技术,配置主要在于将一个 DataSource bean 注入到某种sessionFactory或者 EntityManagerFactory 等 bean 中。纯 JDBC不需要这样的一个集成类 因为JDBC 仅依 赖于一个 DataSource 。
如果你计划使用一种ORM技术,比如JPA或者Hibernate,那么你就不需要Spring -JDBC模块了,你需要的是这个 Spring –ORM。
- SpringWeb
SpringWeb模块主要是对web的支持,是建立在ApplicationContext基础之上的,提供一个适合Web应用的上下文。它也有对Web框架的支持,比如:Struts1,Struts2,JSF等。
1.4.8 Spring的配置文件有什么用?
首先Spring的配置文件是个xml文件,它里面描述了Bean的配置和Bean之间的依赖,还有一些开启包扫描,事物配置等。
1.4.9什么是IOC容器?
IOC容器就是具有依赖注入功能的容器,是可以创建对象的容器,IOC容器负责实例化、定位、配置应用程序中的对象及建立这些对象间的依赖,并管理这整个bean的生命周期。通常new一个实例,控制权由程序员控制,而"控制反转"是指new实例工作不由程序员来做而是交给Spring容器来做。
1.4.10 IOC的优点
-
- 实现组件之间的解耦,提高程序的灵活性和可维护性
- IOC容器支持饿汉式和懒汉式方式初始化bean
1.4.11 ApplicationContext实现类有哪些?
ClassPathXmlApplicationContext
从xml文件中加载Bean的定义,这里需要真确的配置classpath,应为容器在classpath路径下面寻找xml文件。
// 也可以这样写:classpath:applicationContext.xml: ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); |
FileSystemXmlApplicationContext
从xml文件中加载Bean的定义,需要给定一个配置文件的路径,从该路径加载xml文件。
// 也可以这样写:file:src/applicationContext.xml ApplicationContext ctx = new FileSystemXmlApplicationContext("src/applicationContext.xml"); |
WebApplicationContext
WebApplicationContext Spring提供WebApplicationContextUtils通过该类的getWebApplicationContext(ServletContext sc)方法获取,即可从ServletContext中获取WebApplicationContext。一般用到Web环境中获取Spring容器对象,还需要在web.xml中添加监听器在Web容器启动的时候初始化Spring容器。
// 1.获取Servlet上下文对象 ServletContext servletContext = request.getServletContext();
// 2.在Web环境中获取Spring容器 WebApplicationContext wac = WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext); |
1.4.12 BeanFactory和ApplicationContext有什么区别?
- IoC容器的主要接口关系图如下:
- BeanFactory
BeanFactory 是 Spring 的“心脏”。它就是 Spring IoC 容器的真面目。Spring 使用 BeanFactory 来实例化、配置和管理 Bean。
BeanFactory是IOC容器的核心接口, 它定义了IOC的基本功能,我们看到它主要定义了getBean方法。getBean方法是IOC容器获取bean对象和引发依赖注入的起点。方法的功能是返回特定的名称的Bean。
BeanFactory 是初始化 Bean 和调用它们生命周期方法的“吃苦耐劳者”。注意,BeanFactory 只能管理单例(Singleton)Bean 的生命周期。它不能管理原型(prototype,非单例)Bean 的生命周期。这是因为原型 Bean 实例被创建之后便被传给了客户端,容器失去了对它们的引用。
- ApplicationContext
如果说BeanFactory是Spring的心脏,那么ApplicationContext就是完整的躯体了,ApplicationContext由BeanFactory派生而来,提供了更多面向实际应用的功能。在BeanFactory中,很多功能需要以编程的方式实现,而在ApplicationContext中则可以通过配置实现。
BeanFactorty接口提供了配置框架及基本功能,但是无法支持spring的aop功能和web应用。而ApplicationContext接口作为BeanFactory的派生,因而提供BeanFactory所有的功能。而且ApplicationContext还在功能上做了扩展,相较于BeanFactorty,ApplicationContext还提供了以下的功能:
- MessageSource, 提供国际化的消息访问
- 资源访问,如URL和文件
- 事件传播特性,即支持aop特性
- 载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层
ApplicationContext:是IOC容器另一个重要接口, 它继承了BeanFactory的基本功能, 同时也继承了容器的高级功能,如:MessageSource(国际化资源接口)、ResourceLoader(资源加载接口)、ApplicationEventPublisher(应用事件发布接口)等。
- 二者区别
BeanFactroy采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化,这样,我们就不能发现一些存在的Spring的配置问题。而ApplicationContext则相反,它是在容器启动时,一次性创建了所有的Bean。这样,在容器启动时,我们就可以发现Spring中存在的配置错误。 相对于基本的BeanFactory,ApplicationContext 唯一的不足是占用内存空间。当应用程序配置Bean较多时,程序启动较慢。
BeanFacotry延迟加载,如果Bean的某一个属性没有注入,BeanFacotry加载后,直至第一次使用调用getBean方法才会抛出异常;而ApplicationContext则在初始化自身是检验,这样有利于检查所依赖属性是否注入;所以通常情况下我们选择使用 ApplicationContext。应用上下文则会在上下文启动后预载入所有的单实例Bean。通过预载入单实例bean ,确保当你需要的时候,你就不用等待,因为它们已经创建好了。
beanFactory主要是面对与 spring 框架的基础设施,面对 spring 自己。而 Applicationcontex 主要面对与 spring 使用的开发者。基本都会使用 Applicationcontex 并非 beanFactory。
- 二者作用
- BeanFactory负责读取bean配置文档,管理bean的加载,实例化,维护bean之间的依赖关系,负责bean的声明周期。
- ApplicationContext除了提供上述BeanFactory所能提供的功能之外,还提供了更完整的框架功能:
- 国际化支持
- 资源访问:Resource rs = ctx. getResource(“classpath:config.properties”), “file:c:/config.properties”
- 事件传递:通过实现ApplicationContextAware接口
- 常用的获取ApplicationContext
- FileSystemXmlApplicationContext
- ClassPathXmlApplicationContext
- WebApplicationContext
1.4.13 Spring中有哪些注入方式?
- setXxx注入
- 构造器注入
- 静态工厂的方法注入
1.4.14 Spring支持的几种bean的作用域
Spring框架支持以下五种bean的作用域
- singleton:单例模式,在整个Spring IoC容器中,使用singleton定义的Bean将只有一个实例。
- prototype:原型模式,每次通过容器的getBean方法获取prototype定义的Bean时,都将产生一个新的Bean实例。
- request:对于每次HTTP请求,使用request定义的Bean都将产生一个新实例,即每次HTTP请求将会产生不同的Bean实例。只有在Web应用中使用Spring时,该作用域才有效
- session:对于每次HTTP Session,使用session定义的Bean都将产生一个新实例。同样只有在Web应用中使用Spring时,该作用域才有效。
- globalsession:每个全局的HTTP Session,使用session定义的Bean都将产生一个新实例。典型情况下,仅在使用portlet context的时候有效。同样只有在Web应用中使用Spring时,该作用域才有效。
如果不指定Bean的作用域,Spring默认使用singleton作用域。Java在创建Java实例时,需要进行内存申请;销毁实例时,需要完成垃圾回收,这些工作都会导致系统开销的增加。因此,prototype作用域Bean的创建、销毁代价比较大。而singleton作用域的Bean实例一旦创建成功,可以重复使用。因此,除非必要,否则尽量避免将Bean被设置成prototype作用域。
1.4.14 Spring框架中的单例bean是线程安全的吗?
单列整个容器只有一个实例,每次调用都是那一个实例,所以线程不安全。
1.4.15什么是Spring的内部bean
当一个 bean 仅被用作另一个 bean 的属性时,它能被声明为一个内部 bean,为了定义内部Bean,在Spring 的 基于XML的 配置元数据中,可以在 <property/>或 <constructor-arg/> 元素内使用<bean/> 元素,内部bean通常是匿名的,它们的Scope一般是prototype。
<bean id="userService" class="com.qf.service.impl.UserServiceImpl"> <property name="userDao"> <!-- 内部Bean --> <bean class="com.qf.dao.impl.UserDaoImpl"></bean> </property> </bean> |
1.4.17 在 Spring中如何注入一个java集合?
被注入的Bean定义
public class UserServiceImpl implements IUserService {
private IUserDao userDao;
private String username;
private Integer age; // 自动类型转换
private List<Integer> ids;
private Map<String, Object> map;
private Properties properties; //属性对象 } |
Spring中配置如下:
<bean id="userService" class="com.qf.service.impl.UserServiceImpl"> <!-- 注入对象 --> <property name="userDao" ref="userDao"></property>
<!-- 注入字符串 --> <property name="username" value="admin"></property>
<!-- 注入Integer类型 --> <property name="age" value="22"></property>
<!-- 注入list --> <property name="ids"> <list> <value>1</value> <value>2</value> <value>3</value> <!-- list中装对象的情况 --> <!-- <ref bean="beanId"/> --> </list> </property>
<!-- 注入map --> <property name="map"> <map> <entry key="name" value="admin"></entry> <entry key="password" value="123"></entry> <!-- value-ref:指向的一个对象 --> <entry key="user" value-ref="userDao"></entry> </map> </property>
<!-- 注入属性对象 --> <property name="properties"> <props> <prop key="username">root</prop> <prop key="url">jdbc:mysql://localhost:3306/1805_hibernate</prop> </props> </property> </bean> |
1.4.18 什么是bean的自动装配?
无须在Spring配置文件中描述javaBean之间的依赖关系(如配置<property>、<constructor-arg>)。IOC容器会自动建立javabean之间的关联关系。可以通过<bean>标签中的autowire属性来完成自动装配。Autowire属性值有五种分别如下:
1)no:默认的方式是不进行自动装配,通过显式设置ref 属性来进行装配。
2)byName:根据属性名称自动装配,容器试图匹配、装配和该bean的属性具有相同名字的bean,如果找到就装配,没有找到就不装配。
3)byType::按数据类型自动装配, 容器试图匹配、装配和该bean的属性具有相同类型的bean。如果有多个bean符合条件,则抛出错误。
4)constructor:这个方式类似于 byType, 但是要提供给构造器参数,如果没有确定的带参数的构造器参数类型,将会抛出异常。
5)default:按照父节点<beans>中的自动装配机制装配。
1.4.19什么是基于注解的容器配置
相对于XML文件,注解型的配置依赖于通过字节码元数据装配组件,而非尖括号的声明。开发者通过在相应的类,方法或属性上使用注解的方式,直接组件类中进行配置,而不是使用xml表述bean的装配关系。
Xml配置VS注解
配置方式 | 优点 | 缺点 |
xml配置 | 1. XML配置方式进一步降低了耦合,使得应用更加容易扩展,即使对配置文件进一步修改也不需要工程进行修改和重新编译。
2. 在处理大的业务量的时候,用XML配置应该更加好一些。因为XML更加清晰的表明了各个对象之间的关系,各个业务类之间的调用。同时spring的相关配置也能一目了然。 | 配置文件读取和解析需要花费一定的时间,配置文件过多的时候难以管理,无法对配置的正确性进行校验,增加了测试难度。 |
注解配置 | 1. 在class文件中,可以降低维护成本,annotation的配置机制很明显简单
2. 不需要第三方的解析工具,利用java反射技术就可以完成任务
3. 编辑期可以验证正确性,差错变得容易 4. 提高开发效率 | 1. 如果需要对于annotation进行修改,那么要重新编译整个工程 。 2. 业务类之间的关系不如XML配置那样容易把握。 3. 如果在程序中annotation比较多,直接影响代码质量,对于代码的简洁度有一定的影响 |
1.4.20怎样开启注解装配?
注解装配默认是不开启的,如果要开启注解装配需要在xml中配置如下节点
<context:component-scan base-package="com.qf"/> |
1.4.21 在Spring框架中如何更有效地使用JDBC?
Spring中提供了对JDBC的支持,可以方便的操作数据库。JdbcTemplate和JdbcDaoSupport都是Spring提供操作数据库的类。这些类提供了很多便利的方法和解决诸如把数据库数据转变成基本数据类型或对象的方法。让使用者用来简单,高效。
1.4.22 使用 Spring 通过什么方式访问 Hibernate?
Spring中提供了如下两种方式来访问Hibenrate:
- HibernateTemplate
该类中定义了大量利用Hibnerate操作数据库的方法,还可以去获取session对象。使用的时候需要注入sessionFactory。
- HibernateDAOSupport
该类是一个抽象类,在HibernateTemplate基础之上做了扩展,里面还是用HibernateTemplate来操作数据库,我们在使用的时候可以注入sessionFactory或者HibernateTemplate。注入sessionFactory它会自动创建一个HibernateTemplate,注入HibernateTemplate它会直接使用。
1.4.23 Spring 支持的 ORM 框架有哪些?
Spring支持的ORM框架有:
Hibernate、iBatis/MyBatis、JPA(SpringDataJPA) 、TopLink、JDO (Java Data Objects)、OJB。
- JDO
Java数据对象(Java Data Objects,JDO)是一个应用程序接口(API),它是Java程序员能够间接地访问数据库,也就是说,不需使用直接的结构化查询语言(SQL)语句。JDO是作为Java数据库连接(JDBC)的一个补充来介绍的,而JDBC是一个支持使用SOL语句对流行的数据库程序进行访问的接口。有了 JDO,程序员就可以使用类来定义数据对象,然后支撑程序就会根据类的定义来管理对给定数据库的实际的数据访问。
JDO是以Sun公司为首所制定的Java Community Process(Java标准制定组织,JCP)的一部分。JDBC仍然保留使用是因为它比起JDO允许程序员在数据库访问上有更大的控制权。除JDO和 JDBC外的另一个选择是Enterprise JavaBeans (EJB)。
- OJB
对象关系桥(OJB)是一种对象关系映射工具,它能够完成从Java对象到关系数据库的透明存储。
OJB使用基于XML的对象关系映射。映射发生在一个动态的元数据层,使得可以通过一个简单的元对象协议(MOP)在运行时就可以操作元数据层去改变存储内核。 OJB提供了高级的O/R,如对象缓冲,通过虚拟代理实现后期实例化,配置事务隔离层实现分布式的锁治理,同时支持多种锁治理。
OJB与J2EE应用服务器进行了很好的集成。支持数据源的JNDI查找;与JTA和JCA全面集成;能够在jsps,Servlet和SessionBeans中使用;OJB也为实体Bean治理Bean(BMP)提供了非凡的支持。
OJB提供了良好的可配置性,和集成机制,答应用户使用预定义的组件或是自己实现扩展组件。
- TopLink
Toplink是Oracle公司捐献给开源社区的,是最早的ORM映射框架之一,也是对JPA规范支持最好的一个框架。TopLink,是位居第一的Java对象关系可持续性体系结构,原署WebGain公司的产品,后被Oracle收购,并重新包装为Oracle AS TopLink。TOPLink为在关系数据库表中存储 Java 对象和企业 Java 组件 (EJB) 提供高度灵活和高效的机制。TopLink 为开发人员提供极佳的性能和选择,可以与任何数据库、任何应用服务器、任何开发工具集和过程以及任何 J2EE 体系结构协同工作。
1.4.24 请解释下Spring中AOP思想
AOP(Aspect Oriented Programming),即面向切面编程,可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。OOP引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。不过OOP允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能。日志代码往往横向地散布在所有对象层次中,而与它对应的对象的核心功能毫无关系对于其他类型的代码,如安全性、异常处理和透明的持续性也都是如此,这种散布在各处的无关的代码被称为横切(cross cutting),在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。
AOP技术恰恰相反,它利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。
使用"横切"技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事物。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。
简单一句话概括:编译期业务主体和系统服务是分开的,在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想。
AOP术语
1.通知(Advice)
就是你想要的功能,也就是常见的 安全,事物,日志等。你给先定义好把,然后在想用的地方用一下。
2.连接点(JoinPoint)
这个更好解释了,就是spring允许你使用通知的地方,那可真就多了,基本每个方法的前,后(两者都有也行),或抛出异常时都可以是连接点,spring只支持方法连接点.其他如aspectJ还可以让你在构造器或属性注入时都行,不过那不是咱关注的,只要记住,和方法有关的前前后后(抛出异常),都是连接点。
3.切入点(Pointcut)
上面说的连接点的基础上,来定义切入点,你的一个类里,有15个方法,那就有几十个连接点了对把,但是你并不想在所有方法附近都使用通知(使用叫织入,以后再说),你只想让其中的几个,在调用这几个方法之前,之后或者抛出异常时干点什么,那么就用切点来定义这几个方法,让切点来筛选连接点,选中那几个你想要的方法。
4.切面(Aspect)
切面是通知和切入点的结合。现在发现了吧,没连接点什么事情,连接点就是为了让你好理解切点,搞出来的,明白这个概念就行了。通知说明了干什么和什么时候干(什么时候通过方法名中的before,after,around等就能知道),而切入点说明了在哪干(指定到底是哪个方法),这就是一个完整的切面定义。
5.引入(introduction)
允许我们向现有的类添加新方法属性。这不就是把切面(也就是新方法属性:通知定义的)用到目标类中吗
6.目标(target)
引入中所提到的目标类,也就是要被通知的对象,也就是真正的业务逻辑,他可以在毫不知情的情况下,被咱们织入切面。而自己专注于业务本身的逻辑。
7.代理(proxy)
怎么实现整套aop机制的,都是通过代理,这个一会给细说。
8.织入(weaving)
把切面应用到目标对象来创建新的代理对象的过程。有3种方式,spring采用的是运行时,为什么是运行时,后面解释。
Spring AOP 实现机制
Spring AOP使用动态代理技术在运行期织入增强的代码,使用了两种代理机制,一种是基于JDK的动态代理,另一种是基于CGLib的动态代理。
Jdk实现动态代理
定义对象接口:
public interface IUserService { public void add(); } |
定义实现类
public class UserServiceImpl implements IUserService { @Override public void add() { // 做真实业务主体 System.out.println("UserServiceImpl.add()"); } } |
定义动态代理的Handler
public class ProxyHanlder implements InvocationHandler {
// 1.系统服务 private TransactionManager tx; private TimeManager tm;
// 2.目标对象 private Object target;
public ProxyHanlder(TransactionManager tx, TimeManager tm, Object target) { this.tx = tx; this.tm = tm; this.target = target; }
/** * proxy:代理类 * method:目标对象方法 * args:目标对对象方法的参数 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 1.系统服务 tm.start(); tx.beign();
// 2.调用真实业务主体 Object result = method.invoke(target, args);
// 2.系统服务结束 tx.commit(); tm.end(); return result; } } |
测试
@Test public void testAdd2() {
// 1.创建目标对象 IUserService userService = new UserServiceImpl();
// 2.增强 TransactionManager tx = new TransactionManager(); TimeManager tm = new TimeManager();
// 3.创建代理 ClassLoader classLoader = userService.getClass().getClassLoader(); // 目标对象类加载器 Class<?>[] interfaces = userService.getClass().getInterfaces(); // 目标对象接口 ProxyHanlder proxyHanlder = new ProxyHanlder(tx, tm, userService); // InvocationHandler IUserService userServiceProxy = (IUserService)Proxy.newProxyInstance(classLoader, interfaces, proxyHanlder);
// 3.调用方法 userServiceProxy.add();
} |
注意: JDK动态代理有一个限制,即它只能为接口创建代理实例。
CGLIB实现动态代理
CGLib采用非常底层的字节码技术,可以在运行时为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用。
以上面的例子为例,目标对象的接口和实现类不变,只需要改变动态代理的handler即可,需改代码如下:
public class ProxyHanlder implements MethodInterceptor { // 1.系统服务 private TransactionManager tx; private TimeManager tm; public ProxyHanlder(TransactionManager tx, TimeManager tm) { this.tx = tx; this.tm = tm; } /** * obj:目标对象 * method:目标对象的中执行的方法 * args:方法的参数 * proxy:代理 */ @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { // 1.系统服务 tm.start(); tx.beign(); // 2.调用目标对象 Object result = proxy.invokeSuper(obj, args); // 调用父类的方法 tx.commit(); tm.end(); return result; } } |
测试
@Test public void testAdd2() { // 1.创建目标对象 IUserService userService = new UserServiceImpl(); // 2.增强 TransactionManager tx = new TransactionManager(); TimeManager tm = new TimeManager(); // 3.创建代理 Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(userService.getClass()); // 设置父类(目标对象) enhancer.setCallback(new ProxyHanlder(tx, tm)); IUserService userServiceProxy =(IUserService) enhancer.create(); // 创建代理类 // 3.调用方法 userServiceProxy.add(); } |
注意: CGLIB动态代理有一个限制,不能给final修饰的类创建代理。
之后对两者做了一个效率对比,在自己本机上通过System.nanoTime()对两者做了记录,结果如下。
No | JDK动态代理 | CGLiib |
创建代理对象时间 | 720 1394 | 1 3473 7007 |
代理对象执行方法时间 | 97 7322 | 15 2080 |
一个创建花费时间长,一个执行时间长。
AOP的引用场景
事务,日志,缓存,权限,异常处理等。
1.4.25 在 Spring AOP 中,关注点和横切关注的区别是什么?
关注点是系统中的一个行为,也就是真实业务主体,横切关注就是系统服务。
1.4.26 Spring哪几种类型?
- before:前置通知,在一个方法执行前被调用。
- after: 在方法执行之后调用的通知,无论方法执行是否成功。
- after-returning: 仅当方法成功完成后执行的通知。
- after-throwing: 在方法抛出异常退出时执行的通知。
- around: 在方法执行之前和之后调用的通知。
- Shiro
1.5.1简单介绍一下 Shiro 框架
Apache Shiro 是 Java 的一个安全(权限)框架。 Shiro 可以非常容易的开发出足够好的应用,其不仅可以用在 JavaSE 环境,也可以用在 JavaEE 环境。使用Shiro的人越来越多,因为它相当简单,对比Spring Security,可能没有Spring Security做的功能强大,但是在实际工作时可能并不需要那么复杂的东西,所以使用小而简单的Shiro就足够了。
Shiro 可以完成:认证、授权、加密、会话管理、与Web 集成、缓存 等。
1.5.2 Shiro三个核心组件
Subject :正与系统进行交互的人,或某一个第三方服务。所有 Subject 实例都被绑定到(且这是必须的)一个SecurityManager 上。
SecurityManager:Shiro 架构的心脏,用来协调内部各安全组件,管理内部组件实例,并通过它来提供安全管理的各种服务。当 Shiro 与一个 Subject 进行交互时,实质上是幕后的 SecurityManager 处理所有繁重的 Subject 安全操作。
Realms :本质上是一个特定安全的 DAO。当配置 Shiro 时,必须指定至少一个 Realm 用来进行身份验证和/或授权。Shiro 提供了多种可用的 Realms 来获取安全相关的数据。如关系数据库(JDBC),INI 及属性文件等。可以定义自己 Realm 实现来代表自定义的数据源
1.5.3 Shiro 的四大核心部分
Authentication(身份验证):简称为“登录”,即证明用户是谁。
Authorization(授权):访问控制的过程,即决定是否有权限去访问受保护的资源。
Session Management(会话管理):管理用户特定的会话,即使在非 Web 或 EJB 应用程序。
Cryptography(加密):通过使用加密算法保持数据安全
1.5.4 shiro能解决什么问题?
- 认证:验证用户的身份
- 授权:对用户执行访问控制:判断用户是否被允许做某事
- 会话管理:在任何环境下使用 Session API,即使没有 Web 或EJB 容器。
- 加密:以更简洁易用的方式使用加密功能,保护或隐藏数据防止被偷窥
- Realms:聚集一个或多个用户安全数据的数据源
1.5.5 Shiro 的四种权限控制方式
1.编程式
通过写if/else授权代码块完成:
Subject subject = SecurityUtils.getSubject(); if(subject.hasRole("admin")) { //有权限 } else { //无权限 } |
2.注解式
通过在执行的Java方法上放置相应的注解完成:
@RequiresRoles("admin") // 拥有admin角色才能访问该方法 public void hello() { //有权限 }
// 拥有user:update权限才能方法该方法 @RequiresPermissions("user:update") @RequestMapping(value = "/update") public String update() { System.out.println("UserController.update()"); return "ok"; } |
如果要使用注解使用,首先要在配置文件中开启注解(SpringMVC配置文件中) <!-- 启动shiro注解 --> <aop:config proxy-target-class="true"></aop:config> <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"> <property name="securityManager" ref="securityManager" /> </bean> |
3. 页面标签权限控制
在JSP/GSP页面通过相应的标签完成:
<shiro:hasRole name="admin"> <!— 有权限 —> </shiro:hasRole> |
4.url 级别权限控制
<property name="filterChainDefinitions"> <value> /admin/**=roles[admin] // 用户必须拥有admin角色才可以访问 /user/**=perms["user:create"] //用户必须拥有user:create权限才可以访问 </value> </property> |
1.5.6 Shiro授权流程
授权也叫访问控制,即在应用中控制谁能访问哪些资源(如访问页面/编辑数据/页面操作等)。在授权中需了解的几个关键对象:主体(Subject)、资源(Resource)、权限(Permission)、角色(Role)。
主体:即访问应用的用户,在Shiro中使用Subject代表该用户。用户只有授权后才允许访问相应的资源。
资源:在应用中用户可以访问的任何东西,比如访问JSP页面、查看/编辑某些数据、访问某个业务方法、打印文本等等都是资源。用户只要授权后才能访问。
权限:安全策略中的原子授权单位,通过权限我们可以表示在应用中用户有没有操作某个资源的权力。
角色:在应用中代表着用户的身份。
授权流程如下:
- 首先调用Subject.isPermitted*/hasRole*接口,其会委托给SecurityManager,而SecurityManager接着会委托给Authorizer;
- Authorizer是真正的授权者,如果我们调用如isPermitted(“user:view”),其首先会通过PermissionResolver把字符串转换成相应的Permission实例;
- 在进行授权之前,其会调用相应的Realm获取Subject相应的角色/权限用于匹配传入的角色/权限;
- Authorizer会判断Realm的角色/权限是否和传入的匹配,如果有多个Realm,会委托给ModularRealmAuthorizer进行循环判断,如果匹配如isPermitted*/hasRole*会返回true,否则返回false表示授权失败。
ModularRealmAuthorizer进行多Realm匹配流程:
1、首先检查相应的Realm是否实现了实现了Authorizer;
2、如果实现了Authorizer,那么接着调用其相应的isPermitted*/hasRole*接口进行匹配;
3、如果有一个Realm匹配那么将返回true,否则返回false。
1.5.7 Shiro认证流程
在shiro中,用户需要提供principals (身份)和credentials(证明)给shiro,从而应用能验证用户身份。
principals:身份,即主体的标识属性,可以是任何东西,如用户名、邮箱等,唯一即可。一个主体可以有多个principals,但只有一个Primary principals,一般是用户名/密码/手机号。
credentials:证明/凭证,即只有主体知道的安全值,如密码/数字证书等。
最常见的principals和credentials组合就是用户名/密码了。接下来先进行一个基本的身份认证。
认证流程如下:
- 首先调用Subject.login(token)进行登录,其会自动委托给Security Manager,调用之前必须通过SecurityUtils. setSecurityManager()设置;
- SecurityManager负责真正的身份验证逻辑;它会委托给Authenticator进行身份验证;
- Authenticator才是真正的身份验证者,Shiro API中核心的身份认证入口点,此处可以自定义插入自己的实现;
- Authenticator可能会委托给相应的AuthenticationStrategy进行多Realm身份验证,默认ModularRealmAuthenticator会调用AuthenticationStrategy进行多Realm身份验证;
- Authenticator会把相应的token传入Realm,从Realm获取身份验证信息,如果没有返回/抛出异常表示身份验证失败了。此处可以配置多个Realm,将按照相应的顺序及策略进行访问。
- 框架部分
-
1.1 Hibernate
1.1.1说一下Hibernate开发流程
- Hibernate开发环境搭建
- 导入Hibernate相关jar包即数据库的驱动包
- 新建数据库
- 在数据库中新建一个表用来做测试
- 编写POJO类
- 配置文件
- Hibernate.cfg.xml(hibernate核心配置文件),主要配置数据库连接信息,连接池信息,方言,缓存,映射文件等。
- User.hbm.xml(映射文件),主要是配置对象和表中的映射信息
- 编写SessionFactory工具类
- 通过工具类获取SessionFactory
- 通过SessionFactory创建Session
- 获取到Session意味着和数据库建立连接
- 开启事务
- 操作数据库
- 提交事务
- 关闭session
- Session关闭后连接释放到连接池
- Hibernate中对象的三种状态
- Session关闭后连接释放到连接池
-
- 瞬时状态(Transient)/临时状态/自由状态:
-
对于刚创建的一个对象,如果session中和数据库中都不存在该对象,那么该对象就是瞬时对象。
- 持久(Persistent)
-
已经持久化(数据库中有)并且已经交个session托管(session缓存中有),持久化对象在事务提交的时会把session中托管的对象和持久化对象进行比较,如果不同就发送一条update语句,否则不会发送update语句。
- 脱管(Detached)
-
数据库存在该对象,但是该对象又没有被session所托管。
-
-
- Hibernate缓存机制
-
-
Hibernate缓存有3中,一级缓存,二级缓存,查询缓存。
- 一级缓存
- Hibernate的一级缓存是Session的缓存,Session内置的缓存是不能被卸载的
- 的表示号)
- 一级缓存存放的是数据库数据的拷贝。
- 二级缓存
- Hibernate的二级缓存是SessionFactory级别的缓存,所有session共享一个二级缓存。
- Hibernate对二级缓存只是提供了一个接口,具体的需要第三方来实现,比如Ehcache
- Hibernate的二级缓存默认是不启用的,二级缓存存放元数据和预定义的SQL。
- 查询缓存
- 查询缓存主要是针对HQL的查询
- 查询缓存要依赖于二级缓存
- 查询缓存中缓存的是预定义的SQL语句
-
- Hibernate中的查询方式有哪些?
-
-
Hibernate中有四中查询方式,分别是主键查询,HQL,QBC,原生态SQL查询。
- 主键查询
-
User user = (User) session.get(User.class, userId); // 立即加载
User user1 = (User) session.load(User.class, userId); // 支持懒加载
- HQL查询
-
Query query2 = session.createQuery("from User u where u.id = :id");
需要注意的是HQL是面向对象的查询方式,里面写的都是对象或属性是区分大小写的。
- 原生态的SQL查询
-
SQLQuery sqlQuery = session.createSQLQuery("select * from t_user u where u.id = :id")
原生态的SQL查询,适合使用复杂的查询,或者不想使用HQL或者criteria查询,可以使用本地SQL查询,缺点是不能跨越数据库,一般不适用,除非遇到复杂的sql语句才使用。
- Criteria查询
-
Criteria criteria = session.createCriteria(User.class);
List<User> list = criteria.list();
Criteria查询叫做条件查询也称QBC(Query By Criteria)查询,它完全面向对象的查询方式,里面把排序和分组都封装了方法。
-
-
- mybatis和hibernate的区别
-
- MyBatis
- 优点
- 可以由开发者灵活的控制SQL语句,减少查询字段。
- 所有的SQL语句都放在统一配置文件中方便维护,管理,减少SQL和程序的耦合
- MyBatis是一个半自动化的ORM框架,对象数据以及对象实际关系仍然需要通过手写sql来实 现和管理
- 支持动态编写SQL
- 缺点
- 数据库移植性很弱,不同的数据库要编写不同的SQL语句
- DAO层的方法不支持方法的重载
- 不支持级联更新和删除
-
- Hibernate
- 优点
-
- Hibernate中的方言可以方便的做到数据库的移植
- Hibernate中提供了一级缓存,二级缓存,查询缓存来提高查询效率,MyBatis中的缓存不佳
- Hibernate是个全自动化科技,Dao层的开发比较简单,直接调用save方法即可。
-
- 缺点
-
- 多表关联的时候比较复杂,使用成本不低
- SQL语句都是自动生成,对于SQL的优化,修改比较困难
-
-
- 总结
-
两个框架各有千秋,如果项目中很少存在多表关联查询(比如10张表以上)那可以选择使用Hiberate,否则还是选择MyBatis,具体选择哪个框架还是要根据项目来定。
mybatis:小巧、方便、高效、简单、直接、半自动
hibernate:强大、方便、高效、复杂、绕弯子、全自动
-
-
- Hibernate和 JDBC优缺点对比
-
- 相同点
- 两者都是JAVA的数据库操作中间件。
- 两者对于数据库进行直接操作的对象都不是线程安全的,都需要及时关闭。
- 两者都可以对数据库的更新操作进行显式的事务处理。
- 不同点
- 使用的SQL语言不同:JDBC使用的是基于关系型数据库的标准SQL语言,Hibernate使用的是HQL(Hibernate query language)语言
- 操作的对象不同:JDBC操作的是数据,将数据通过SQL语句直接传送到数据库中执行,Hibernate操作的是持久化对象,由底层持久化对象的数据更新到数据库中。
- 数据状态不同:JDBC操作的数据是“瞬时”的,变量的值无法与数据库中的值保持一致,而Hibernate操作的数据是可持久的,即持久化对象的数据属性的值是可以跟数据库中的值保持一致的。
- JDBC与Hibernate读取性能
- JDBC仍然是最快的访问方式,不论是Write还是Read操作,都是JDBC快。
- Hibernate使用uuid.hex构造主键,性能稍微有点损失,但是不大。
- Create操作,JDBC在使用批处理的方式下速度比Hibernate快,使用批处理方式耗用JVM内存比不使用批处理方式要多得多。
- 读取数据,Hibernate的Iterator速度非常缓慢,因为他是每次next的时候才去数据库取数据,这一点从观察任务管理器的java进程占用内存的变化也可以看得很清楚,内存是几十K几十K的增加。
- 读取数据,Hibernate的List速度很快,因为他是一次性把数据取完,这一点从观察任务管理器的java进程占用内存的变化也可以看得很清楚,内存几乎是10M的10M的增加。
- JDBC读取数据的方式和Hibernate的List方式是一样的(这跟JDBC驱动有很大关系,不同的JDBC驱动,结果会很不一样),这 从观察java进程内存变化可以判断出来,由于JDBC不需要像Hibernate那样构造一堆Cat对象实例,所以占用JVM内存要比 Hibernate的List方式大概少一半左右。
- Hibernate的Iterator方式并非一无是处,它适合于从大的结果集中选取少量的数据,即不需要占用很多内存,又可以迅速得到结果。另外Iterator适合于使用JCS缓冲。
-
- Hibernate的ORM原理和实现
-
- Hibernate的ORM原理和实现
-
ORM的全称是Object Relational Mapping,即对象关系映射。它的实现思想就是将关系数据库中表的数据映射成为对象,以对象的形式展现,这样开发人员就可以把对数据库的操作转化为对这些对象的操作。因此它的目的是为了方便开发人员以面向对象的思想来实现对数据库的操作。
- Hibernate是如何实现映射的
-
在使用Hibernate实现ORM功能的时候,主要的文件有:映射类(*.java)、映射文件(*.hbm.xml)以及数据库配置文件(*.cfg.xml),它们各自的作用如下:
- 映射类
-
它的作用是描述数据库表的结构,表中的字段在类中被描述成属性,将来就可以实现把表中的记录映射成为该类的对象。
- 映射文件
-
它的作用是指定数据库表和映射类之间的关系,包括映射类和数据库表的对应关系、表字段和类属性类型的对应关系以及表字段和类属性名称的对应关系等。
- 数据库配置文件
-
它的作用是指定与数据库连接时需要的连接信息,比如连接哪中数据库、登录用户名、登录密码以及连接字符串等。
-
-
- get和load的区别
-
- get
- 调用后立即发送SQL语句查询,返回的实体对象。
- 查询一个不存在的数据返回null
- get的查询顺序是先到一级缓存à二级缓存à数据库
- load
- 是懒加载,返回的是个代理对象
- 查询一个不存在的数据抛出异常
- 调用load后不会发送sql语句,返回的对象是个代理的,这个对象中只有id属性有值,只有调用该对象的其他属性时才会发送sql语句查询。
- Load默认是懒加载的机制,这种机制是可以通过设置class节点中的lazy属性修改。
-
- 如何进行Hibernate优化
-
-
很多时候我们是在效率与安全/准确性上找一个平衡点,无论如何,优化都不是一个纯技术的问题,你应该对你的应用和业务特征有足够的了解,一般的,优化方案应在架构设计期就基本确定,否则可能导致没必要的返工,致使项目延期,而作为架构师和用Hibernate与用Jdbc性能相差十几倍很正常,如果不及早调整,很可能影响整个项目的进度。大体上,对于Hibernate性能调优的主要考虑点如下:
- 数据库设计
- 降低关联的复杂性
- 尽量不使用联合主键
- ID的生成机制,不同的数据库所提供的机制并不完全一样
- 适当的冗余数据,不过分追求高范式
- HQL优化
-
HQL如果抛开它同Hibernate本身一些缓存机制的关联,HQL的优化技巧同普通的SQL优化技巧一样。
- 主配置
- 查询缓存,同下面讲的缓存不太一样,它是针对HQL语句的缓存,即完全一样的语句再次执行时可以利用缓存数据。但是,查询缓存在一个交易系统(数据变更频繁,查询条件相同的机率并不大)中可能会起反作用:它会白白耗费大量的系统资源但却难以派上用场。
- batch_size,同JDBC的相关参数作用类似,参数并不是越大越好,而应根据业务特征去设置
- 生产系统中,切记要关掉SQL语句打印。
- 缓存
- SESSION缓存:在一个Hibernate Session有效,这级缓存的可干预性不强,大多于HIBERNATE自动管理,但它提供清除缓存的方法,这在大批量增加/更新操作是有效的。比如,同时增加十万条记录,按常规方式进行,很可能会发现OutofMemeroy的异常,这时可能需要手动清除这一级缓存:Session.evict以及Session.clear
- 缓存有几种形式,可以在映射文件中配置:read-only(只读,适用于很少变更的静态数据/历史数据),nonstrict-read-write,read-write(比较普遍的形式,效率一般),transactional(JTA中,且支持的缓存产品较少)
- 延迟加载
- 实体延迟加载:通过使用动态代理实现
- 集合延迟加载:通过实现自有的SET/LIST
- 属性延迟加载:通过load
- 事务控制
- 如果不涉及多个事务管理器事务的话,不需要使用JTA,只有Jdbc的事务控制就可以。
- 使用标准的SQL事务隔离级别
- 批量操作
- 批量操作的时候直接使用JDBC
-
项目经理,还要面对开发人员可能的抱怨,必竟,我们对用户需求更改的控制力不大,但技术/架构风险是应该在初期意识到并制定好相关的对策。应用层的缓存只是锦上添花,永远不要把它当救命稻草,应用的根基(数据库设计,算法,高效的操作语句,恰当API的选择等)才是最重要的。
-
-
- 什么是Hibernate延迟加载
-
-
延迟加载,也叫懒加载,它是Hibernate为提高程序执行效率而提供的一种机制,即只有真正使用该对象的数据时才会创建。
Hibernate中主要是通过代理机制来实现延迟加载。它的具体过程:Hibernate丛数据库获取某一个对象数据时、获取某一个对象的集合属性值时,或获取某一个对象所关联的另一个对象时,由于没有使用该对象的数据,hibernate并不是数据库加载真正的数据,而只是为该对象创建一个代理对象(cglib生成)来代表这个对象,这个对象上的所有属性都是默认值;只有在真正需要使用该对象的数据时才创建这个真实对象,真正从数据库中加载它的数据,这样在某些情况下,就可以提高查询效率。
举个例子:加载某对象X时,并不会立即从数据库中返回该对象所有的属性值,而是采用代理机制生成X对象的代理,当访问该代理对象的属性时才从数据库中加载该属性值。
Hibernate的延迟加载提供了三大方面其中分别是:实体关联、集合类、属性的延迟加载。
-
-
- NoSession的问题原因及解决方案
-
- 问题产生的原因
-
因为Hibernate中调用load懒加载的查询,当调完load后session就关闭了,因为我们的session只是配置到了dao层,表现层获取不到,所以在表现层调用的时候session就已经关闭,就爬出了NoSession异常。
- 解决方案
- 可以配置关联关系时设置lazy属性=false,立即加载方法,也可以提前使用数据,使其自动加载。
- 利用Spring提供的OpenSessionInViewFilter解决no session问题
- 说说session缓存的作用
- 减少访问数据库的频率。应用程序从缓存中读取持久化对象的速度显然要比数据库中检索数据的速度块。从而session缓存可以提高查询效率。
- 事务提交的时候会隐式调用flush(),保证了缓存中的数据和数据库数据同步。
- 当缓存中持久化对象的状态发生改变时,Session不会立即执行相关的SQL语句,这使得Session能够把几条相关的SQL语句合并为一条SQL语句,以减少数据库的访问次数,提高访问效率。
-
- session的清理和清空有什么区别
-
- 清理缓存
- 清理缓存调用的是session.flush()方法
- 清理缓存是指按照缓存中对象的状态的变化来同步更新数据库,但不清空缓存
- 清空缓存
- 清空缓存调用的是session.clear()方法
- 清空是把session的缓存置空,但不同步更新数据库。
- 清空缓存后也就意味着关闭session
- Relish
- 根据数据库的变化来同步更新session缓存
- Hibernate中session的特点有哪些?
- 根据数据库的变化来同步更新session缓存
- 线程不安全,每个线程都应该从一个SessionFactory获取自己的session实例。
- Session的主要功能是提供对映射的实体类实例的创建,读取和删除操作。
- 如果其持久化对象类是可序列化的,则Session实例也是可序列化的。
-
- Hibernate三种检索策略的优缺点对比
-
- 立即检索
-
采用立即检索策略,会将被检索的对象,以及和这个对象关联的一对多对象都加载到缓存中。Session的get方法就使用的立即检索策略。
- 优点:频繁使用的关联对象能够被加载到缓存中。
- 缺点
-
- 占用内存。
- Select语句过多。
-
-
- 延迟检索
-
采用延迟检索策略,就不会加载关联对象的内容。直到第一次调用关联对象时,才去加载关联对象。在不涉及关联类操作时,延迟检索策略只适用于Session的load方法。
在类级别操作时,延迟检索策略,只加载类的OID不加载类的其他属性,只用当第一次访问其他属性时,才回访问数据库去加载内容。(这里使用了CGLIB生成了类的代理类)
在关联级别操作时,延迟检索策略,只加载类本身,不加载关联类,直到第一次调用关联对象时,才去加载关联对象程序模式都是用延迟加载策略。如果需要指定使用延迟加载策略。在配置文件中设置<class>的lazy=true,<set>的lazy=true或extra(增强延迟)<many-to-one>的lazy=proxy和no-proxy。
- 优点:由程序决定加载哪些类和内容,避免了大量无用的sql语句和内存消耗。
- 缺点:在Session关闭后,就不能访问关联类对象了。需要确保在Session.close方法前,调用关联对象。
-
- 迫切左外连接检索
-
采用左外连接检索,能够使用Sql的外连接查询,将需要加载的关联对象加载在缓存中。
<set>fetch设置为join,<many-to-one>的fetch设置为 join。
- 优点:
- 对应用程序完全透明,不管对象处于持久化状态,还是游离状态,应用程序都可以方便的从一个对象导航到与它关联的对象。
- 使用了外连接,select语句数目少。
- 缺点:
-
- 可能会加载应用程序不需要访问的对象,白白浪费许多内存空间。
- 复杂的数据库表连接也会影响检索性能。
-
-
-
- 什么是悲观锁和乐观锁?
-
- 什么是锁
-
为什么需要锁?在多用户环境中,在同一时间可能会有多个用户更新相同的记录,这会产生冲突,这就是著名的并发性问题。而锁就可以解决这个问题,即给我们选定的目标数据上锁,使其无法被其他程序修改。
- 悲观锁
-
悲观锁是指假设并发更新冲突会发生,所以不管冲突是否真的发生,都会使用锁机制。悲观锁会锁住读取的记录,防止其它事务读取和更新这些记录。其它事务会一直阻塞,直到这个事务结束。悲观锁是在使用了数据库的事务隔离功能的基础上,独享占用的资源,以此保证读取数据一致性,避免修改丢失。
数据库悲观锁实现
select * from t_user where id = 1 for update
这条 sql语句锁定了 t_user表中id为1的记录。本次事务提交之前(事务提交时会释放事务过程中的锁),外界无法修改这些记录。
Hibernate的悲观锁,也是基于数据库的锁机制实现。下面的代码实现了对查询记录的加锁。
主键查询加锁:
User user= (User)session.get(User.class, 1, LockOptions.UPGRADE);
Hql查询加锁:
Query query = session.createQuery("from User u where u.id = 1");
query.setLockOptions(LockOptions.UPGRADE);
- 乐观锁
-
总是认为不会产生并发问题,每次去取数据的时候总认为不会有其他线程对数据进行修改,因此不会上锁,但是在更新时会判断其他线程在这之前有没有对数据进行修改,一般会使用版本号机制或CAS操作实现。
乐观锁的工作原理:读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。
Hibernate中实现:表中添加一个version字段,对象中添加一个version属性,在映射文件中建立它们之间的关系就可以实现,这个version字段由hibernate帮我们维护。
-
-
- 什么ORM?
-
-
ORM概念
ORM,即Object-Relational Mapping(对象关系映射),它的作用是在关系型数据库和业务实体对象之间作一个映射,这样,我们在具体的操作业务对象的时候,就不需要再去和复杂的SQL语句打交道,只需简单的操作对象的属性和方法。
先从项目中数据流存储形式这个角度说起.简单拿MVC这种分层模式.来说. Model作为数据承载实体. 在用户界面层和业务逻辑层之间数据实现面向对象的形式传递. 当我们需要通过Controller层分发请求把数据持久化时我们会发现,内存中的面向对象如何持久化成关系型数据中存储一条实际数据记录呢?面向对象是从软件工程基本原则(如耦合、聚合、封装)的基础上发展起来的,而关系数据库则是从数学理论发展而来的. 两者之间是不匹配的.而ORM作为项目中间件形式实现数据在不同场景下数据关系映射. 对象关系映射(Object Relational Mapping,简称ORM)是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术.ORM就是这样而来的。
ORM优缺点
优点:
第一:隐藏了数据访问细节,“封闭”的通用数据库交互,ORM的核心。他使得我们的通用数据库交互变得简单易行,并且完全不用考虑该死的SQL语句。快速开发,由此而来。
第二:ORM使我们构造固化数据结构变得简单易行。在ORM年表的史前时代,我们需要将我们的对象模型转化为一条一条的SQL语句,通过直连或是DB helper在关系数据库构造我们的数据库体系。而现在,基本上所有的ORM框架都提供了通过对象模型构造关系数据库结构的功能。这,相当不错。
缺点:
第一:无可避免的,自动化意味着映射和关联管理,代价是牺牲性能。现在的各种ORM框架都在尝试使用各种方法来减轻这块(LazyLoad,Cache),效果还是很显著的。
第二:面向对象的查询语言(X-QL)作为一种数据库与对象之间的过渡,虽然隐藏了数据层面的业务抽象,但并不能完全的屏蔽掉数据库层的设计,并且无疑将增加学习成本.
第三:对于复杂查询,ORM仍然力不从心。
世上没有驴是不吃草的(又想好又想巧,买个老驴不吃草),任何优势的背后都隐藏着缺点,这是不可避免的。问题在于,我们是否能容忍缺点。
常用的ORM框架
-
- Hibernate全自动需要些hql语句
- MyBatis半自动自己写sql语句,可操作性强,小巧
- EclipseLink 一个可扩展的支持JPA的ORM框架,供强大的缓存功能,缓存支持集群。
- Apache OJB
-
1.2 MyBatis
1.2.1 MyBatis中#和$符号区别
- #{name}
- 被解析为jdbc的预编译语句每个#{}代表一个占位符,比如 where name =?
- 这种方式不会出现SQL注入的问题
- 比较常用
-
举个例子:
<select id="getUserById" resultMap="userMap">
select * from t_User where id = #{id}
</select>
解析后的SQL如下:
- ${name}
- 将内容直接写到SQL语句中,比如:where name = admin
- 这种方式无法防止SLQ注入的问题
- 一般用于动态表或者动态字段排序
-
举个列子:
<select id="getUserById" resultMap="userMap">
select * from t_User where id = ${id}
</select>
解析后的SQL如下:
1.2.2 MyBatis开发流程
- 导入Mybatis相关jar包
- 配置文件
- MyBatis核心配置文件:主要配置连接数据库参数,连接池,插件,Mapper文件等。
- Mapper文件:主要配置调用的sql
- 创建SqlSessionFactoryBuilder
- 通过SqlSessionFactoryBuilder来创建SqlSessionFactory
- 通过SqlSessionFactory创建SqlSession
- 通过SqlSession操作数据库
- 调用sqlSession.commit()提交事务
- 关闭sqlSession.close()关闭session
-
1.2.3 JDBC和MyBatis的对比?
JDBC是Java提供的一个操作数据库的API,我们平时使用jdbc进行编程,需要如下几个步骤。
- 使用jdbc编程需要连接数据库,注册驱动和数据库信息
- 操作Connection,打开Statement对象
- 通过Statement对象执行SQL,返回结果到ResultSet对象
- 使用ResultSet读取数据,然后通过代码转化为具体的POJO对象
- 关闭数据库相关的资源
-
- JDBC缺点
- 工作量比较大,需要连接数据库,然后处理jdbc底层事务,处理数据类型,还需要操作Connection,Statement对象和ResultSet对象最后还需要关闭它们。
- 频繁的创建和释放连接造成资源浪费从而影响性能
- SQL语句写在代码中造成代码不易维护,修改SQL需要改变Java代码。
- 对结果集的解析麻烦,需要手动转化为POJO对象
- 向preparedStatement中设置参数,对占位符号位置和设置参数值,硬编码在java代码中
-
因为JDBC实在不好用,于是出现了ORM对Jdbc进行封装,MyBatis对JDBC的封装很好,几乎可以取代Jdbc。
- MyBatis解决JDBC的以下缺点
- MyBatis使用SqlSessionFactoryBuilder来连接完成JDBC需要代码完成的数据库获取和连接,减少了代码的重复。
- MyBatis可以将SQL代码写入xml中,易于修改和维护。
- MyBatis的mapper会自动将执行后的结果映射到对应的Java对象中
- MyBatis的编码风格统一优雅、性能高,灵活性好。
-
- jdbc,mybatis,hibernate的区别
-
-
- 从层次上看
-
JDBC是较底层的持久层操作方式,而Hibernate和MyBatis都是在JDBC的基础上进行了封装使其更加方便程序员对持久层的操作。
-
- 从功能上看
-
JDBC就是简单的建立数据库连接,然后创建statement,将sql语句传给statement去执行,如果是有返回结果的查询语句,会将查询结果放到ResultSet对象中,通过对ResultSet对象的遍历操作来获取数据;Hibernate是将数据库中的数据表映射为持久层的Java对象,对sql语句进行修改和优化比较困难;MyBatis是将sql语句中的输入参数和输出参数映射为java对象,sql修改和优化比较方便.
-
- 从使用上看
-
如果进行底层编程,而且对性能要求极高的话,应该采用JDBC的方式;如果要对数据库进行完整性控制的话建议使用Hibernate;如果要灵活使用sql语句的话建议采用MyBatis框架。
1.2.3使用MyBatis中Mapper接口使用?
- Mapper接口方法名和mapper.xml中定义的每个sql的id相同
- Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql 的parameterType的类型相同
- Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同
- Mapper.xml文件中的namespace即是mapper接口的类路径。
- Mapper文件中的slq标签的id尽量不要重复。
-
1.2.4 MyBatis中的一级缓存和二级缓存
-
- 一级缓存
-
一级缓存是SqlSession级别的缓存。在操作数据库时需要构造sqlSession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的,一级缓存默认是开启的。
一级缓存有如下特点:
- 第一次发起查询先去找缓存中是否有,如果没有,从数据库查询,然后将查询结果存储到一级缓存中。
- 如果中间sqlSession去执行commit操作(执行插入、更新、删除),则会清空SqlSession中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。
- 第二次发起查询数据,先去找缓存中是否,缓存中有,直接从缓存中获取数据返回。
- 二级缓存
-
二级缓存的原理和一级缓存原理一样,第一次查询,会将数据放入缓存中,然后第二次查询则会直接去缓存中取。但是一级缓存是基于 sqlSession 的,而 二级缓存是基于 mapper文件的namespace的,也就是说多个sqlSession可以共享一个mapper中的二级缓存区域,并且如果两个mapper的namespace相同,即使是两个mapper,那么这两个mapper中执行sql查询到的数据也将存在相同的二级缓存区域中。
-
-
-
- 二级缓存的特点如下:
-
-
- 二级缓存需要手动开启
- 放在二级缓存中的对象需要实现Serializable接口(因为二级缓存数据存储介质多种多样,不一定只存在内存中,有可能存在硬盘中,如果我们要再取这个缓存的话,就需要反序列化了。所以mybatis中的pojo都去实现Serializable接口)。
- 执行commit操作二级缓存数据将会清空。
- 默认事务提交的时候会清空二级缓存数据
-
-
-
-
- useCache和flushCache
-
-
-
userCache是用来设置是否禁用二级缓存的,在statement中设置useCache=false可以禁用当前select语句的二级缓存,即每次查询都会发出sql去查询,默认情况是true,即该sql使用二级缓存。
<select id="getUserById" resultMap="userMap" useCache="true">
Select * from t_User where id = #{id}
</select
在mapper的同一个namespace中,如果有其它insert、update、delete操作数据后需要刷新缓存,如果不执行刷新缓存会出现脏读。设置statement配置中的flushCache=”true” 属性,默认情况下为true,即刷新缓存,如果改成false则不会刷新。使用缓存时如果手动修改数据库表中的查询数据会出现脏读。
<update id="updateUser" flushCache="true">
update t_user set name = #{username} where id = #{id}
</update>
1.2.5 MyBatis插入如何返回主键?
-
- MySQl用法
-
<insert id="addUser" useGeneratedKeys="true" keyProperty="userId">
insert into t_user (username,password) values(#{username},#{password});
</insert>
useGeneratedKeys:取值范围true|false(默认值),设置是否使用JDBC的getGenereatedKeys方法获取主键并赋值到keyProperty设置的领域模型属性中。
keyProperty :用户设置主键回填的到那个属性。以上述例子为列,如果调用addUser方法时传递的是user对象则把主键设置到user对象的userId属性中(没有属性则不设置),如果传递的是个Map则给Map中添加一个键值对,key是userId,value是主键值。
-
- Oracle用法
-
<insert id="updateUser" >
<selectKey resultType="INTEGER" order="BEFORE" keyProperty="userId">
SELECT SEQ_USER.NEXTVAL as userId from DUAL
</selectKey>
insert into t_user (user_id, user_name) values (#{userId}, #{userName})
</insert>
由于Oracle没有自增长一说法,只有序列这种模仿自增的形式,所以不能再使用“useGeneratedKeys”属性。
1.2.6 MyBatis参数的传递有几种?常用有哪些
- 采用map的方式传递
-
- 采用注解的方式
-
- 采用对象的方式
-
- 采用索引获取
-
如果方法的形参超过3个建议使用Map或者封装成对象,如果形参少于3个就使用注解,按照索引获取不推荐使用,因为可读性不强。
1.2.7说说MyBatis中ResultMap都是怎么用的?
ResultMap标签一般用在三种地方,
第一种:对象属性和表中字段不一致可以用ResultMap建立映射关系。
第二种:级联查询。
第三种:多个ResultMap可以继承。
1.3 SpringMVC
1.3.1 SpringMVC工作原理
SpringMVC原理:
SpringMVC执行流程:
- 用户发送请求至前端控制器DispatcherServlet。
- DispatcherServlet收到请求调用HandlerMapping映射器。
- 映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。
- DispatcherServlet调用HandlerAdapter处理器。
- HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)。
- Controller执行完成返回ModelAndView。
- HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet。
- DispatcherServlet将ModelAndView传给ViewReslover视图解析器。
- ViewReslover解析后返回具体View。
- DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。
- DispatcherServlet响应用户。
-
SpringMVC中的重要组件:
- 前端控制器DispatcherServlet:作用是:接收请求、响应结果,相当于转发器;
-
DispatcherServlet相当于中央处理器,有了它就减少了其他组件之间的耦合。
- 处理器映射器HandlerMapping:作用是:根据请求的url查找Handler。
- 处理器适配器HandlerAdapter:作用是:按照特定的规则(HandlerAdapter要求的规则)去执行Handler。
- 视图解析器ViewResolver:作用是:根据逻辑视图名解析真正的视图(View)。
- 视图 View:View是一个接口,实现类支持不同的View类型(jsp、freemarker等)。
-
1.3.2 SpringMVC的常用注解有哪些?
- 组件行型注解
-
注解
说明
@Component
类定义之前添加@Component注解,他会被spring容器识别,并转为bean
@Repository
对Dao实现类进行注解(特殊的@Component)
@Service
用于对业务逻辑层进行注解(特殊的@Component)
@Controller
用于控制层注解(特殊的@Component)
以上四种注解都是注解在类上的,被注解的类将被spring初始话为一个bean,然后统一管理。
- 请求和参数型注解
- @RequestMapping:用于处理请求地址映射,可以作用于类和方法上。
-
- value:定义request请求的映射地址
- method:请求的方式,默认接受get请求,如果请求方式和定义的方式不一样则请求无法成功。
- params:定义request请求中必须包含的参数值。
- headers:定义request请求中必须包含某些指定的请求头,如:RequestMapping(value = "/something", headers = "content-type=text/*")说明请求中必须要包含"text/html", "text/plain"这中类型的Content-type头,才是一个匹配的请求。
- consumes:定义请求提交内容的类型。
- produces:指定返回的内容类型
-
- @RequestMapping:用于处理请求地址映射,可以作用于类和方法上。
-
@RequestMapping(value = "/test.do", params = {"name=qf"}, headers = {
"Accept-Encoding=gzip" },method =RequestMethod.GET)
public String test() {
System.out.println("请求成功");
return "index";
}
-
- @RequestParam:用于获取传入参数的值
-
- value:参数的名称
- required:定义该传入参数是否必须
- defaultValue:默认值
-
- @RequestParam:用于获取传入参数的值
-
@RequestMapping("/test2.do")
public String test2(@RequestParam(value = "name", required = false,defaultValue="qf") String username) {
System.out.println("name = " + username);
return "index";
}
-
- @PathViriable:用于定义路径参数值
-
- value:参数的名称
-
- @PathViriable:用于定义路径参数值
-
@RequestMapping("/getUserById/{id}")
public String test3(@PathVariable(value = "id") Integer id){
System.out.println("id = "+id);
return "index";
}
-
- @ResponseBody:作用于方法上,可以将整个返回结果以某种格式返回,如json或xml格式。
-
@RequestMapping("/test4.do")
@ResponseBody
public String test4() {
return "hello";
}
-
- @CookieValue:用于获取请求的Cookie值
-
@RequestMapping("/requestParams.do")
public String requestParams(@CookieValue("JSESSIONID") String cookie) {
return "index";
}
-
- @ModelAttribute:用于把参数保存到model中,可以注解方法或参数,注解在方法上的时候,该方法将在处理器方法执行之前执行,然后把返回的对象存放在 session(前提时要有@SessionAttributes注解) 或模型属性中,@ModelAttribute(“attributeName”) 在标记方法的时候指定,若未指定,则使用返回类型的类名称(首字母小写)作为属性名称。
-
// 方法的返回值放到request作用域,key为customer
// 该方法将在处理器方法执行之前执行
@ModelAttribute("customer")
public Customer getUser() {
Customer customer = new Customer();
customer.setName("test姓名");
return customer;
}
// 自动注入request作用域中key为customer的数据
@RequestMapping("/testMode1.do")
public String getUsers(@ModelAttribute("customer") Customer customer,ModelMap map) {
System.out.println("CustomerController.getUsers() :"+customer.getName());
map.put("msg", "hello");
return "index";
}
-
- @SessionAttributes
-
默认情况下Spring MVC将模型中的数据存储到request域中。当一个请求结束后,数据就失效了。如果要跨页面使用。那么需要使用到session。而@SessionAttributes注解就可以使得模型中的数据存储一份到session域中。配合@ModelAttribute("user")使用的时候,会将对应的名称的model值存到session中,
@Controller
@RequestMapping(value = "customerController/")
// 加入SessionAttributes注解后request作用域中key为customer,msg的数据会储存一份到session作用域中
@SessionAttributes(value={"customer","msg"})
public class CustomerController {
// 方法的返回值放到request作用域,key为customer
// 该方法将在处理器方法执行之前执行
@ModelAttribute("customer")
public Customer getUser() {
Customer customer = new Customer();
customer.setName("test姓名");
return customer;
}
// 自动注入request作用域中key为customer的数据
@RequestMapping("/testMode1.do")
public String getUsers(@ModelAttribute("customer") Customer customer,ModelMap map) {
System.out.println("CustomerController.getUsers() :"+customer.getName());
map.put("msg", "hello");
return "index";
}
}
-
- @RequestBody
-
该注解常用来处理Content-Type: 不是application/x-www-form-urlencoded编码的内容,例如application/json, application/xml等,接收HTTP请求的JSON数据,将JOSN数据转为Java对象。
@RequestMapping(value="addUser")
public String addUser(@RequestBody Customer customer){
System.out.println("DemoController.addUser():"+customer);
return "index";
}
var obj = new Object();
obj.id =1;
obj.name = '张三';
$.ajax({
url: "addUser",
type:"POST",
contentType:"application/json",
data:JSON.stringify(obj),
success: function(data){
}
});
-
- @InitBinder:属性编辑器,在Controller之前执行,可以对用户输入数据进行处理。
-
@InitBinder
public void initBinder(WebDataBinder dataBinder) {
dataBinder.registerCustomEditor(Date.class, new PropertyEditorSupport() {
public void setAsText(String value) {
try {
setValue(new SimpleDateFormat("yyyy-MM-dd").parse(value));
} catch (Exception e) {
setValue(null);
}
}
});
}
-
- @DateTimeFormat:时间格式化
-
@DateTimeFormat(pattern="yyyy-MM-dd")
private Date birthday;
注意:该注解起作用需要开启注解驱动: <mvc:annotation-driven/>
- 自动注入注解
- @Autowired
-
该注解是按照类型(byType)装配依赖对象,默认情况下它要求依赖对象必须存在,如果允许null值,可以设置它的required属性为false。如果我们想使用按照名称(byName)来装配,可以结合@Qualifier注解一起使用。
-
- @Resource
-
该默认按照ByName自动注入,由J2EE提供, @Resource有两个重要的属性:name和type,如果使用name属性,则使用byName的自动注入策略,而使用type属性时则使用byType自动注入策略。如果既不制定name也不制定type属性,这时将通过反射机制使用byName自动注入策略。
注:两者都可以写在字段和setter方法上。两者如果都写在字段上,那么就不需要再写setter方法。最好是将@Resource放在setter方法上,因为这样更符合面向对象的思想,通过set、get去操作属性,而不是直接去操作属性。
1.3.3如何解决Get和Post乱码
-
-
-
- 解决post乱码
-
-
-
SpringMVC中提供了一个CharacterEncodingFilter来解决乱码的问题,只需要在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>
-
-
-
- 解决get乱码
-
-
-
解决get乱码有两种方式第一种是修改tomcat配置文件编码与工程编码一致,修改tomcat的config文件夹中的server.xml文件。
<Connector URIEncoding="utf-8" connectionTimeout="20000" port="80" protocol="HTTP/1.1" redirectPort="8443"/>
另外一种方法对参数进行重新编码,ISO8859-1是tomcat默认编码,需要将tomcat编码后的内容按utf-8编码。
String name = new String(request.getParameter("name").getBytes("iso-8859-1"),"utf-8");
1.3.4 什么是RestFUL的风格
RESTful风格只是一种架构风格,是一种种思想,http是这个风格下的产物。它主要用于客户端和服务器交互类的软件。基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。讲RestFul下面两个概念一定要说。
-
-
-
- 资源的定位:从url可以知道用户的访问的是那块资源。
-
-
-
-
-
-
- 资源的操作:从请求方式可以知道用户对资源进行一个什么样的操作。
-
-
-
操作
添加
修改
删除
查询
请求方式(method)
POST
PUT
DELETE
GET
请求地址(url)
/addUser?name=zhang
/updateUser?name=admin&id=12
/deleteUser/12
/findUser/12
说明
调用insert操作
调用update操作
调用delete操作
调用select操作
1.3.5 Spring容器和SpirngMVC容器有什么关系
首先 springmvc和spring它俩都是容器,容器就是管理对象的地方,例如Tomcat,就是管理servlet对象的,而springMVC容器和spring容器,就是管理bean对象的地方,再说的直白点,springmvc就是管理controller对象的容器,spring就是管理service和dao的容器,所以我们在springmvc的配置文件里配置的扫描路径就是controller,而spring的配置文件里自然配的就是service和dao。
Spring-mvc.xml
<context:component-scan base-package="com.qf.controller"></context:component-scan>
applicationContext.xml
<context:component-scan base-package="com.qf" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
<context:include-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
</context:component-scan>
至于它是怎么管理起来的,又是怎么注入属性的,这就涉及到他们底层的实现技术了,其次, spring容器和springmvc容器的关系是父子容器的关系。spring容器是父容器,springmvc是子容器。在子容器里可以访问父容器里的对象,但是在父容器里不可以访问子容器的对象,说的通俗点就是,在controller里可以访问service对象,但是在service里不可以访问controller对象,项目中用的bean很多,都交给那个容器管理呢?看下面
- SpringMVC容器管理的Bean
-
- Controller
- SpringMVC相关的bean,比如事务解析器,文件上传bean
-
-
- Spring容器管理的Bean
- 读取配置文件
- 数据源
- 事务管理相关的bean
- Service
- dao
- Spring和其他框架整合的bean
-
1.3.6 SpirngMVC对异常的处理方式有哪些?
SpringMVC中对异常的处理有三种方式分别如下:
- 使用Spring MVC提供的简单异常处理器SimpleMappingExceptionResolver。
-
这种方式需要在springmvc-servlet.xml中配置。
<bean class = "org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<!-- 定义默认的异常处理页面,当该异常类型的注册时使用 -->
<property name="defaultErrorView" value="error">
</property>
<!-- 定义异常处理页面用来获取异常信息的变量名,默认名为exception -->
<property name = "exceptionAttribute" value="ex">
</property>
<!-- 定义需要特殊处理的异常-->
<property name="exceptionMappings">
<props>
<!-- 用类名或完全路径名作为key,异常页面名字作为值 -->
<prop key = "org.apache.shiro.authz.UnauthorizedException">
common/unauthorized
</prop>
</props>
</property>
</bean>
- 实现Spring的异常处理接口HandlerExceptionResolver 自定义自己的异常处理器。
-
@Component
public class MyExceptionHandler implements HandlerExceptionResolver {
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,
Exception ex) {
Map<String, Exception> map = new HashMap<String, Exception>();
map.put("ex", ex);
// 根据获取的Exception参数进行view跳转
if (ex instanceof RuntimeException) {
return new ModelAndView("error-my", map);
} else {
return new ModelAndView("error", map);
}
}
}
- 使用@ExceptionHandler进行处理。
-
使用@ExceptionHandler进行处理有一个不好的地方是进行异常处理的方法必须与出错的方法在同一个Controller里面。
@Controller
public class DemoController {
@ExceptionHandler({ Exception.class })
public String exception(Exception e) {
System.out.println("DemoController.exception()");
System.out.println(e.getMessage());
e.printStackTrace();
return "error";
}
@RequestMapping(value = "test")
public String test() {
System.out.println("DemoController.test()");
int i =10/0;
return "index";
}
}
-
- Spring
-
1.4.1谈谈你对Spring的理解
首先Spring 是一个开源框架,是为了解决企业应用程序开发复杂性而创建的。框架的主要优势之一就是其分层架构,分层架构允许你选择使用哪一个组件,同时为 J2EE 应用程序开发提供集成的框架。Spring为简化企业级应用开发而生,使用Spring可以使简单的JavaBean实现以前只有EJB才能实现的功能。Spring是一个IOC和AOP容器框架。
Spring 框架是一个分层架构,由 7 个定义良好的模块组成。Spring 模块构建在核心容器之上,核心容器定义了创建、配置和管理 bean 的方式。
组成 Spring 框架的每个模块(或组件)都可以单独存在,或者与其他一个或多个模块联合实现。每个模块的功能如下:
核心容器:核心容器提供 Spring 框架的基本功能。核心容器的主要组件是 BeanFactory,它是工厂模式的实现。BeanFactory 使用控制反转 (IOC) 模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。
Spring上下文:Spring 上下文是一个配置文件,向 Spring 框架提供上下文信息。Spring 上下文包括企业服务,例如 JNDI、EJB、电子邮件、国际化、校验和调度功能。
Spring AOP:通过配置管理特性,Spring AOP 模块直接将面向方面的编程功能集成到了 Spring 框架中。所以,可以很容易地使 Spring 框架管理的任何对象支持 AOP。Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖 EJB 组件,就可以将声明性事务管理集成到应用程序中。
Spring DAO:JDBC DAO 抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量(例如打开和关闭连接)。Spring DAO 的面向 JDBC 的异常遵从通用的 DAO 异常层次结构。
Spring ORM:Spring 框架插入了若干个 ORM 框架,从而提供了 ORM 的对象关系工具,其中包括 JDO、Hibernate 和 iBatis SQL Map。所有这些都遵从 Spring 的通用事务和 DAO 异常层次结构。
Spring Web 模块:Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。所以,Spring 框架支持与 Jakarta Struts 的集成。Web 模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。
Spring MVC 框架:MVC 框架是一个全功能的构建 Web 应用程序的 MVC 实现。通过策略接口,MVC 框架变成为高度可配置的,MVC 容纳了大量视图技术,其中包括 JSP、Velocity、Tiles、iText 和 POI。
Spring主要核心是:
IOC:Inversion of Control(控制反转) 平常的java开发中,程序员在某个类中需要依赖其它类的方法,则通常是new一个依赖类再调用类实例的方法,这种开发存在的问题是new的类实例不好统一管理,spring提出了依赖注入的思想,即依赖类不由程序员实例化,而
是通过 spring 容器帮我们 new 指定实例并且将实例注入到需要该对象的类中。依赖注入的另一种说法是“控制反转”,通俗的理解是:平常我们 new 一个实例,这个实例的控制权是我们程序员,而控制反转是指 new 实例工作不由我们程序员来做而是交给spring容器来做。
DI:Dependency Injection,即“依赖注入”。其实IOC和DI本就是同一个概念的两种不同的表述,应用程序依赖容器提供的外部对象,容器将其依赖的外部资源在运行期注入到应用程序中;某个对象被调用时,其所依赖的对象由容器注入。
AOP: Aspect-OrientedProgrammin(面向切面编程)在面向对象编程(OOP)思想中,我们将事物纵向抽象成一个个的对象。而在面向切面编程中,我们将一个个对象某些类似的方面横向抽象成一个切面,对这个切面进行一些如权限验证,事物管理,记录日志等公用操作处理的过程就是面向切面编程的思想。
1.4.2 Spring中用到的设计模式有哪些?
- 工厂模式
-
将对象的创建和使用相分离,采用工厂模式,即应用程序将对象的创建及初始化职责交给Bean工厂对象。
- 单例模式
-
Spring下默认的bean均为singleton,可通过scope属性来指定bean的作用域。
- 代理模式
-
在Spring Aop的实现中用到了JDK或Cglib来实现的动态代理。
- 观察者
-
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。spring中Observer模式常用的地方是listener的实现。如ApplicationListener。
- 适配器
-
在Spring的Aop中,使用的Advice(通知)来增强被代理类的功能。Spring实现这一AOP功能的原理就使用代理模式对类进行方法级别的切面增强,即生成被代理类的代理类, 并在代理类的方法前,设置拦截器,通过执行拦截器重的内容增强了代理方法的功能,实现的面向切面编程。
- 策略模式
-
加载资源文件的方式,使用了不同的方法,比如:ClassPathResourece,FileSystemResource,ServletContextResource,UrlResource但他们都有共同的借口Resource。
1.4.3 Spring中的常用注解
- @Autowired: 可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作。
- qequired:是否必须注入,默认是true。
-
该注解可以消除get,set方法,是根据类型进行自动转配的。该注解根据bean 类型从spring 上线文中进行查找,注册类型必须唯一,否则报异常。@Autowired(required=false) 表示,如果spring 上下文中没有找到该类型的bean 时,才会使用new SoftPMServiceImpl()。
@Autowired 标注作用于Map类型时,如果 Map 的 key 为 String 类型,则 Spring 会将容器中所有类型符合 Map 的 value 对应的类型的 Bean 增加进来,用 Bean 的 id 或 name 作为 Map 的 key。
@Autowired
private Map<String, IUserDao> map;
@Autowired
private IUserDao userDao;
- @Qualifier
-
使用 @Autowired时,如果找到多个同一类型的bean,则会抛异常,此时可以使用 @Qualifier("beanName"),明确指定bean的名称进行注入,此时与 @Resource指定name属性作用相同。
@Autowired
@Qualifier(value="userDao")
private IUserDao userDao;
- @Resource
-
该注解默认按bean的name进行查找,如果没有找到会按type进行查找,此时与 @Autowired类似。如果按照类型匹配到多个可以通过name属性指定织入的bean名称。
@Resource
private IUserDao userDao;
- @Scope:该注解用来定义一个bean的作用范围。
-
该注解中可以指定如下值:
-
- singleton:定义bean的范围为每个spring容器一个实例(默认值)
- prototype:定义bean可以被多次实例化(使用一次就创建一次)
- request: 定义bean的范围是http请求(springMVC中有效)
- session: 定义bean的范围是http会话(springMVC中有效)
-
@Scope("session")
@Repository
public class UserDaoImpl {}
- @PostConstruct
-
在方法上加上注解 @PostConstruct,这个方法就会在Bean初始化之后被Spring容器执行。执行顺序是Constructor >> @Autowired >> @PostConstruct
- @PreDestroy
-
在方法上加上注解 @PreDestroy ,这个方法就会在Bean 被销毁前被Spring 容器执行。
- @Required
-
@Required负责检查一个bean在初始化时其声明的set方法是否被执行,当某个被标注了 @Required的Setter方法没有被调用,则Spring在解析的时候会抛出异常,以提醒开发者对相应属性进行设置。@Required注解只能标注在Setter方法之上。因为依赖注入的本质是检查Setter方法是否被调用了,而不是真的去检查属性是否赋值了以及赋了什么样的值。如果将该注解标注在非setXxxx()类型的方法则被忽略。
其他注解请看1.3.2内容。
1.4.4简单介绍下SpringBean的生命周期
在说明前可以思考一下Servlet的生命周期:实例化,初始init,接收请求service,销毁destroy;Spring上下文中的Bean也类似,如下:
- 实例化一个Bean--也就是我们常说的new;
- 按照Spring上下文对实例化的Bean进行配置--也就是IOC注入;
- 如果这个Bean已经实现了BeanNameAware接口,会调用它实现的setBeanName(String)方法,此处传递的就是Spring配置文件中Bean的id值
- 如果这个Bean已经实现了BeanFactoryAware接口,会调用它实现的setBeanFactory(setBeanFactory(BeanFactory)传递的是Spring工厂自身(可以用这个方式来获取其它Bean,只需在Spring配置文件中配置一个普通的Bean就可以);
- 如果这个Bean已经实现了ApplicationContextAware接口,会调用setApplicationContext(ApplicationContext)方法,传入Spring上下文(同样这个方式也可以实现步骤4的内容,但比4更好,因为ApplicationContext是BeanFactory的子接口,有更多的实现方法);
- 如果这个Bean关联了BeanPostProcessor接口,将会调用postProcessBeforeInitialization(Object obj, String s)方法,BeanPostProcessor经常被用作是Bean内容的更改,并且由于这个是在Bean初始化结束时调用那个的方法,也可以被应用于内存或缓存技术;
- 如果Bean在Spring配置文件中配置了init-method属性会自动调用其配置的初始化方法。
- 如果这个Bean关联了BeanPostProcessor接口,将会调用postProcessAfterInitialization(Object obj, String s)方法、;注:以上工作完成以后就可以应用这个Bean了,那这个Bean是一个Singleton的,所以一般情况下我们调用同一个id的Bean会是在内容地址相同的实例,当然在Spring配置文件中也可以配置非Singleton,这里我们不做赘述。
- 当Bean不再需要时,会经过清理阶段,如果Bean实现了DisposableBean这个接口,会调用那个其实现的destroy()方法;
- 最后,如果这个Bean的Spring配置中配置了destroy-method属性,会自动调用其配置的销毁方法。
-
1.4.5 Spring有什么优点?能帮我做什么?
- 优点
- 使用Spring的IOC容器,将对象之间的依赖关系交给Spring,降低组件之间的耦合性,让我们更专注于应用逻辑
- 可以提供众多服务,事务管理,WS等。
- AOP的很好支持,方便面向切面编程。
- 对主流的框架提供了很好的集成支持,如hibernate,Struts2,JPA等
- Spring DI机制降低了业务对象替换的复杂性。
- Spring属于低侵入,代码污染极低。
- Spring的高度可开放性,并不强制依赖于Spring,开发者可以自由选择Spring部分或全部。
- Spring自己也提供了JDBC模板来操作数据库。
- Spring能方便的和JavaEE(邮件发送,任务调度)整合
-
1.4.6说下Spring对事务的控制
作为企业级应用程序框架,Spring在不同的事务管理API之上定义了一个抽象层。而应用程序开发人员不必了解底层的事务管理API,就可以使用Spring的事务管理机制。Spring支持编程式事务管理和声明式的事务管理。
编程式事务管理:将事务管理代码嵌入到业务方法中来控制事务的提交和回滚,在编程式事务中,必须在每个业务操作中包含额外的事务管理代码。
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext.xml" })
public class test {
@Resource
private PlatformTransactionManager txManager;
@Resource
private DataSource dataSource;
@Test
public void testdelivery() {
// 1.定义事务隔离级别,传播行为,
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
// 2.获取事务
// 事务状态类,通过PlatformTransactionManager的getTransaction方法根据事务定义获取;
// 获取事务状态后,Spring根据传播行为来决定如何开启事务
TransactionStatus status = txManager.getTransaction(def);
// 3.创建JdbcTemplate
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
try {
// 4.操作数据库
jdbcTemplate.update("insert into t_user(name) values(?)", "达老师");
// 5.手动提交事务
txManager.commit(status); // 提交status中绑定的事务
} catch (RuntimeException e) {
// 6.手动回滚事务
txManager.rollback(status); // 回滚
}
}
}
声明式事务管理:大多数情况下比编程式事务管理更好用。它将事务管理代码从业务方法中分离出来,事务管理作为一种横切关注点,通过AOP思想实现事务控制。
<!-- 5.事务管理器 -->
<bean id="tx" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 6.事务的策略 -->
<tx:advice transaction-manager="tx" id="txAdvices">
<tx:attributes>
<!-- 查询的操作 -->
<tx:method name="get*" isolation="DEFAULT" propagation="REQUIRED" read-only="true"/>
<tx:method name="find*" isolation="DEFAULT" propagation="REQUIRED" read-only="true"/>
<!-- 更新操作 -->
<tx:method name="add*" isolation="DEFAULT" propagation="REQUIRED" read-only="false"/>
<tx:method name="update*" isolation="DEFAULT" propagation="REQUIRED" read-only="false"/>
<tx:method name="delete*" isolation="DEFAULT" propagation="REQUIRED" read-only="false"/>
</tx:attributes>
</tx:advice>
<!-- 7.AOP的配置 -->
<aop:config>
<aop:pointcut expression="execution(* com.qf.service.*.*(..))" id="p1"/>
<aop:advisor advice-ref="txAdvices" pointcut-ref="p1" />
</aop:config>
Spring中和事务相关的三个顶级接口:
接口名称
说明
PlatformTransactionManager
最核心的一个接口,定义了获取事务,提交事务,回滚事务等操作。
TransactionDefinition
定义了事务的属性,比如:事务隔离级别,传播行为,是否只读等操作。
TransactionStatus
定义了事务的保存点,是否为新事物,是否为回滚等操作。
Spring提供了许多内置事务管理器实现:
- DataSourceTransactionManager:位于org.springframework.jdbc.datasource包中,数据源事务管理器,提供对单个javax.sql.DataSource事务管理,用于Spring JDBC抽象框架、iBATIS或MyBatis框架的事务管理。
-
- JdoTransactionManager:位于org.springframework.orm.jdo包中,提供对单个javax.jdo.PersistenceManagerFactory事务管理,用于集成JDO框架时的事务管理。
-
- JpaTransactionManager:位于org.springframework.orm.jpa包中,提供对单个javax.persistence.EntityManagerFactory事务支持,用于集成JPA实现框架时的事务管理。
-
- HibernateTransactionManager:位于org.springframework.orm.hibernate3包中,提供对单个org.hibernate.SessionFactory事务支持,用于集成Hibernate框架时的事务管理;该事务管理器只支持Hibernate3+版本,且Spring3.0+版本只支持Hibernate 3.2+版本。
-
- JtaTransactionManager:位于org.springframework.transaction.jta包中,提供对分布式事务管理的支持,并将事务管理委托给Java EE应用服务器事务管理器。
-
- OC4JjtaTransactionManager:位于org.springframework.transaction.jta包中,Spring提供的对OC4J10.1.3+应用服务器事务管理器的适配器,此适配器用于对应用服务器提供的高级事务的支持。
-
- WebSphereUowTransactionManager:位于org.springframework.transaction.jta包中,Spring提供的对WebSphere 6.0+应用服务器事务管理器的适配器,此适配器用于对应用服务器提供的高级事务的支持。
-
- WebLogicJtaTransactionManager:位于org.springframework.transaction.jta包中,Spring提供的对WebLogic 8.1+应用服务器事务管理器的适配器,此适配器用于对应用服务器提供的高级事务的支持。
-
1.4.7解释SpringJDBC,SpringORM,SpringWeb模块作用
- SpringJDBC
-
Spring 的JDBC 模块负责数据库资源管理和错误处理,大大简化了开发人员对数据库的操作,使得开发人员可以从繁琐的数据库操作中解脱出来,从而将更多的精力投入到编写业务逻辑中。SpringJDBC提供了一个模板类可以直接操作数据,还提供了一个JdbcDaoSupport,这样可以对我们的Dao层进行扩展。
- SpringORM
-
SpringORM模块是Spring集成ORM框架技术(hibernate,JPA,TopLink,MyBatis)而提供的,对于这些技术Spring每一个都提供了集成类,并平稳的和Spring事务进行集成。
对于每一种技术,配置主要在于将一个 DataSource bean 注入到某种sessionFactory或者 EntityManagerFactory 等 bean 中。纯 JDBC不需要这样的一个集成类 因为JDBC 仅依 赖于一个 DataSource 。
如果你计划使用一种ORM技术,比如JPA或者Hibernate,那么你就不需要Spring -JDBC模块了,你需要的是这个 Spring –ORM。
- SpringWeb
-
SpringWeb模块主要是对web的支持,是建立在ApplicationContext基础之上的,提供一个适合Web应用的上下文。它也有对Web框架的支持,比如:Struts1,Struts2,JSF等。
1.4.8 Spring的配置文件有什么用?
首先Spring的配置文件是个xml文件,它里面描述了Bean的配置和Bean之间的依赖,还有一些开启包扫描,事物配置等。
1.4.9什么是IOC容器?
IOC容器就是具有依赖注入功能的容器,是可以创建对象的容器,IOC容器负责实例化、定位、配置应用程序中的对象及建立这些对象间的依赖,并管理这整个bean的生命周期。通常new一个实例,控制权由程序员控制,而"控制反转"是指new实例工作不由程序员来做而是交给Spring容器来做。
1.4.10 IOC的优点
-
- 实现组件之间的解耦,提高程序的灵活性和可维护性
- IOC容器支持饿汉式和懒汉式方式初始化bean
-
1.4.11 ApplicationContext实现类有哪些?
ClassPathXmlApplicationContext
从xml文件中加载Bean的定义,这里需要真确的配置classpath,应为容器在classpath路径下面寻找xml文件。
// 也可以这样写:classpath:applicationContext.xml:
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
FileSystemXmlApplicationContext
从xml文件中加载Bean的定义,需要给定一个配置文件的路径,从该路径加载xml文件。
// 也可以这样写:file:src/applicationContext.xml
ApplicationContext ctx = new FileSystemXmlApplicationContext("src/applicationContext.xml");
WebApplicationContext
WebApplicationContext Spring提供WebApplicationContextUtils通过该类的getWebApplicationContext(ServletContext sc)方法获取,即可从ServletContext中获取WebApplicationContext。一般用到Web环境中获取Spring容器对象,还需要在web.xml中添加监听器在Web容器启动的时候初始化Spring容器。
// 1.获取Servlet上下文对象
ServletContext servletContext = request.getServletContext();
// 2.在Web环境中获取Spring容器
WebApplicationContext wac = WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext);
1.4.12 BeanFactory和ApplicationContext有什么区别?
- IoC容器的主要接口关系图如下:
- BeanFactory
-
BeanFactory 是 Spring 的“心脏”。它就是 Spring IoC 容器的真面目。Spring 使用 BeanFactory 来实例化、配置和管理 Bean。
BeanFactory是IOC容器的核心接口, 它定义了IOC的基本功能,我们看到它主要定义了getBean方法。getBean方法是IOC容器获取bean对象和引发依赖注入的起点。方法的功能是返回特定的名称的Bean。
BeanFactory 是初始化 Bean 和调用它们生命周期方法的“吃苦耐劳者”。注意,BeanFactory 只能管理单例(Singleton)Bean 的生命周期。它不能管理原型(prototype,非单例)Bean 的生命周期。这是因为原型 Bean 实例被创建之后便被传给了客户端,容器失去了对它们的引用。
- ApplicationContext
-
如果说BeanFactory是Spring的心脏,那么ApplicationContext就是完整的躯体了,ApplicationContext由BeanFactory派生而来,提供了更多面向实际应用的功能。在BeanFactory中,很多功能需要以编程的方式实现,而在ApplicationContext中则可以通过配置实现。
BeanFactorty接口提供了配置框架及基本功能,但是无法支持spring的aop功能和web应用。而ApplicationContext接口作为BeanFactory的派生,因而提供BeanFactory所有的功能。而且ApplicationContext还在功能上做了扩展,相较于BeanFactorty,ApplicationContext还提供了以下的功能:
- MessageSource, 提供国际化的消息访问
- 资源访问,如URL和文件
- 事件传播特性,即支持aop特性
- 载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层
-
ApplicationContext:是IOC容器另一个重要接口, 它继承了BeanFactory的基本功能, 同时也继承了容器的高级功能,如:MessageSource(国际化资源接口)、ResourceLoader(资源加载接口)、ApplicationEventPublisher(应用事件发布接口)等。
- 二者区别
-
BeanFactroy采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化,这样,我们就不能发现一些存在的Spring的配置问题。而ApplicationContext则相反,它是在容器启动时,一次性创建了所有的Bean。这样,在容器启动时,我们就可以发现Spring中存在的配置错误。 相对于基本的BeanFactory,ApplicationContext 唯一的不足是占用内存空间。当应用程序配置Bean较多时,程序启动较慢。
BeanFacotry延迟加载,如果Bean的某一个属性没有注入,BeanFacotry加载后,直至第一次使用调用getBean方法才会抛出异常;而ApplicationContext则在初始化自身是检验,这样有利于检查所依赖属性是否注入;所以通常情况下我们选择使用 ApplicationContext。应用上下文则会在上下文启动后预载入所有的单实例Bean。通过预载入单实例bean ,确保当你需要的时候,你就不用等待,因为它们已经创建好了。
beanFactory主要是面对与 spring 框架的基础设施,面对 spring 自己。而 Applicationcontex 主要面对与 spring 使用的开发者。基本都会使用 Applicationcontex 并非 beanFactory。
- 二者作用
- BeanFactory负责读取bean配置文档,管理bean的加载,实例化,维护bean之间的依赖关系,负责bean的声明周期。
- ApplicationContext除了提供上述BeanFactory所能提供的功能之外,还提供了更完整的框架功能:
- 国际化支持
- 资源访问:Resource rs = ctx. getResource(“classpath:config.properties”), “file:c:/config.properties”
- 事件传递:通过实现ApplicationContextAware接口
- 常用的获取ApplicationContext
- FileSystemXmlApplicationContext
- ClassPathXmlApplicationContext
- WebApplicationContext
-
1.4.13 Spring中有哪些注入方式?
- setXxx注入
- 构造器注入
- 静态工厂的方法注入
-
1.4.14 Spring支持的几种bean的作用域
Spring框架支持以下五种bean的作用域
- singleton:单例模式,在整个Spring IoC容器中,使用singleton定义的Bean将只有一个实例。
- prototype:原型模式,每次通过容器的getBean方法获取prototype定义的Bean时,都将产生一个新的Bean实例。
- request:对于每次HTTP请求,使用request定义的Bean都将产生一个新实例,即每次HTTP请求将会产生不同的Bean实例。只有在Web应用中使用Spring时,该作用域才有效
- session:对于每次HTTP Session,使用session定义的Bean都将产生一个新实例。同样只有在Web应用中使用Spring时,该作用域才有效。
- globalsession:每个全局的HTTP Session,使用session定义的Bean都将产生一个新实例。典型情况下,仅在使用portlet context的时候有效。同样只有在Web应用中使用Spring时,该作用域才有效。
-
如果不指定Bean的作用域,Spring默认使用singleton作用域。Java在创建Java实例时,需要进行内存申请;销毁实例时,需要完成垃圾回收,这些工作都会导致系统开销的增加。因此,prototype作用域Bean的创建、销毁代价比较大。而singleton作用域的Bean实例一旦创建成功,可以重复使用。因此,除非必要,否则尽量避免将Bean被设置成prototype作用域。
1.4.14 Spring框架中的单例bean是线程安全的吗?
单列整个容器只有一个实例,每次调用都是那一个实例,所以线程不安全。
1.4.15什么是Spring的内部bean
当一个 bean 仅被用作另一个 bean 的属性时,它能被声明为一个内部 bean,为了定义内部Bean,在Spring 的 基于XML的 配置元数据中,可以在 <property/>或 <constructor-arg/> 元素内使用<bean/> 元素,内部bean通常是匿名的,它们的Scope一般是prototype。
<bean id="userService" class="com.qf.service.impl.UserServiceImpl">
<property name="userDao">
<!-- 内部Bean -->
<bean class="com.qf.dao.impl.UserDaoImpl"></bean>
</property>
</bean>
1.4.17 在 Spring中如何注入一个java集合?
被注入的Bean定义
public class UserServiceImpl implements IUserService {
private IUserDao userDao;
private String username;
private Integer age; // 自动类型转换
private List<Integer> ids;
private Map<String, Object> map;
private Properties properties; //属性对象
}
Spring中配置如下:
<bean id="userService" class="com.qf.service.impl.UserServiceImpl">
<!-- 注入对象 -->
<property name="userDao" ref="userDao"></property>
<!-- 注入字符串 -->
<property name="username" value="admin"></property>
<!-- 注入Integer类型 -->
<property name="age" value="22"></property>
<!-- 注入list -->
<property name="ids">
<list>
<value>1</value>
<value>2</value>
<value>3</value>
<!-- list中装对象的情况 -->
<!-- <ref bean="beanId"/> -->
</list>
</property>
<!-- 注入map -->
<property name="map">
<map>
<entry key="name" value="admin"></entry>
<entry key="password" value="123"></entry>
<!-- value-ref:指向的一个对象 -->
<entry key="user" value-ref="userDao"></entry>
</map>
</property>
<!-- 注入属性对象 -->
<property name="properties">
<props>
<prop key="username">root</prop>
<prop key="url">jdbc:mysql://localhost:3306/1805_hibernate</prop>
</props>
</property>
</bean>
1.4.18 什么是bean的自动装配?
无须在Spring配置文件中描述javaBean之间的依赖关系(如配置<property>、<constructor-arg>)。IOC容器会自动建立javabean之间的关联关系。可以通过<bean>标签中的autowire属性来完成自动装配。Autowire属性值有五种分别如下:
1)no:默认的方式是不进行自动装配,通过显式设置ref 属性来进行装配。
2)byName:根据属性名称自动装配,容器试图匹配、装配和该bean的属性具有相同名字的bean,如果找到就装配,没有找到就不装配。
3)byType::按数据类型自动装配, 容器试图匹配、装配和该bean的属性具有相同类型的bean。如果有多个bean符合条件,则抛出错误。
4)constructor:这个方式类似于 byType, 但是要提供给构造器参数,如果没有确定的带参数的构造器参数类型,将会抛出异常。
5)default:按照父节点<beans>中的自动装配机制装配。
1.4.19什么是基于注解的容器配置
相对于XML文件,注解型的配置依赖于通过字节码元数据装配组件,而非尖括号的声明。开发者通过在相应的类,方法或属性上使用注解的方式,直接组件类中进行配置,而不是使用xml表述bean的装配关系。
Xml配置VS注解
配置方式
优点
缺点
xml配置
1. XML配置方式进一步降低了耦合,使得应用更加容易扩展,即使对配置文件进一步修改也不需要工程进行修改和重新编译。
2. 在处理大的业务量的时候,用XML配置应该更加好一些。因为XML更加清晰的表明了各个对象之间的关系,各个业务类之间的调用。同时spring的相关配置也能一目了然。
配置文件读取和解析需要花费一定的时间,配置文件过多的时候难以管理,无法对配置的正确性进行校验,增加了测试难度。
注解配置
1. 在class文件中,可以降低维护成本,annotation的配置机制很明显简单
2. 不需要第三方的解析工具,利用java反射技术就可以完成任务
3. 编辑期可以验证正确性,差错变得容易
4. 提高开发效率
1. 如果需要对于annotation进行修改,那么要重新编译整个工程 。
2. 业务类之间的关系不如XML配置那样容易把握。
3. 如果在程序中annotation比较多,直接影响代码质量,对于代码的简洁度有一定的影响
1.4.20怎样开启注解装配?
注解装配默认是不开启的,如果要开启注解装配需要在xml中配置如下节点
<context:component-scan base-package="com.qf"/>
1.4.21 在Spring框架中如何更有效地使用JDBC?
Spring中提供了对JDBC的支持,可以方便的操作数据库。JdbcTemplate和JdbcDaoSupport都是Spring提供操作数据库的类。这些类提供了很多便利的方法和解决诸如把数据库数据转变成基本数据类型或对象的方法。让使用者用来简单,高效。
1.4.22 使用 Spring 通过什么方式访问 Hibernate?
Spring中提供了如下两种方式来访问Hibenrate:
- HibernateTemplate
-
该类中定义了大量利用Hibnerate操作数据库的方法,还可以去获取session对象。使用的时候需要注入sessionFactory。
- HibernateDAOSupport
-
该类是一个抽象类,在HibernateTemplate基础之上做了扩展,里面还是用HibernateTemplate来操作数据库,我们在使用的时候可以注入sessionFactory或者HibernateTemplate。注入sessionFactory它会自动创建一个HibernateTemplate,注入HibernateTemplate它会直接使用。
1.4.23 Spring 支持的 ORM 框架有哪些?
Spring支持的ORM框架有:
Hibernate、iBatis/MyBatis、JPA(SpringDataJPA) 、TopLink、JDO (Java Data Objects)、OJB。
- JDO
-
Java数据对象(Java Data Objects,JDO)是一个应用程序接口(API),它是Java程序员能够间接地访问数据库,也就是说,不需使用直接的结构化查询语言(SQL)语句。JDO是作为Java数据库连接(JDBC)的一个补充来介绍的,而JDBC是一个支持使用SOL语句对流行的数据库程序进行访问的接口。有了 JDO,程序员就可以使用类来定义数据对象,然后支撑程序就会根据类的定义来管理对给定数据库的实际的数据访问。
JDO是以Sun公司为首所制定的Java Community Process(Java标准制定组织,JCP)的一部分。JDBC仍然保留使用是因为它比起JDO允许程序员在数据库访问上有更大的控制权。除JDO和 JDBC外的另一个选择是Enterprise JavaBeans (EJB)。
- OJB
-
对象关系桥(OJB)是一种对象关系映射工具,它能够完成从Java对象到关系数据库的透明存储。
OJB使用基于XML的对象关系映射。映射发生在一个动态的元数据层,使得可以通过一个简单的元对象协议(MOP)在运行时就可以操作元数据层去改变存储内核。 OJB提供了高级的O/R,如对象缓冲,通过虚拟代理实现后期实例化,配置事务隔离层实现分布式的锁治理,同时支持多种锁治理。
OJB与J2EE应用服务器进行了很好的集成。支持数据源的JNDI查找;与JTA和JCA全面集成;能够在jsps,Servlet和SessionBeans中使用;OJB也为实体Bean治理Bean(BMP)提供了非凡的支持。
OJB提供了良好的可配置性,和集成机制,答应用户使用预定义的组件或是自己实现扩展组件。
- TopLink
-
Toplink是Oracle公司捐献给开源社区的,是最早的ORM映射框架之一,也是对JPA规范支持最好的一个框架。TopLink,是位居第一的Java对象关系可持续性体系结构,原署WebGain公司的产品,后被Oracle收购,并重新包装为Oracle AS TopLink。TOPLink为在关系数据库表中存储 Java 对象和企业 Java 组件 (EJB) 提供高度灵活和高效的机制。TopLink 为开发人员提供极佳的性能和选择,可以与任何数据库、任何应用服务器、任何开发工具集和过程以及任何 J2EE 体系结构协同工作。
1.4.24 请解释下Spring中AOP思想
AOP(Aspect Oriented Programming),即面向切面编程,可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。OOP引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。不过OOP允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能。日志代码往往横向地散布在所有对象层次中,而与它对应的对象的核心功能毫无关系对于其他类型的代码,如安全性、异常处理和透明的持续性也都是如此,这种散布在各处的无关的代码被称为横切(cross cutting),在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。
AOP技术恰恰相反,它利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。
使用"横切"技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事物。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。
简单一句话概括:编译期业务主体和系统服务是分开的,在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想。
AOP术语
1.通知(Advice)
就是你想要的功能,也就是常见的 安全,事物,日志等。你给先定义好把,然后在想用的地方用一下。
2.连接点(JoinPoint)
这个更好解释了,就是spring允许你使用通知的地方,那可真就多了,基本每个方法的前,后(两者都有也行),或抛出异常时都可以是连接点,spring只支持方法连接点.其他如aspectJ还可以让你在构造器或属性注入时都行,不过那不是咱关注的,只要记住,和方法有关的前前后后(抛出异常),都是连接点。
3.切入点(Pointcut)
上面说的连接点的基础上,来定义切入点,你的一个类里,有15个方法,那就有几十个连接点了对把,但是你并不想在所有方法附近都使用通知(使用叫织入,以后再说),你只想让其中的几个,在调用这几个方法之前,之后或者抛出异常时干点什么,那么就用切点来定义这几个方法,让切点来筛选连接点,选中那几个你想要的方法。
4.切面(Aspect)
切面是通知和切入点的结合。现在发现了吧,没连接点什么事情,连接点就是为了让你好理解切点,搞出来的,明白这个概念就行了。通知说明了干什么和什么时候干(什么时候通过方法名中的before,after,around等就能知道),而切入点说明了在哪干(指定到底是哪个方法),这就是一个完整的切面定义。
5.引入(introduction)
允许我们向现有的类添加新方法属性。这不就是把切面(也就是新方法属性:通知定义的)用到目标类中吗
6.目标(target)
引入中所提到的目标类,也就是要被通知的对象,也就是真正的业务逻辑,他可以在毫不知情的情况下,被咱们织入切面。而自己专注于业务本身的逻辑。
7.代理(proxy)
怎么实现整套aop机制的,都是通过代理,这个一会给细说。
8.织入(weaving)
把切面应用到目标对象来创建新的代理对象的过程。有3种方式,spring采用的是运行时,为什么是运行时,后面解释。
Spring AOP 实现机制
Spring AOP使用动态代理技术在运行期织入增强的代码,使用了两种代理机制,一种是基于JDK的动态代理,另一种是基于CGLib的动态代理。
Jdk实现动态代理
定义对象接口:
public interface IUserService {
public void add();
}
定义实现类
public class UserServiceImpl implements IUserService {
@Override
public void add() {
// 做真实业务主体
System.out.println("UserServiceImpl.add()");
}
}
定义动态代理的Handler
public class ProxyHanlder implements InvocationHandler {
// 1.系统服务
private TransactionManager tx;
private TimeManager tm;
// 2.目标对象
private Object target;
public ProxyHanlder(TransactionManager tx, TimeManager tm, Object target) {
this.tx = tx;
this.tm = tm;
this.target = target;
}
/**
* proxy:代理类
* method:目标对象方法
* args:目标对对象方法的参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 1.系统服务
tm.start();
tx.beign();
// 2.调用真实业务主体
Object result = method.invoke(target, args);
// 2.系统服务结束
tx.commit();
tm.end();
return result;
}
}
测试
@Test
public void testAdd2() {
// 1.创建目标对象
IUserService userService = new UserServiceImpl();
// 2.增强
TransactionManager tx = new TransactionManager();
TimeManager tm = new TimeManager();
// 3.创建代理
ClassLoader classLoader = userService.getClass().getClassLoader(); // 目标对象类加载器
Class<?>[] interfaces = userService.getClass().getInterfaces(); // 目标对象接口
ProxyHanlder proxyHanlder = new ProxyHanlder(tx, tm, userService); // InvocationHandler
IUserService userServiceProxy = (IUserService)Proxy.newProxyInstance(classLoader, interfaces, proxyHanlder);
// 3.调用方法
userServiceProxy.add();
}
注意: JDK动态代理有一个限制,即它只能为接口创建代理实例。
CGLIB实现动态代理
CGLib采用非常底层的字节码技术,可以在运行时为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用。
以上面的例子为例,目标对象的接口和实现类不变,只需要改变动态代理的handler即可,需改代码如下:
public class ProxyHanlder implements MethodInterceptor {
// 1.系统服务
private TransactionManager tx;
private TimeManager tm;
public ProxyHanlder(TransactionManager tx, TimeManager tm) {
this.tx = tx;
this.tm = tm;
}
/**
* obj:目标对象
* method:目标对象的中执行的方法
* args:方法的参数
* proxy:代理
*/
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
// 1.系统服务
tm.start();
tx.beign();
// 2.调用目标对象
Object result = proxy.invokeSuper(obj, args); // 调用父类的方法
tx.commit();
tm.end();
return result;
}
}
测试
@Test
public void testAdd2() {
// 1.创建目标对象
IUserService userService = new UserServiceImpl();
// 2.增强
TransactionManager tx = new TransactionManager();
TimeManager tm = new TimeManager();
// 3.创建代理
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(userService.getClass()); // 设置父类(目标对象)
enhancer.setCallback(new ProxyHanlder(tx, tm));
IUserService userServiceProxy =(IUserService) enhancer.create(); // 创建代理类
// 3.调用方法
userServiceProxy.add();
}
注意: CGLIB动态代理有一个限制,不能给final修饰的类创建代理。
之后对两者做了一个效率对比,在自己本机上通过System.nanoTime()对两者做了记录,结果如下。
No
JDK动态代理
CGLiib
创建代理对象时间
720 1394
1 3473 7007
代理对象执行方法时间
97 7322
15 2080
一个创建花费时间长,一个执行时间长。
AOP的引用场景
事务,日志,缓存,权限,异常处理等。
1.4.25 在 Spring AOP 中,关注点和横切关注的区别是什么?
关注点是系统中的一个行为,也就是真实业务主体,横切关注就是系统服务。
1.4.26 Spring哪几种类型?
- before:前置通知,在一个方法执行前被调用。
- after: 在方法执行之后调用的通知,无论方法执行是否成功。
- after-returning: 仅当方法成功完成后执行的通知。
- after-throwing: 在方法抛出异常退出时执行的通知。
- around: 在方法执行之前和之后调用的通知。
- Shiro
-
1.5.1简单介绍一下 Shiro 框架
Apache Shiro 是 Java 的一个安全(权限)框架。 Shiro 可以非常容易的开发出足够好的应用,其不仅可以用在 JavaSE 环境,也可以用在 JavaEE 环境。使用Shiro的人越来越多,因为它相当简单,对比Spring Security,可能没有Spring Security做的功能强大,但是在实际工作时可能并不需要那么复杂的东西,所以使用小而简单的Shiro就足够了。
Shiro 可以完成:认证、授权、加密、会话管理、与Web 集成、缓存 等。
1.5.2 Shiro三个核心组件
Subject :正与系统进行交互的人,或某一个第三方服务。所有 Subject 实例都被绑定到(且这是必须的)一个SecurityManager 上。
SecurityManager:Shiro 架构的心脏,用来协调内部各安全组件,管理内部组件实例,并通过它来提供安全管理的各种服务。当 Shiro 与一个 Subject 进行交互时,实质上是幕后的 SecurityManager 处理所有繁重的 Subject 安全操作。
Realms :本质上是一个特定安全的 DAO。当配置 Shiro 时,必须指定至少一个 Realm 用来进行身份验证和/或授权。Shiro 提供了多种可用的 Realms 来获取安全相关的数据。如关系数据库(JDBC),INI 及属性文件等。可以定义自己 Realm 实现来代表自定义的数据源
1.5.3 Shiro 的四大核心部分
Authentication(身份验证):简称为“登录”,即证明用户是谁。
Authorization(授权):访问控制的过程,即决定是否有权限去访问受保护的资源。
Session Management(会话管理):管理用户特定的会话,即使在非 Web 或 EJB 应用程序。
Cryptography(加密):通过使用加密算法保持数据安全
1.5.4 shiro能解决什么问题?
- 认证:验证用户的身份
- 授权:对用户执行访问控制:判断用户是否被允许做某事
- 会话管理:在任何环境下使用 Session API,即使没有 Web 或EJB 容器。
- 加密:以更简洁易用的方式使用加密功能,保护或隐藏数据防止被偷窥
- Realms:聚集一个或多个用户安全数据的数据源
-
1.5.5 Shiro 的四种权限控制方式
1.编程式
通过写if/else授权代码块完成:
Subject subject = SecurityUtils.getSubject();
if(subject.hasRole("admin")) {
//有权限
} else {
//无权限
}
2.注解式
通过在执行的Java方法上放置相应的注解完成:
@RequiresRoles("admin") // 拥有admin角色才能访问该方法
public void hello() {
//有权限
}
// 拥有user:update权限才能方法该方法
@RequiresPermissions("user:update")
@RequestMapping(value = "/update")
public String update() {
System.out.println("UserController.update()");
return "ok";
}
如果要使用注解使用,首先要在配置文件中开启注解(SpringMVC配置文件中)
<!-- 启动shiro注解 -->
<aop:config proxy-target-class="true"></aop:config>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager" />
</bean>
3. 页面标签权限控制
在JSP/GSP页面通过相应的标签完成:
<shiro:hasRole name="admin">
<!— 有权限 —>
</shiro:hasRole>
4.url 级别权限控制
<property name="filterChainDefinitions">
<value>
/admin/**=roles[admin] // 用户必须拥有admin角色才可以访问
/user/**=perms["user:create"] //用户必须拥有user:create权限才可以访问
</value>
</property>
1.5.6 Shiro授权流程
授权也叫访问控制,即在应用中控制谁能访问哪些资源(如访问页面/编辑数据/页面操作等)。在授权中需了解的几个关键对象:主体(Subject)、资源(Resource)、权限(Permission)、角色(Role)。
主体:即访问应用的用户,在Shiro中使用Subject代表该用户。用户只有授权后才允许访问相应的资源。
资源:在应用中用户可以访问的任何东西,比如访问JSP页面、查看/编辑某些数据、访问某个业务方法、打印文本等等都是资源。用户只要授权后才能访问。
权限:安全策略中的原子授权单位,通过权限我们可以表示在应用中用户有没有操作某个资源的权力。
角色:在应用中代表着用户的身份。
授权流程如下:
- 首先调用Subject.isPermitted*/hasRole*接口,其会委托给SecurityManager,而SecurityManager接着会委托给Authorizer;
- Authorizer是真正的授权者,如果我们调用如isPermitted(“user:view”),其首先会通过PermissionResolver把字符串转换成相应的Permission实例;
- 在进行授权之前,其会调用相应的Realm获取Subject相应的角色/权限用于匹配传入的角色/权限;
- Authorizer会判断Realm的角色/权限是否和传入的匹配,如果有多个Realm,会委托给ModularRealmAuthorizer进行循环判断,如果匹配如isPermitted*/hasRole*会返回true,否则返回false表示授权失败。
-
ModularRealmAuthorizer进行多Realm匹配流程:
1、首先检查相应的Realm是否实现了实现了Authorizer;
2、如果实现了Authorizer,那么接着调用其相应的isPermitted*/hasRole*接口进行匹配;
3、如果有一个Realm匹配那么将返回true,否则返回false。
1.5.7 Shiro认证流程
在shiro中,用户需要提供principals (身份)和credentials(证明)给shiro,从而应用能验证用户身份。
principals:身份,即主体的标识属性,可以是任何东西,如用户名、邮箱等,唯一即可。一个主体可以有多个principals,但只有一个Primary principals,一般是用户名/密码/手机号。
credentials:证明/凭证,即只有主体知道的安全值,如密码/数字证书等。
最常见的principals和credentials组合就是用户名/密码了。接下来先进行一个基本的身份认证。
认证流程如下:
- 首先调用Subject.login(token)进行登录,其会自动委托给Security Manager,调用之前必须通过SecurityUtils. setSecurityManager()设置;
- SecurityManager负责真正的身份验证逻辑;它会委托给Authenticator进行身份验证;
- Authenticator才是真正的身份验证者,Shiro API中核心的身份认证入口点,此处可以自定义插入自己的实现;
- Authenticator可能会委托给相应的AuthenticationStrategy进行多Realm身份验证,默认ModularRealmAuthenticator会调用AuthenticationStrategy进行多Realm身份验证;
- Authenticator会把相应的token传入Realm,从Realm获取身份验证信息,如果没有返回/抛出异常表示身份验证失败了。此处可以配置多个Realm,将按照相应的顺序及策略进行访问。