本文摘取了很多其他博客里的精华内容,同时又融入了一些自己的理解,希望对大家找工作之前的面试准备有所帮助;
SpringMVC
Spring MVC是一个基于Java的实现了MVC设计模式的请求驱动类型的轻量级Web框架,通过把Model,View,Controller分离,将web层进行职责解耦,把复杂的web应用分成逻辑清晰的几部分,简化开发,减少出错,方便组内开发人员之间的配合
可以支持各种视图技术,而不仅仅局限于JSP;
与Spring框架集成(如IoC容器、AOP等);
清晰的角色分配:前端控制器(dispatcherServlet) , 请求到处理器映射(handlerMapping), 处理器适配器(HandlerAdapter), 视图解析器(ViewResolver)。
支持各种请求资源的映射策略。
作用:接收请求、响应结果,相当于转发器,有了DispatcherServlet 就减少了其它组件之间的耦合度。
作用:根据请求的URL来查找Handler
注意:在编写Handler的时候要按照HandlerAdapter要求的规则去编写,这样适配器HandlerAdapter才可以正确的去执行Handler。
处理器Handler(需要程序员开发)
视图解析器 ViewResolver(不需要程序员开发)
作用:进行视图的解析,根据视图逻辑名解析成真正的视图(view)
View是一个接口, 它的实现类支持不同的视图类型(jsp,freemarker,pdf等等)
springmvc的入口是一个servlet即前端控制器(DispatchServlet),而struts2入口是一个filter过虑器(StrutsPrepareAndExecuteFilter)。
springmvc是基于方法开发(一个url对应一个方法),请求参数传递到方法的形参,可以设计为单例或多例(建议单例),struts2是基于类开发,传递参数是通过类的属性,只能设计为多例。
Struts采用值栈存储请求和响应的数据,通过OGNL存取数据,springmvc通过参数解析器是将request请求内容解析,并给方法形参赋值,将数据和视图封装成ModelAndView对象,最后又将ModelAndView中的模型数据通过reques域传输到页面。Jsp视图解析器默认使用jstl。
转发:在返回值前面加"forward:",譬如"forward:user.do?name=method4"
重定向:在返回值前面加"redirect:",譬如"redirect:http://www.baidu.com"
通过Jackson框架就可以把Java里面的对象直接转化成Js可以识别的Json对象。具体步骤如下 :
加入Jackson.jar
在配置文件中配置json的映射
在接受Ajax方法里面可以直接返回Object,List等,但方法前面要加上@ResponseBody注解。
在web.xml中配置一个CharacterEncodingFilter过滤器,设置成utf-8;
<filter>
<filter-name>CharacterEncodingFilterfilter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilterfilter-class>
<init-param>
<param-name>encodingparam-name>
<param-value>utf-8param-value>
init-param>
filter>
<filter-mapping>
<filter-name>CharacterEncodingFilterfilter-name>
<url-pattern>/*url-pattern>
filter-mapping>
get请求中文参数出现乱码解决方方式有两个
方式一、
修改tomcat配置文件添加编码与工程编码一致,如下:
方式二、
对参数进行重新编码:
String userName = new String(request.getParamter("userName").getBytes("ISO8859-1"),"utf-8")
ISO8859-1是tomcat默认编码,需要将tomcat编码后的内容按utf-8编码。
可以将异常抛给Spring框架,由Spring框架来处理;我们只需要配置简单的异常处理器,在异常处理器中添视图页面即可。
默认情况下是单例模式,所以在多线程访问的时候有线程安全问题,不要用同步,会影响性能的,解决方案是在控制器里面不能写字段(即在控制器里不写成员变量)。单例好处:性能好,不用每次请求都创建对象;
RequestMapping:用于处理请求 url 映射的注解,可用于类或方法上。用于类上,则表示类中的所有响应请求的方法都是以该地址作为父路径。
RequestBody:注解实现接收http请求的json数据,将json转换为java对象。
ResponseBody:注解实现将conreoller方法返回对象转化为json对象响应给客户。
@Controller:@Controller 只是定义了一个控制器类,而使用@RequestMapping 注解的方法才是真正处理请求的处理器.
@Resource与@Autowired:@Resource和@Autowired都是做bean的注入时使用,其实@Resource并不是Spring的注解,它的包是javax.annotation.Resource,需要导入,但是Spring支持该注解的注入。
@PathVariable:用于将请求URL中的模板变量映射到功能处理方法的参数上
@RequestMapping(value="/product/{productId}",method = RequestMethod.GET)
public String getProduct(@PathVariable("productId") String productId){
System.out.println("Product Id : " + productId);
return "hello";
}
@RequestParam:用于将请求参数区数据映射到功能处理方法的参数上
@RequestMapping("/testRequestParam")
public String testRequestParam(@RequestParam("id") int id) {
System.out.println("testRequestParam " + id);
return "success";
}
一般用@Controller注解,也可以使用@RestController,@RestController注解相当于@ResponseBody + @Controller,表示是表现层,除此之外,一般不用别的注解代替
可以在@RequestMapping注解里面加上method=RequestMethod.GET
直接在方法的形参中声明request,SpringMvc就自动把request对象传入
直接在形参里面声明这个参数就可以,但必须名字和传过来的参数一样
直接在方法中声明这个对象,SpringMvc就自动会把属性赋值到这个对象里面
返回值可以有很多类型,有String, ModelAndView。ModelAndView类把视图和数据都合并的一起的,但一般用String比较好
通过ModelMap对象,可以在这个对象里面调用put方法,把对象加到里面,前台就可以通过el表达式拿到
可以在类上面加上@SessionAttributes注解,里面包含的字符串就是要放入session里面的key
有两种写法,一种是实现HandlerInterceptor接口,另外一种是继承适配器类,接着在接口方法当中,实现处理逻辑;然后在SpringMvc的配置文件中配置拦截器即可:
<mvc:interceptors>
<bean id="myInterceptor" class="com.zwp.action.MyHandlerInterceptor">bean>
<mvc:interceptor>
<mvc:mapping path="/modelMap.do" />
<bean class="com.zwp.action.MyHandlerInterceptorAdapter" />
mvc:interceptor>
mvc:interceptors>
注解本质是一个继承了Annotation的特殊接口,其具体实现类是Java运行时生成的动态代理类。我们通过反射获取注解时,返回的是Java运行时生成的动态代理对象。通过代理对象调用自定义注解的方法,会最终调用AnnotationInvocationHandler的invoke方法。该方法会从memberValues这个Map中索引出对应的值。而memberValues的来源是Java常量池;
Spring
Spring帮我们创建了对象,我们称之为Bean,
创建完毕,销毁:销毁分两步,先销毁接口里的方法,再销毁自身的方法
我们很少去修改SpringBean作用域,默认作用域是单例的,Spring使用单例的目的,是使用IOC控制反转解决耦合的情况;也可以设置为prototype;
如果使用默认的singleton,结果是true,代表是同一个对象
如果使用prototype
结果是false,代表是两个不同的对象
SpringBean所有作用域
Spring所有作用域
Spring五种隔离级别
脏读:一个事物读取到另一个事物还没有提交的数据
不可重复读:同一个事物里,两次去读取相同的数据,读取到的结果不一样,产生的原因是另一条事物对这个事物进行了修改
幻读:同一个事物里,查询到的结果多了或者少了,像产生幻觉一样。比如注册用户时,用户名已经有的话给出提示,用户已存在,不可以注册。当两个线程同时去做注册时(用户名都是张三),在数据库里查的时候都没有,都去做insert,线程1先insert进去了,线程2去insert时,被告知数据库里已经有张三这个用户了,即对于线程2来说,前边查的与后边查的,得到的结果不一样(前边查的没有张三,后边查时又有了),此时对于线程2来说就是幻读;
隔离级别
Spring事物传播机制
事物传播机制:指的是在一个方法里,有多个事物的时候,怎么去处理;Spring的传播机制有以下七种:
Spring设计模式
工厂模式:Spring容器就是工厂模式(BeanFactory和ApplicationContext应用了工厂模式)
单例和原型模式:在Bean创建的时候,Spring提供了单例(singleton)和原型模式(protoType)
代理模式:AOP用到的是代理模式(在原有方法上进行增强,但没有改变原代码)、装饰器模式、适配器模式
观察者模式:事件监听器
模版模式:类似JdbcTemplate等模版对象(因为里边已经封装好了一些方法)
每秒钟处理完请求的次数,注意这里是处理完,具体是指发出请求到服务器处理完成功返回结果。可以理解在server中有个counter,每处理一个请求加1,1秒后counter=QPS。
每秒钟处理完的事务次数,一个应用系统1s能完成多少事务处理,一个事务在分布式处理中,可能会对应多个请求,对于衡量单个接口服务的处理能力,用QPS比较合理
优势
速度快,因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1)
支持丰富数据类型,支持string,list,set,sorted set,hash
支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行
丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除
劣势
Redis的主要缺点是数据库容量受到物理内存的限制,不能用作海量数据的高性能读写,因此Redis适合的场景主要局限在较小数据量的高性能操作和运算上。
多线程处理会涉及到锁,而且多线程处理会涉及到线程切换而消耗CPU。因为CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存或者网络带宽。单线程无法发挥多核CPU性能,不过可以通过在单机开多个Redis实例来解决。
string:最基本的数据类型,二进制安全的字符串,最大512M。
list:按照添加顺序保持顺序的字符串列表。
set:无序的字符串集合,不存在重复的元素。
sorted set:已排序的字符串集合。
hash:key-value对的一种集合。
每种数据类型的应用场景
Redis主要提供了两种持久化机制:RDB和AOF
RDB
默认开启,会按照配置的指定时间将内存中的数据快照到磁盘中,创建一个dump.rdb文件,Redis启动时再恢复到内存中。
Redis会单独创建fork()一个子进程,将当前父进程的数据库数据复制到子进程的内存中,然后由子进程写入到临时文件中,持久化的过程结束了,再用这个临时文件替换上次的快照文件,然后子进程退出,内存释放。
需要注意的是,每次快照持久化都会将主进程的数据库数据复制一遍,导致内存开销加倍,若此时内存不足,则会阻塞服务器运行,直到复制结束释放内存;都会将内存数据完整写入磁盘一次,所以如果数据量大的话,而且写操作频繁,必然会引起大量的磁盘I/O操作,严重影响性能,并且最后一次持久化后的数据可能会丢失;
AOF
以日志的形式记录每个写操作(读操作不记录),只需追加文件但不可以改写文件,Redis启动时会根据日志从头到尾全部执行一遍以完成数据的恢复工作。包括flushDB也会执行。
主要有两种方式触发:有写操作就写、每秒定时写(也会丢数据)。
因为AOF采用追加的方式,所以文件会越来越大,针对这个问题,新增了重写机制,就是当日志文件大到一定程度的时候,会fork出一条新进程来遍历进程内存中的数据,每条记录对应一条set语句,写到临时文件中,然后再替换到旧的日志文件(类似rdb的操作方式)。默认触发是当aof文件大小是上次重写后大小的一倍且文件大于64M时触发。
如何选择持久化
当两种方式同时开启时(默认使用RDB),Redis会优先选择AOF还原数据。一般情况下,只要使用默认开启的RDB即可,因为相对于AOF,RDB便于进行数据库备份,并且恢复数据集的速度也要快很多。
开启持久化缓存机制,对性能会有一定的影响,特别是当设置的内存满了的时候,更是下降到几百reqs/s。所以如果只是用来做缓存的话,可以关掉持久化;
缓存穿透
一般的缓存系统,都是按照key去缓存查询,如果不存在对应的value,就应该去后端系统查找(比如Oracle、Mysql等)。一些恶意的请求会故意查询不存在的key,请求量很大,就会对后端系统造成很大的压力。这就叫做缓存穿透。
如何避免
缓存雪崩
当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,会给后端系统带来很大压力。导致系统崩溃。
何如避免
在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
做二级缓存,A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间设置为短期,A2设置为长期
不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。
有A,B,C三个节点的集群,在没有复制模型的情况下,如果节点B失败了,那么整个集群就会以为缺少5501-11000这个范围的槽而不可用。
redis内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略。
Redis集群没有使用一致性hash,而是引入了哈希槽的概念,Redis集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽,集群的每个节点负责一部分hash槽。
异步复制
Redis并不能保证数据的强一致性,这意味这在实际中集群在特定的条件下可能会丢失写操作。
16384个。
Redis集群目前无法做数据库选择,默认在0数据库。
ping
一次请求/响应服务器能实现处理新的请求,即使旧的请求还未被响应。这样就可以将多个命令发送到服务器,而不用等待回复,最后在一个步骤中读取该答复。这就是管道(pipelining),是一种几十年来广泛使用的技术。例如许多POP3协议已经实现支持这个功能,大大加快了从服务器下载新邮件的过程。
事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。
MULTI、EXEC、DISCARD、WATCH ##28、Redis key的过期时间和永久有效分别怎么设置? EXPIRE和PERSIST命令。
尽可能使用散列表(hashes),散列表(是说散列表里面存储的数少)使用的内存非常小,所以你应该尽可能的将你的数据模型抽象到一个散列表里面。比如你的web系统中有一个用户对象,不要为这个用户的名称,姓氏,邮箱,密码设置单独的key,而是应该把这个用户的所有信息存储到一张散列表里面.
一个客户端运行了新的命令,添加了新的数据。Redi检查内存使用情况,如果大于maxmemory的限制, 则根据设定好的策略进行回收。一个新的命令被执行,等等。所以我们不断地穿越内存限制的边界,通过不断达到边界然后不断地回收回到边界以下。如果一个命令的结果导致大量内存被使用(例如很大的集合的交集保存到一个新的键),不用多久内存限制就会被这个内存使用量超越。
LRU算法
Redis2.6开始redis-cli支持一种新的被称之为pipe mode的新模式用于执行大量数据插入工作。
分区可以让Redis管理更大的内存,Redis将可以使用所有机器的内存。如果没有分区,你最多只能使用一台机器的内存。分区使Redis的计算能力通过简单地增加计算机得到成倍提升,Redis的网络带宽也会随着计算机和网卡的增加而成倍增长。
客户端分区就是在客户端就已经决定数据会被存储到哪个redis节点或者从哪个redis节点读取。大多数客户端已经实现了客户端分区。代理分区 意味着客户端将请求发送给代理,然后代理决定去哪个节点写数据或者读数据。代理根据分区规则决定请求哪些Redis实例,然后根据Redis的响应结果返回给客户端。redis和memcached的一种代理实现就是Twemproxy查询路由(Query routing) 的意思是客户端随机地请求任意一个redis实例,然后由Redis将请求转发给正确的Redis节点。Redis Cluster实现了一种混合形式的查询路由,但并不是直接将请求从一个redis节点转发到另一个redis节点,而是在客户端的帮助下直接redirected到正确的redis节点。
涉及多个key的操作通常不会被支持。例如你不能对两个集合求交集,因为他们可能被存储到不同的Redis实例(实际上这种情况也有办法,但是不能直接使用交集指令)。同时操作多个key,则不能使用Redis事务.分区使用的粒度是key,不能使用一个非常长的排序key存储一个数据集(The partitioning granularity is the key, so it is not possible to shard a dataset with a single huge key like a very big sorted set).当使用分区的时候,数据处理会非常复杂,例如为了备份你必须从不同的Redis实例和主机同时收集RDB / AOF文件。分区时动态扩容或缩容可能非常复杂。Redis集群在运行时增加或者删除Redis节点,能做到最大程度对用户透明地数据再平衡,但其他一些客户端分区或者代理分区方法则不支持这种特性。然而,有一种预分片的技术也可以较好的解决这个问题。
如果Redis被当做缓存使用,使用一致性哈希实现动态扩容缩容。如果Redis被当做一个持久化存储使用,必须使用固定的keys-to-nodes映射关系,节点的数量一旦确定不能变化。否则的话(即Redis节点需要动态变化的情况),必须使用可以在运行时进行数据再平衡的一套系统,而当前只有Redis集群可以做到这样。
既然Redis是如此的轻量(单实例只使用1M内存),为防止以后的扩容,最好的办法就是一开始就启动较多实例。即便你只有一台服务器,你也可以一开始就让Redis以分布式的方式运行,使用分区,在同一台服务器上启动多个实例。一开始就多设置几个Redis实例,例如32或者64个实例,对大多数用户来说这操作起来可能比较麻烦,但是从长久来看做这点牺牲是值得的。这样的话,当你的数据不断增长,需要更多的Redis服务器时,你需要做的就是仅仅将Redis实例从一台服务迁移到另外一台服务器而已(而不用考虑重新分区的问题)。一旦你添加了另一台服务器,你需要将你一半的Redis实例从第一台机器迁移到第二台机器。
Twemproxy是Twitter维护的(缓存)代理系统,代理Memcached的ASCII协议和Redis协议。它是单线程程序,使用c语言编写,运行起来非常快。它是采用Apache 2.0 license的开源软件。 Twemproxy支持自动分区,如果其代理的其中一个Redis节点不可用时,会自动将该节点排除(这将改变原来的keys-instances的映射关系,所以你应该仅在把Redis当缓存时使用Twemproxy)。 Twemproxy本身不存在单点问题,因为你可以启动多个Twemproxy实例,然后让你的客户端去连接任意一个Twemproxy实例。 Twemproxy是Redis客户端和服务器端的一个中间层,由它来处理分区功能应该不算复杂,并且应该算比较可靠的。
Redis-rb、Predis等。
Redis有着更为复杂的数据结构并且提供对他们的原子性操作,这是一个不同于其他数据库的进化路径。Redis的数据类型都是基于基本数据结构的同时对程序员透明,无需进行额外的抽象。Redis运行在内存中但是可以持久化到磁盘,所以在对不同数据集进行高速读写时需要权衡内存,应为数据量不能大于硬件内存。在内存数据库方面的另一个优点是, 相比在磁盘上相同的复杂的数据结构,在内存中操作起来非常简单,这样Redis可以做很多内部复杂性很强的事情。 同时,在磁盘格式方面他们是紧凑的以追加的方式产生的,因为他们并不需要进行随机访问。
给你举个例子: 100万个键值对(键是0到999999值是字符串“hello world”)在我的32位的Mac笔记本上 用了100MB。同样的数据放到一个key里只需要16MB, 这是因为键值有一个很大的开销。 在Memcached上执行也是类似的结果,但是相对Redis的开销要小一点点,因为Redis会记录类型信息引用计数等等。当然,大键值对时两者的比例要好很多。64位的系统比32位的需要更多的内存开销,尤其是键值对都较小时,这是因为64位的系统里指针占用了8个字节。 但是,当然,64位系统支持更大的内存,所以为了运行大型的Redis服务器或多或少的需要使用64位的系统。
如果你使用的是32位的Redis实例,可以好好利用Hash,list,sorted set,set等集合类型数据,因为通常情况下很多小的Key-Value可以用更紧凑的方式存放到一起。
info
如果达到设置的上限,Redis的写命令会返回错误信息(但是读命令还可以正常返回。)或者你可以将Redis当缓存来使用配置淘汰机制,当Redis达到内存上限时会冲刷掉旧的内容。
可以在同一个服务器部署多个Redis的实例,并把他们当作不同的服务器来使用,在某些时候,无论如何一个服务器是不够的, 所以,如果你想使用多个CPU,你可以考虑一下分片(shard)。
List、Set、Sorted Set他们最多能存放多少元素?理论上Redis可以处理多达232的keys,并且在实际中进行了测试,每个实例至少存放了2亿5千万的keys。我们正在测试一些较大的值。任何list、set、和sorted set都可以放232个元素。换句话说,Redis的存储极限是系统中的可用内存值。
(1) Master最好不要做任何持久化工作,如RDB内存快照和AOF日志文件
(2) 如果数据比较重要,某个Slave开启AOF备份数据,策略设置为每秒同步一次
(3) 为了主从复制的速度和连接的稳定性,Master和Slave最好在同一个局域网内
(4) 尽量避免在压力很大的主库上增加从库
(5) 主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3…这样的结构方便解决单点故障问题,实现Slave对Master的替换。如果Master挂了,可以立刻启用Slave1做Master,其他不变。
RDB持久化方式能够在指定的时间间隔能对你的数据进行快照存储.AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以redis协议追加保存每次写的操作到文件末尾.Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大.如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化方式.你也可以同时开启两种持久化方式, 在这种情况下, 当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整.最重要的事情是了解RDB和AOF持久化方式的不同,让我们以RDB持久化方式开始。
一般来说, 如果想达到足以媲美PostgreSQL的数据安全性, 你应该同时使用两种持久化功能。如果你非常关心你的数据, 但仍然可以承受数分钟以内的数据丢失,那么你可以只使用RDB持久化。有很多用户都只使用AOF持久化,但并不推荐这种方式:因为定时生成RDB快照(snapshot)非常便于进行数据库备份, 并且 RDB 恢复数据集的速度也要比AOF恢复的速度要快,除此之外, 使用RDB还可以避免之前提到的AOF程序的bug。
针对运行实例,有许多配置选项可以通过 CONFIG SET 命令进行修改,而无需执行任何形式的重启。 从 Redis 2.2 开始,可以从 AOF 切换到 RDB 的快照持久性或其他方式而不需要重启 Redis。检索 ‘CONFIG GET *’ 命令获取更多信息。但偶尔重新启动是必须的,如为升级 Redis 程序到新的版本,或者当你需要修改某些目前 CONFIG 命令还不支持的配置参数的时候。
Shiro是一个强大易用的java安全框架,提供了认证、授权、加密、会话管理、与web集成、缓存等功能,对于任何一个应用程序,都可以提供全面的安全服务,相比其他安全框架,shiro要简单的多。
Subject
主体,代表了当前“用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是Subject,如爬虫、机器人等,即Subject是一个抽象概念;所有Subject都绑定到SecurityManager,与Subject的所有交互都会委托给SecurityManager;可以把Subject认为是一个门面;SecurityManager才是实际的执行者。
SecurityManager
安全管理器,即所有与安全有关的操作都会与SecurityManager交互,且它管理着所有Subject;可以看出它是shiro的核心, SecurityManager相当于SpringMVC中的DispatcherServlet前端控制器。
Realm
域,shiro从Realm获取安全数据(如用户、角色、权限),Realm是与持久层交互的桥梁,就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源。
principals
身份,即主体的标识属性,可以是任何东西,如用手机号、户名、邮箱等,唯一即可。
credentials
证明/凭证,即只有主体知道的安全值,如密码/数字证书等。
身份认证流程
Authenticator及AuthenticationStrategy
自定义实现认证时一般继承AbstractAuthenticationStrategy即可
授权,也叫访问控制,即在应用中控制谁能访问哪些资源(如访问页面/编辑数据/页面操作等)。在授权中需了解的几个关键对象:主体(Subject)、资源(Resource)、权限(Permission)、角(Role)
授权方式:
授权流程:
hasRole
接口,其会委托给SecurityManager,而SecurityManager接着会委托给Authorizer;hasRole
会返回true, 否则返回false表示授权失败。编码/解码
Shiro提供了base64和16进制字符串编码/解码的API支持,方便一些编码解码操作
//编码
Base64.encodeToString(str.getBytes())
//解码
Base64.decodeToString(base64Encoded)
散列算法
常见散列算法如MD5,SHA等
生成随机数
SecureRandomNumberGenerator randomNumberGenerator = new SecureRandomNumberGenerator();
randomNumberGenerator.setSeed(“159”.getBytes());
String hex = randomNumberGenerator.nextBytes().toHex();
加密/解密
提供对称式加密/解密算法的支持,如AES、Blowfish等
PasswordService/CredentialsMatcher用于提供加密密码及验证密码服务
Shiro默认提供了PasswordService实现DefaultPasswordService;CredentialsMatcher实现PasswordMatcher及HashedCredentialsMatcher(更强大)
HashedCredentialsMatcher实现密码验证服务
Shiro提供了CredentialsMatcher的散列实现HashedCredentialsMatcher,和PasswordMatcher不同的是,它只是用于密码验证,且可以提供自己的盐,而不是随机生成盐,且生成密码散列值的算法需要自己写,因为能提供自己的盐;
定义Realm(自定义Realm继承AuthorizingRealm即可)
AuthenticationInfo的两个作用
基于表单登录拦截器
onPreHandle主要流程:
任意角色授权拦截器
流程:
默认拦截器
身份验证相关的
authc 基于表单的拦截器,即验证成功之后才能访问 /=authc
authcBasic Basic HTTP身份验证拦截器,主要属性:applicationName
logout 退出 /logout=logout
user 用户拦截器 /=user
anon 匿名拦截器,一般用于静态资源过滤 /static/=anon
授权相关的
roles 角色授权拦截器,主要属性:loginUrl,unauthorizedUrl /admin/=roles[admin]
perms 权限授权拦截器 /user/**=perms[“user:create”]
port 端口拦截器,主要属性: port(80) /test=port[80]
rest rest风格拦截器 /users=rest[user],会自动拼接出“user:read,user:create,user:update,user:delete”
ssl ssl拦截器,只有请求协议是https才能通过
Session
所谓session,即用户访问应用时保持的连接关系,在多次交互中应用能够识别出当前访问的用户是谁,且可以在多次交互中保存一些数据。
Subject subject = SecurityUtils.getSubject();
Session session = subject.getSession();
session.getId(); // 获取当前session的唯一标识
session.getHost(); // 获取当前Subject的主机地址,该地址是通过HostAuthenticationToken.getHost()提供的
session.getTimeOut(); // 获取超时时间
session.setTimeOut(); // 设置超时时间(不设置默认是全局过期时间)
session.touch(); // 更新最后访问时间
session.stop(); // 销毁session,当Subject.logout()时会自动调用stop方法来销毁会话。如果在web中,调用javax.servlet.http.HttpSession.invalidate()也会自动调用shiro session.top方法进行销毁shiro的会话
session.setAttribute(“key”,”123”); // 设置session属性
session.getAttribute(“key”); // 获取session属性
session.removeAttribute(“key”); // 删除属性
注:Shiro提供的会话可以用于javaSE/javaEE环境,不依赖于任何底层容器,可以独立使用,是完整的会话模块。
Session manager 会话管理器
会话管理器管理着应用中所有Subject的会话的创建、维护、删除、失效、验证等工作。是Shiro的核心组件,顶层组件SecurityManager直接继承了SessionManager,且提供了SessionSecurityManager实现直接把会话管理委托给相应的SessionManager、DefaultSecurityManager及DefaultWebSecurityManager 默认SecurityManager都继承了SessionSecurityManager。
Shiro提供了三个默认实现
记住我
登录的记住我
登陆过,即是游客身份都继承Collection接口
ArrayList
有序可重复,底层是数组结构,查询快,增删慢
LinkedList
有序可重复,底层是链表结构,查询慢,增删快
Vector
Vector使用了synchronized方法-线程安全,性能上比ArrayList差一点
和List一样,继承Collection接口,不同的是Set集合是不可重复的(不一定是无序的),并且最多只能允许一个null值。Set常见的实现类有:HashSet、TreeSet和LinkedHashSet。
HashSet
HashSet是一个没有重复元素的集合。它是由HashMap实现的,不能保证元素的顺序,重要的是HashSet允许使用null元素。
HashSet是非同步的。如果多个线程同时访问一个hashset,而其中至少一个线程修改了该hashset,那么它必须保持外部同步。
TreeSet
是一个有序的集合,是一个set集合。继承abstractset,实现了navigableset,cloneable,serializable接口。
LinkedHashSet
是一个有序集合
HashMap、HashTable都继承Map接口
HashMap
线程不安全,使用HashMap要注意避免集合的扩容,它会很耗性能,根据元素的数量给它一个初始大小的值(HashMap是数组和链表组成的,默认大小为16,当hashmap中的元素个数超过数组大小*loadFactor(默认值为0.75)时就会把数组的大小扩展为原来的两倍大小,然后重新计算每个元素在数组中的位置)。HashMap的键值都可以为NULL,HashTable不行。
HashMap是数组+链表+红黑树(JDK1.8增加了红黑树部分)实现的,采用哈希表来存储的;
HashTable
线程安全,key、value不可以为null;
LinkedHashMap
按照添加顺序存储元素(按自然顺序存储元素则使用TreeMap,按照自定义顺序存储元素也使用TreeMap(Comparetor c))
有没有有顺序的 Map 实现类? 如果有, 他们是怎么保证有序的?
TreeMap和LinkedHashMap是有序的(TreeMap默认升序,LinkedHashMap则记录了插入顺序)。
第一范式:原子件,要求每一列的值不能再拆分了。
第二范式: 一张表只描述一个实体(若列中有冗余数据,则不满足)
第三范式: 所有列与主键值直接相关。
原子性(Atomic): 事务中的各项操作,要么全做要么全不做,任何一项操作的失败都会导致整个事务的失败。
一致性(Consistent): 事务前后数据的完整性必须保持一致。
隔离性(Isolated):多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离。
持久性(Durable):持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。
Dml 数据操做语言,如select、update、delete,insert
Ddl 数据定义语言,如create table 、drop table、Truncate 等等
Dcl 数据控制语言, 如 commit、 rollback、grant、 invoke等
建索引的原则
索引缺点
Length 长度、 lower 小写、upper 大写, to_date 转化日期, to_char转化字符
Ltrim 去左边空格、 rtrim去右边空格,substr取字串,add_month增加或者减掉月份、to_number转变为数字
Oracle中使用rownum来进行分页, 这个是效率最好的分页方法,hibernate也是使用rownum来进行oralce分页的
select * from
( select rownum r,a from tabName where rownum <= 20 )
where r > 10
死锁
就是存在加了锁而没有解锁,可能是使用锁没有提交或者没有回滚事务;如果是表级锁则不能操作表,客户端处于等在状态,如果是行级锁则不能操作锁定行;
解决死锁
select b.owner,b.object_name,a.session_id,a.locked_mode
from v$locked_object a,dba_objects b
where b.object_id = a.object_id;
select b.username,b.sid,b.serial#,logon_time
from v$locked_object a,v$session b
where a.session_id = b.sid order by b.logon_time;
alter system kill session "sid,serial#";
读未提交(Read uncommitted):
这种事务隔离级别下,select语句不加锁。
此时,可能读取到不一致的数据,即“读脏 ”。这是并发最高,一致性最差的隔离级别。
读已提交(Read committed):
可避免 脏读 的发生。
在互联网大数据量,高并发量的场景下,几乎 不会使用 上述两种隔离级别。
可重复读(Repeatable read):
MySql默认隔离级别。
可避免脏读 、不可避免重复读的发生。
串行化(Serializable ):
可避免 脏读、不可重复读、幻读 的发生。
以上四种隔离级别最高的是 Serializable 级别,最低的是 Read uncommitted 级别,当然级别越高,执行效率就越低。像 Serializable 这样的级别,就是以 锁表 的方式(类似于Java多线程中的锁)使得其他的线程只能在锁外等待,所以平时选用何种隔离级别应该根据实际情况。在MySQL数据库中默认的隔离级别为Repeatable read (可重复读) 。
在MySQL数据库中,支持上面四种隔离级别,默认的为Repeatable read (可重复读) ;而在 Oracle数据库 中,只支持Serializable (串行化) 级别和 Read committed (读已提交) 这两种级别,其中默认的为 Read committed(读已提交) 级别。
SpringBoot 是 Spring 开源组织下的子项目,是 Spring 组件一站式解决方案,主要是简化了使用 Spring 的难度,简省了繁重的配置,提供了各种启动器,开发者能快速上手。Spring Boot 与传统项目最大的区别是,传统项目都是打成 WAR 包部署到服务器上面,需要额外的 Servlet 容器, 而 Spring Boot 则可以直接打成 jar包,并内置集成了 Servlet 容器,通过命令 java -jar xx.jar 则可以直接运行,不需要独立的 Servlet 容器。
一、独立运行
Spring Boot而且内嵌了各种servlet容器,Tomcat、Jetty等,现在不再需要打成war包部署到容器中,Spring Boot只要打成一个可执行的jar包就能独立运行,所有的依赖包都在一个jar包内。
二、简化配置
spring-boot-starter-web启动器自动依赖其他组件,简少了maven的配置。相对于SpringMVC,SpringBoot无需XML配置文件就能完成所有配置工作
Spring Boot 中有以下两种配置文件
bootstrap与application 的区别
Spring Cloud 构建于 Spring Boot 之上,在 Spring Boot 中有两种上下文,一种是 bootstrap,,另外一种是 application,,bootstrap 是应用程序的父上下文,也就是说 bootstrap 加载优先于 applicaton。bootstrap 主要用于从额外的资源来加载配置信息,还可以在本地外部配置文件中解密属性。这两个上下文共用一个环境,它是任何Spring应用程序的外部属性的来源。bootstrap 里面的属性会优先加载,它们默认也不能被本地相同配置覆盖。
所以,对比 application 配置文件,bootstrap 配置文件具有以下几个特性:
boostrap 由父 ApplicationContext 加载,比 applicaton 优先加载
boostrap 里面的属性不能被覆盖
bootstrap与application 的应用场景
application 配置文件这个容易理解,主要用于 Spring Boot 项目的自动化配置。
bootstrap 配置文件有以下几个应用场景:
启动类上面的注解是@SpringBootApplication,它也是 SpringBoot 的核心注解,其主要组合包含以下 3 个注解:
@SpringBootConfiguration:组合了 @Configuration 注解,实现配置文件的功能。
@EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选项,如关闭数据源自动配置功能: @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })。
@ComponentScan:Spring组件扫描。
两种方式
方式一
继承spring-boot-starter-parent项目
>
>org.springframework.boot >
>spring-boot-starter-parent >
>1.5.6.RELEASE >
>
方式二
导入spring-boot-dependencies项目依赖
>
>
>
>org.springframework.boot >
>spring-boot-dependencies >
>1.5.6.RELEASE >
>pom >
>import >
>
>
>
如果你想在Spring Boot启动的时候运行一些特定的代码,你可以实现接口ApplicationRunner或者CommandLineRunner,这两个接口实现方式一样,它们都只提供了一个run方法。
使用方式:
package com.qf.service.impl;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
/**
* @Author: sgw
* @Date 2020/3/8 16:12
* @Description: TODO
**/
@Component
public class MyBean implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
System.out.println("启动项目的时候执行这里的代码");
}
}
启动顺序
如果启动的时候有多个ApplicationRunner和CommandLineRunner,想控制它们的启动顺序,可以实现 org.springframework.core.Ordered接口或者使用 org.springframework.core.annotation.Order注解。
读取application文件
在application.yml或者properties文件中添加:
info.address=China
方式一、在程序里的属性上加注解读取
@Value("${info.address}")
private String addr;
方式二、在类上加注解,添加前缀后,将属性名称对应上即可
@Component
@ConfigurationProperties(prefix="info")
public class MyTest{
private String addr;
//get/set方法......
}
读取指定文件
资源目录下建立config/db-config.properties:
db.username=root
db.password=123456
方式一、@PropertySource+@Value注解读取方式:
package com.qf.service.impl;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
/**
* @Author: sgw
* @Date 2020/3/8 16:12
* @Description: TODO
**/
@Component
@PropertySource(value = {"config/db-config.properties"})
public class MyBean {
@Value("${db.username}")
private String userName;
@Value("${db.password}")
private String passWord;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassWord() {
return passWord;
}
public void setPassWord(String passWord) {
this.passWord = passWord;
}
}
注意:@PropertySource不支持yml文件读取。
方式二、@PropertySource+@ConfigurationProperties注解读取方式:
Spring Boot支持Java Util Logging,Log4j2,Lockback作为日志框架,如果你使用starters启动器,Spring Boot将使用Logback作为默认日志框架。无论使用哪种日志框架,Spring Boot都支持配置将日志输出到控制台或者文件中。
spring-boot-starter启动器包含spring-boot-starter-logging启动器并集成了slf4j日志抽象及Logback日志框架。
自定义日志文件
根据不同的日志框架,默认加载的日志配置文件的文件名,放在资源根目录下,其他的目录及文件名不能被加载
既然默认自带了Logback框架,Logback也是最优秀的日志框架,往资源目录下创建一个logback-spring.xml即可。
强烈推荐使用logback-spring.xml作为文件名,因为logback.xml加载太早。
添加依赖
>
>org.springframework.boot >
>spring-boot-devtools >
>true >
>
在配置文件里配置热部署
IDEA需要设置:
ctrl+Alt+shift+/
StringBuffer:线程安全,效率低;
StringBuilder:线程不安全,效率高;
字符串比较
String x = "string";
String y = "string";
String z = new String("string");
System.out.println(x==y); // true,引用相同
System.out.println(x==z); // false,==:string比较引用,开辟了新的堆内存空间,所以false
System.out.println(x.equals(y)); // true,equals:string:比较值,相同
System.out.println(x.equals(z)); // true,equals:string比较值,相同
字符串反转
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append("abcdefg");
System.out.println(stringBuffer.reverse()); // gfedcba
字符串常用方法
indexOf():返回指定字符的索引。
charAt():返回指定索引处的字符。
replace():字符串替换。
trim():去除字符串两端空白。
split():分割字符串,返回一个分割后的字符串数组。
getBytes():返回字符串的 byte 类型数组。
length():返回字符串长度。
toLowerCase():将字符串转成小写字母。
toUpperCase():将字符串转成大写字符。
substring():截取字符串。
equals():字符串比较。
IO流种类
按功能来分:输入流(input)、输出流(output)。
按类型来分:字节流和字符流。
字节流和字符流的区别是:字节流按 8 位传输以字节为单位输入输出数据,字符流按 16 位传输以字符为单位输入输出数据。
Files的常用方法都有哪些
Files.exists():检测文件路径是否存在。
Files.createFile():创建文件。
Files.createDirectory():创建文件夹。
Files.delete():删除一个文件或目录。
Files.copy():复制文件。
Files.move():移动文件。
Files.size():查看文件个数。
Files.read():读取文件。
Files.write():写入文件。
1、Exception和Error的区别
Exception和Error都继承自Throwable,在Java中只有Throwable类型的实例才可以被抛出或捕获。
Error指正常情况下不太可能出现的情况,绝大部分的Error或导致程序崩溃,处于非正常的不可恢复的状态,如OutOfMemoryError、StackOverflowError。是程序中不应该试图捕获的严重问题。
Exception是程序正常运行中可以预料的意外情况,可以捕获并处理。
2.运行时异常和一般异常的区别
受检查异常(一般异常):在编译时被强制检查的异常。在方法的声明中声明的异常。
(举例:ClassNotFoundException、IOException)
不受检查异常(运行时异常,都继承自RuntimeException):不受检查异常通常是在编码中可以避免的逻辑错误,根据需求来判断如何处理,不需要再编译期强制要求。
3.写出几种常见的运行时异常(考察编程经验)
运行时异常RuntimeException是所有不受检查异常的基类。
NullPointerException、ClassCastException、NumberFormatException、IndexOutOfBoundsException。
自定义servlet,继承HttpServlet即可,重写init、doGet、doPost、destory方法。servlet是单例的,也就是说在整个容器里只有一个servlet对象;
在web.xml里将servlet与请求地址对应起来:
自定义Servlet:
servlet生命周期
上边的步骤:1-2是初始化阶段,3-4是使用阶段,5是销毁阶段,其中第一步延迟加载,指的是在第一次访问的时候才加载,可以在web.xml里配置为启动容器时就加载,如下
连接池的作用
打开关闭数据库连接非常耗时,频繁开关连接的话就会消耗系统资源,连接池可以控制并发数量
可以控制并发数量(比如应用在队列里排队,拿到数据库连接后才可以使用)
连接池工作机制
服务器在启动的时候会建立一定数量的连接池,并一直维持不少于此数目的连接池;
客户端程序连接时,池驱动程序会返回一个为使用的池连接并将其标记为忙;
如果当前没有空闲连接,则池驱动程序会新建一定数量的连接,新建连接具体的数量由配置决定,但是不会超过最大连接数;
当使用的池连接调用完之后,池驱动程序就会将此链接标记为空闲,其他调用者就可以使用这个连接了;
先介绍几个简单概念
主机A向主机B发送数据的同时,主机A也可以接收主机B发过来的数据;
在TCP报文包里,有六个标志位 ,这里了介绍其中的两个:SYN包与ACK包;
SYN包:请求建立连接的数据包,SYN=1,则表示要建立连接;
ACK包:回应数据包(用来做回应的),表示接收到了对方的某个数据包,仅当ACK=1时,确认号字段才有效;
seq序列号:用来标记数据包的顺序;
ack确认号:表示序号为确认号减去1的数据包及其以前的所有数据包已经正确接收,也就是说他相当于下一个准备接收的字节的序号 (如果ack确认号是101,则表示前100个都已经收到了);
当我们发一个SYN=1的包时,会得到一个ACK=1的包;
三次握手
第一次握手:建立连接时,客户端发送数据包,标志位SYN=1,随机seq=x到服务器(x代表随机生成的数)
第二次握手:服务器收到SYN=1的包,知道客户端要建立连接,返回SYN=1和ACK=1,ack=x+1,和随机seq=y (y代表随机生成的数)
第三次握手:客户端厚道服务器的SYN+ACK包,向服务器发送确认包ACK=1,ack=y+1