Java中高级面试题总览(三)

一、Spring面试题

1.1、什么是控制反转(IOC)?什么是依赖注入?

ioc和依赖注入的概念差不多
Spring中的 org.springframework.beans 包和 org.springframework.context包构成了Spring框架IoC容器的基础。

BeanFactory 接口提供了一个先进的配置机制,使得任何类型的对象的配置成为可能。


依赖注入是在编译阶段尚未知所需的功能是来自哪个的类的情况下,将其他对象所依赖的功能对象实例化的模式
在Java中依赖注入有以下三种实现方式:
构造器注入
Setter方法注入
接口注入

1.2、@Component、@Repository   @Service、@Controller有什么区别?

看字面含义,很容易却别出其中三个:
@Controller   控制层,就是我们的action层
@Service        业务逻辑层,就是我们的service或者manager层
@Repository  持久层,就是我们常说的DAO层
而@Component  (字面意思就是组件),它在你确定不了事哪一个层的时候使用。
其实,这四个注解的效果都是一样的,spring都会把它们当做需要注入的Bean加载在上下文中;
但是在项目中,却建议你严格按照除Componen的其余三个注解的含义使用在项目中。这对分层结构的web架构很有好处!!

1.3、Spring框架中的单例bean是线程安全的吗?

不,Spring框架中的单例bean不是线程安全的。

1.4、 解释Spring框架中bean的生命周期(重点)。

  • Spring容器 从XML 文件中读取bean的定义,并实例化bean。
  • Spring根据bean的定义填充所有的属性。
  • --------------------------------------------------------------------(实例化之后调用的回调方法)-------------
  • 如果bean实现了BeanNameAware 接口,Spring 传递bean 的ID 到 setBeanName方法。
  • 如果Bean 实现了 BeanFactoryAware 接口, Spring传递beanfactory 给setBeanFactory 方法。
  • 如果有任何与bean相关联的BeanFactoryPostProcessor,Spring会在postProcessBeanFactory()方法内调用它们。(典型应用PropertyPlaceholderConfigurer)
  • 如果bean实现IntializingBean了,调用它的afterPropertySet方法,如果bean声明了初始化方法,调用此初始化方法。
  • 如果有BeanPostProcessors 和bean 关联,这些bean的postProcessAfterInitialization() 方法将被调用。
  • --------------------------------------------------------------------(销毁之调用的回调方法)-------------
  • 如果bean实现了 DisposableBean,它将调用destroy()方法。
  • --------------------------------------------------------------------------------------------------------------------

1.5、解释AOP

AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码降低模块间的耦合度,并有利于未来的可拓展性和可维护性

Spring AOP就是基于动态代理的,如果要代理的对象,实现了某个接口,那么Spring AOP会使用JDK Proxy,去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候Spring AOP会使用Cglib

1.6、关于面向切面编程的一些术语

  • 切面(Aspect): 切面用于组织多个Advice,Advice放在切面中定义
  • 连接点(Joinpoint): 程序执行过程中明确的点,如方法的调用,或者异常的抛出。在Spring AOP中,连接点总是方法的调用
  • 增强处理(Advice): AOP框架在特定的切入点执行的增强处理。处理有“around”、“before”和“after”等类型
  • 切入点(Pointcut): 可以插入增强处理的连接点。简而言之,当某个连接点满足指定要求时,该连接点将被添加增强处理,该连接点也就变成了切入点

1.7、 Spring AOP 和 AspectJ AOP 有什么区别?

Spring AOP 属于运行时增强,而 AspectJ 是编译时增强。 Spring AOP 基于代理(Proxying),而 AspectJ 基于字节码操作(Bytecode Manipulation)。

Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。AspectJ 相比于 Spring AOP 功能更加强大,但是 Spring AOP 相对来说更简单,

如果我们的切面比较少,那么两者性能差异不大。但是,当切面太多的话,最好选择 AspectJ ,它比Spring AOP 快很多。

1.8、Spring中自动装配的方式有哪些?

  1. no:这是Spring框架的默认设置,在该设置下自动装配是关闭的,开发者需要自行在bean定义中用标签明确的设置依赖关系。
  2. byName:该选项可以根据bean名称设置依赖关系。当向一个bean中自动装配一个属性时,容器将根据bean的名称自动在在配置文件中查询一个匹配的bean。如果找到的话,就装配这个属性,如果没找到的话就报错。
  3. byType:该选项可以根据bean类型设置依赖关系。当向一个bean中自动装配一个属性时,容器将根据bean的类型自动在在配置文件中查询一个匹配的bean。如果找到的话,就装配这个属性,如果没找到的话就报错。
  4. constructor:造器的自动装配和byType模式类似,但是仅仅适用于与有构造器相同参数的bean,如果在容器中没有找到与构造器参数类型一致的bean,那么将会抛出异常。
  5. autodetect:该模式自动探测使用构造器自动装配或者byType自动装配。首先,首先会尝试找合适的带参数的构造器,如果找到的话就是用构造器自动装配,如果在bean内部没有找到相应的构造器或者是无参构造器,容器就会自动选择byTpe的自动装配方式。

1.9、Spring Scope

singleton:一个容器中只存在一个实例,所有对该类型bean的依赖都引用这一单一实例

prototype:容器在getBean(),会每次都重新生成一个新的对象给请求方

request:为每个http的request创建一个bean

session:Spring容器会为每个独立的session创建属于自己的全新的UserPreferences实例

global session:global session只有应用在基于porlet的web应用程序中才有意义,他映射到porlet的global范围的session,如果普通的servlet的web 应用中使用了这个scope,容器会把它作为普通的session的scope对待。

1.10、请举例说明如何在Spring中注入一个Java Collection

Spring提供了以下四种集合类的配置元素:

<list> :   该标签用来装配可重复的list值。
<set> :    该标签用来装配没有重复的set值。
<map>:   该标签可用来注入键和值可以为任何类型的键值对。
<props> : 该标签支持注入键和值都是字符串类型的键值对。

1.11、Spring 框架中都用到了哪些设计模式?

Spring框架中使用到了大量的设计模式,下面列举了比较有代表性的:

工厂设计模式 : Spring使用工厂模式通过 BeanFactory、ApplicationContext 创建 bean 对象。
代理设计模式 : Spring AOP 功能的实现。
单例设计模式 : Spring 中的 Bean 默认都是单例的。
模板方法模式 : Spring 中 jdbcTemplate、hibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。
包装器设计模式 : 我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。
观察者模式: Spring 事件驱动模型就是观察者模式很经典的一个应用。
适配器模式 :Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式适配Controller

1.12、Spring MVC的工作原理是怎样的?

答:Spring MVC的工作原理如下图所示:
这里写图片描述
① 客户端的所有请求都交给前端控制器DispatcherServlet来处理,它会负责调用系统的其他模块来真正处理用户的请求。
② DispatcherServlet收到请求后,将根据请求的信息(包括URL、HTTP协议方法、请求头、请求参数、Cookie等)以及HandlerMapping的配置找到处理该请求的Handler(任何一个对象都可以作为请求的Handler)。
③在这个地方Spring会通过HandlerAdapter对该处理器进行封装。
④ HandlerAdapter是一个适配器,它用统一的接口对各种Handler中的方法进行调用。
⑤ Handler完成对用户请求的处理后,会返回一个ModelAndView对象给DispatcherServlet,ModelAndView顾名思义,包含了数据模型以及相应的视图的信息。
⑥ ModelAndView的视图是逻辑视图,DispatcherServlet还要借助ViewResolver完成从逻辑视图到真实视图对象的解析工作。
⑦ 当得到真正的视图对象后,DispatcherServlet会利用视图对象对模型数据进行渲染。
⑧ 客户端得到响应,可能是一个普通的HTML页面,也可以是XML或JSON字符串,还可以是一张图片或者一个PDF文件。

备注:源码类问题最好自己动手去断点查看印象更加深刻

1.13、Spring初始化Mybatis的过程:

1MapperScannerConfigurer根据spring的xml配置文件中的包路径来初始化以后需要映射的信息

2根据classpath找到mapper的xml配置

3获取到sqlSessionFectory

4调用Mybatis里的checkDaoConfig检查配置文件是否出错

1.14、Spring中如何让A和B两个bean按顺序加载

可能有些场景中,bean A 间接依赖 bean B。如Bean B应该需要更新一些全局缓存,可能通过单例模式实现且没有在spring容器注册,bean A需要使用该缓存;因此,如果bean B没有准备好,bean A无法访问

解决方法:在bean A上使用@DependsOn注解,告诉容器bean B应该先被初始化。

1.15、Spring Bean 的生命周期

一、单一Bean
创建过程
1. 实例化; 
2. 设置属性值; 
3. 如果实现了BeanNameAware接口,调用setBeanName设置Bean的ID或者Name; 
4. 如果实现BeanFactoryAware接口,调用setBeanFactory 设置BeanFactory; 
5. 如果实现ApplicationContextAware,调用setApplicationContext设置ApplicationContext 
6. 调用BeanPostProcessor的预先初始化方法; 
7. 调用InitializingBean的afterPropertiesSet()方法; 
8. 调用定制init-method方法; 
9. 调用BeanPostProcessor的后初始化方法;

销毁过程
1. 调用DisposableBean的destroy(); 
2. 调用定制的destroy-method方法;

二、多个Bean的先后顺序
优先加载BeanPostProcessor的实现Bean
按Bean文件和Bean的定义顺序,确定bean的装载顺序(即使加载多个spring文件时存在id覆盖)
“设置属性值”(第2步)时,遇到ref,则在“实例化”(第1步)之后先加载ref的id对应的bean
AbstractFactoryBean的子类,在第6步之后,会调用createInstance方法,之后会调用getObjectType方法
BeanFactoryUtils类也会改变Bean的加载顺序

1.16、Spring 事务七中传播行为

这里以A业务和B业务之间如何传播事务为例说明:

①、PROPAGATION_REQUIRED :required , 必须。默认值,A如果有事务,B将使用该事务;如果A没有事务,B将创建一个新的事务。

②、PROPAGATION_SUPPORTS:supports ,支持。A如果有事务,B将使用该事务;如果A没有事务,B将以非事务执行。

③、PROPAGATION_MANDATORY:mandatory ,强制。A如果有事务,B将使用该事务;如果A没有事务,B将抛异常。

④、PROPAGATION_REQUIRES_NEW :requires_new,必须新的。如果A有事务,将A的事务挂起,B创建一个新的事务;如果A没有事务,B创建一个新的事务。

⑤、PROPAGATION_NOT_SUPPORTED :not_supported ,不支持。如果A有事务,将A的事务挂起,B将以非事务执行;如果A没有事务,B将以非事务执行。

⑥、PROPAGATION_NEVER :never,从不。如果A有事务,B将抛异常;如果A没有事务,B将以非事务执行。

⑦、PROPAGATION_NESTED :nested ,嵌套。A和B底层采用保存点机制,形成嵌套事务。

PROPAGATION_REQUIRES_NEW 启动一个新的, 不依赖于环境的 "内部" 事务. 这个事务将被完全 commited 或 rolled back 而不依赖于外部事务, 它拥有自己的隔离范围, 自己的锁, 等等. 当内部事务开始执行时, 外部事务将被挂起, 内务事务结束时, 外部事务将继续执行. 

PROPAGATION_NESTED 理解Nested的关键是savepoint。

  1. 定义:保存点是事务中的一个特定点,你可以在该点设置一个标记(即创建一个保存点),然后在需要时回滚到这个点,而不是回滚整个事务。

  2. 作用:在嵌套事务中,如果内部事务(子事务)失败,你可以回滚到保存点,而不影响外部事务(父事务)。这允许更细粒度的错误恢复和事务控制

他与PROPAGATION_REQUIRES_NEW的区别是,PROPAGATION_REQUIRES_NEW另起一个事务,将会与他的父事务相互独立,而Nested的事务和他的父事务是相依的,他的提交是要等和他的父事务一块提交的。也就是说,如果父事务最后回滚,他也要回滚的。 而Nested事务的好处也是他有一个savepoint。子事务回滚不会影响父事务

1.17、在Spring 事务管理中,定义的隔离级别

①、ISOLATION_DEFAULT:使用后端数据库默认的隔离级别

②、ISOLATION_READ_UNCOMMITTED:最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻
读或不可重复读

③、ISOLATION_READ_COMMITTED:允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复
读仍有可能发生

④、ISOLATION_REPEATABLE_READ:对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所
修改,可以阻止脏读和不可重复读,但幻读仍有可能发生

⑤、ISOLATION_SERIALIZABLE:最高的隔离级别,完全服从ACID的隔离级别,确保阻止脏读、不可重复读以
及幻读,也是最慢的事务隔离级别,因为它通常是通过完全锁定事务相关的数据库表

1.18、在Spring  事务超时和、回滚规则

  默认情况下,事务只有遇到运行期异常时才会回滚,而在遇到检查型异常时不会回滚

1.19、Spring事务异常回滚,捕获异常不抛出就不会回滚

   默认spring事务只在发生未被捕获的 runtimeexcetpion时才回滚。  
   spring aop  异常捕获原理:被拦截的方法需显式抛出异常,并不能经任何处理,这样aop代理才能捕获到方
法的异常,才能进行回滚,默认情况下aop只捕获runtimeexception的异常,但可以通过  
    
配置来捕获特定的异常并回滚  
  换句话说在service的方法中不使用try catch 或者在catch中最后加上throw new 
runtimeexcetpion(),这样程序异常时才能被aop捕获进而回滚
  

解决方案: 
  方案1.例如service层处理事务,那么service中的方法中不做异常捕获,或者在catch语句中最后增加throw 
new RuntimeException()语句,以便让aop捕获异常再去回滚,并且在service上层(webservice客户端,
view层action)要继续捕获这个异常并处理

  方案2.在service层方法的catch语句中增加:
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();语句,手动回滚,这样上
层就无需去处理异常

Spring事务不能回滚的深层次原因_int i = 1/0事物不会回滚-CSDN博客

1.20、BeanFactory与ApplicationContext的区别

BeanFactroy采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化,这样,我们就不能发现一些存在的Spring的配置问题。

负责读取bean配置文档,管理bean的加载,实例化,维护bean之间的依赖关系,负责bean的声明周期

ApplicationContext是在容器启动时,一次性创建了所有的Bean。 ApplicationContext继承eanFactory所能提供的功能之外,还提供了更完整的框架功能

a. 国际化支持

b. 资源访问:Resource rs = ctx. getResource(“classpath:config.properties”), “file:c:/config.properties”

c. 事件传递:通过实现ApplicationContextAware接口

1.21、 SpringIOC getBean() 的代码逻辑

1、转化beanName(传入的参数可能是别名)

2、从缓存中加载单例

3、如果缓存中得到了bean,就要对bean进行实例化(只是最原始的bean状态)

4、原型模式下要进行循环依赖检查

5、如果缓存中没有的话直接去父类工厂parentBeanFactory加载

6、寻杂bean依赖的某些属性

7、针对不同的scope创建bean

1.22、Spring有几种配置方式?

将Spring配置到应用开发中有以下三种方式:

  1. 基于XML的配置
  2. 基于注解的配置(常用)
  3. 基于Java的配置

二、网站性能优化面试题

备注:设计类问题考察的目的是懂得理论后能更好的实践

1、你用过的网站前端优化的技术有哪些?

① 浏览器访问优化:
- 减少HTTP请求数量:合并CSS、合并JavaScript、合并图片(CSS Sprite)
- 使用浏览器缓存:通过设置HTTP响应头中的Cache-Control和Expires属性,将CSS、JavaScript、图片等在
浏览器中缓存,当这些静态资源需要更新时,可以更新HTML文件中的引用来让浏览器重新请求新的资源
- 启用压缩
- CSS前置,JavaScript后置
- 减少Cookie传输
② CDN加速:CDN(Content Distribute Network)的本质仍然是缓存,将数据缓存在离用户最近的地方,CDN
通常部署在网络运营商的机房,不仅可以提升响应速度,还可以减少应用服务器的压力。当然,CDN缓存的通常都
是静态资源。
③ 反向代理:反向代理相当于应用服务器的一个门面,可以保护网站的安全性,也可以实现负载均衡的功能,当然
最重要的是它缓存了用户访问的热点资源,可以直接从反向代理将某些内容返回给用户浏览器。

 

2、大型网站在架构上应当考虑哪些问题?

答:- 分层:分层是处理任何复杂系统最常见的手段之一,将系统横向切分成若干个层面,每个层面只承担单一的职责,然后通过下层为上层提供的基础设施和服务以及上层对下层的调用来形成一个完整的复杂的系统。计算机网络的开放系统互联参考模型(OSI/RM)和Internet的TCP/IP模型都是分层结构,大型网站的软件系统也可以使用分层的理念将其分为持久层(提供数据存储和访问服务)、业务层(处理业务逻辑,系统中最核心的部分)和表示层(系统交互、视图展示)。需要指出的是:(1)分层是逻辑上的划分,在物理上可以位于同一设备上也可以在不同的设备上部署不同的功能模块,这样可以使用更多的计算资源来应对用户的并发访问;(2)层与层之间应当有清晰的边界,这样分层才有意义,才更利于软件的开发和维护。

- 分割:分割是对软件的纵向切分。我们可以将大型网站的不同功能和服务分割开,形成高内聚低耦合的功能模块(单元)。在设计初期可以做一个粗粒度的分割,将网站分割为若干个功能模块,后期还可以进一步对每个模块进行细粒度的分割,这样一方面有助于软件的开发和维护,另一方面有助于分布式的部署,提供网站的并发处理能力和功能的扩展。

- 分布式:可以将分布在各处的资源综合利用。而这种利用对用户而言是透明的,可以将负载由单个节点转移到多个,从而提高效率,分布式技术可以避免由于单个节点失效而使整个系统崩溃的危险,

- 集群:集群使得有更多的服务器提供相同的服务,可以更好的提供对并发的支持,解决了数据库宕机带来的单点数据库不能访问的问题;

(分布式,从窄意上理解,也跟集群差不多, 但是它的组织比较松散,不像集群,有一个组织性,一台服务器垮了,其它的服务器可以顶上来。 分布式的每一个节点,都完成不同的业务,一个节点垮了,那这个业务就不可访问了)

 -负载均衡: 有效的降低了单台 机器的访问负载,降低了宕机的可能性;

- 缓存:所谓缓存就是用空间换取时间的技术,将数据尽可能放在距离计算最近的位置。使用缓存是网站优化的第一定律。我们通常说的CDN、反向代理、热点数据都是对缓存技术的使用。


- 异步:异步是实现软件实体之间解耦合的又一重要手段。异步架构是典型的生产者消费者模式,二者之间没有直接的调用关系,只要保持数据结构不变,彼此功能实现可以随意变化而不互相影响,这对网站的扩展非常有利。

-冗余:各种服务器都要提供相应的冗余服务器以便在某台或某些服务器宕机时还能保证网站可以正常工作,同时也提供了灾难恢复的可能性。冗余是网站高可用性的重要保证。

-读写分离:最大限度了提高了应用中读取 (Read)数据的速度和并发量。

3、什么是XSS攻击?什么是SQL注入攻击?什么是CSRF攻击?
答:
- XSS(Cross Site Script,跨站脚本攻击)是向网页中注入恶意脚本在用户浏览网页时在用户浏览器中执行恶意脚本的攻击方式。跨站脚本攻击分有两种形式:反射型攻击(诱使用户点击一个嵌入恶意脚本的链接以达到攻击的目标,目前有很多攻击者利用论坛、微博发布含有恶意脚本的URL就属于这种方式)和持久型攻击(将恶意脚本提交到被攻击网站的数据库中,用户浏览网页时,恶意脚本从数据库中被加载到页面执行,QQ邮箱的早期版本就曾经被利用作为持久型跨站脚本攻击的平台)。XSS虽然不是什么新鲜玩意,但是攻击的手法却不断翻新,防范XSS主要有两方面:消毒(对危险字符进行转义)和HttpOnly(防范XSS攻击者窃取Cookie数据)。

- SQL注入攻击是注入攻击最常见的形式(此外还有OS注入攻击(Struts 2的高危漏洞就是通过OGNL实施OS注入攻击导致的)),当服务器使用请求参数构造SQL语句时,恶意的SQL被嵌入到SQL中交给数据库执行。SQL注入攻击需要攻击者对数据库结构有所了解才能进行,攻击者想要获得表结构有多种方式:(1)如果使用开源系统搭建网站,数据库结构也是公开的(目前有很多现成的系统可以直接搭建论坛,电商网站,虽然方便快捷但是风险是必须要认真评估的);(2)错误回显(如果将服务器的错误信息直接显示在页面上,攻击者可以通过非法参数引发页面错误从而通过错误信息了解数据库结构,Web应用应当设置友好的错误页,一方面符合最小惊讶原则,一方面屏蔽掉可能给系统带来危险的错误回显信息);(3)盲注。防范SQL注入攻击也可以采用消毒的方式,通过正则表达式对请求参数进行验证,此外,参数绑定也是很好的手段,这样恶意的SQL会被当做SQL的参数而不是命令被执行,JDBC中的PreparedStatement就是支持参数绑定的语句对象,从性能和安全性上都明显优于Statement。

- CSRF攻击(Cross Site Request Forgery,跨站请求伪造)是攻击者通过跨站请求,以合法的用户身份进行非法操作(如转账或发帖等)。CSRF的原理是利用浏览器的Cookie或服务器的Session,盗取用户身份。防范CSRF的主要手段是识别请求者的身份,主要有以下几种方式:(1)在表单中添加令牌(token);(2)验证码;(3)检查请求头中的Referer(前面提到防图片盗链接也是用的这种方式)。令牌和验证都具有一次消费性的特征,因此在原理上一致的,但是验证码是一种糟糕的用户体验,不是必要的情况下不要轻易使用验证码,目前很多网站的做法是如果在短时间内多次提交一个表单未获得成功后才要求提供验证码,这样会获得较好的用户体验。

补充:防火墙的架设是Web安全的重要保障,ModSecurity是开源的Web防火墙中的佼佼者。企业级防火墙的架设应当有两级防火墙,Web服务器和部分应用服务器可以架设在两级防火墙之间的DMZ,而数据和资源服务器应当架设在第二级防火墙之后。

3、什么是跨域如何解决?

JavaScript由于安全性方面的考虑,不允许页面跨域调用其他页面的对象,那么问题来了,什么是跨域问题?

答:这是由于浏览器同源策略的限制,现在所有支持JavaScript的浏览器都使用了这个策略。那么什么是同源呢?所谓的同源是指三个方面“相同”:

  1. 域名相同
  2. 协议相同
  3. 端口相同

下面就举几个例子来帮助更好的理解同源策略。

URL说明是否允许通信
http://www.a.com/a.js 
http://www.a.com/b.js
同一域名允许
http://www.a.com/a.js 
http://www.b.com/a.js
不同域名不允许
http://www.a.com:8000/a.js
http://www.a.com/b.js
同一域名不同端口不允许
https://www.a.com/a.js 
http://www.a.com/b.js
同一域名不同协议不允许

第一种解决办法

JSONP(JSON with Padding)是一种早期用来解决跨域数据访问问题的技术。它的工作原理是利用<script>标签的一个特性:不受同源策略限制,可以从不同的域加载资源。

  • 安全性问题:JSONP存在安全风险,因为它允许从其他域执行代码,如果不小心使用,可能导致跨站点脚本攻击(XSS)。
  • 仅限GET请求:JSONP只支持GET请求,不适用于POST、PUT、DELETE等HTTP方法。
  • 错误处理有限:与普通的Ajax请求相比,JSONP的错误处理能力较弱。

第二种解决办法:

在现代的Web开发中,跨域资源共享(CORS,Cross-Origin Resource Sharing)是解决跨域问题的更安全和更灵活的标准。CORS允许服务器通过HTTP头部明确声明哪些源可以加载资源,支持所有类型的HTTP请求,并提供了更强的安全性

以下是针对不同场景的CORS配置:

1. 普通的GET或POST请求

  • 允许所有域的跨域访问
    response.addHeader("Access-Control-Allow-Origin", "*");
  • 允许GET和POST请求
    response.addHeader("Access-Control-Allow-Methods", "GET, POST");

2. AJAX的JSON请求

  • 添加普通跨域请求所需的头部,然后:
    response.addHeader("Access-Control-Allow-Headers", "Content-Type");
  • 设置预检请求的缓存时间(例如:3600秒):
    response.addHeader("Access-Control-Max-Age", "3600");

3. 带有Cookie的跨域请求

  • 指定允许的源(不可以使用*):
    response.addHeader("Access-Control-Allow-Origin", "http://localhost:8020");
  • 允许凭证(如Cookies)
    response.addHeader("Access-Control-Allow-Credentials", "true");

4. 带有自定义Headers的跨域请求

  • 指定允许的自定义请求头
response.addHeader("Access-Control-Allow-Headers", "Content-Type, 自定义1, 自定义2");

三、JVM面试题

 JVM原理讲解和调优_jvm原理及性能调优-CSDN博客

Java中高级面试题总览(一)-CSDN博客

四、Java调优

具体参考http://www.importnew.com/22336.html

在系统层面能够影响应用性能的一般包括三个因素:CPU、内存和IO,可以从这三方面进行程序的性能瓶颈分析。

CPU分析:

找出占用cpu最高的线程

1,使用命令top -p <pid> ,显示你的java进程的内存情况,pid是你的java进程号,可以用jdk自带的jsp命令获取
2,按H,获取每个线程的内存情况
3,找到内存和cpu占用最高的线程pid,比如15248
4,执行 printf 0x%x 15248 得到 0x3b90 ,此为线程id的十六进制
5,执行 jstack 123|grep -A 10 3b90,得到线程堆栈信息中3b90这个线程所在行的后面10行
6,查看对应的堆栈信息找出可能存在问题的代码

也可以使用jstat来查看对应进程的gc信息,以判断是否是gc造成了cpu繁忙。

还可以通过vmstat,通过观察内核状态的上下文切换(cs)次数,来判断是否是上下文切换造成的cpu繁忙。

内存分析:排查堆内存问题的常用工具是jmap,是jdk自带的

IO分析:文件IO可以使用系统工具pidstat、iostat、vmstat来查看io的状况。网络IO查看网络io状况,一般使用的是netstat工具。可以查看所有连接的状况、数目、端口信息等

五、系统架构

5.1、秒杀问题

1、秒杀未开始前,前端统一调用一个秒杀地址暴露接口,判断是否可以调用真正的秒杀地址接口,秒杀地址暴露接口,伴有大量的并发请求秒杀数据的操作,可以放入redis

如果从redis中获得数据,是获得byte的数组,而不是具体的类,因为redis无法帮你反序列化,因此,反序列化的操作应该由你自己完成。protostuff的反序列化效率比Serializable高了很多倍,而且压缩后的空间比Serializable小了10倍左右

2、真正的秒杀接口地址增加md5加密部分,不同的商品的秒杀地址不同,避免了用户利用插件或者猜测拼接url等其他方法,在活动未开始之前,访问秒杀地址。

3、判用户的余额是否可以秒杀该商品,可以的话发条消息,告诉消费者可以进行秒杀

4、在消费者里处理所有的秒杀请求

5、库存为零后,不再消费中间的消息。转去结束画面。

6、将事务SQL在Mysql端进行(存储过程),减少网络延迟和GC带来的时间消耗

7、服务器负载最小化。比如非逻辑处理等一律移去前台,尽可能静态化页面等。

详细代码:

 https://gitee.com/lzhcode/maven-parent/blob/master/lzh-seckill

5.2、如何实现分布式session

目前用户的认证多半是基于表单的认证,基于表单的认证一般会使用 Cookie 来管理Session

用户在登录的时候,会在Web服务器中开辟一段内存空间Session用于保存用户的认证信息和其他信息,用户登录成功之后会通过Set-Cookie的首部字段信息,通知客户端保存Cookie,而这Cookie保存的就是服务器端Session的ID,下次请求的时候客户端会带上该Cookie向服务器端发送请求,服务器端进行校验,如果Session中保存的有该ID的Session就表示用户认证通过,否则失败!

但是一个显著的问题就是,在集群模式下如果通过Nginx负载均衡的时候,如果有一个用户登录的时候请求被分配到服务器A上,登录成功后设置的Session就会存放在服务器A上了,但是在服务器B上却没有该用户的Session数据,当用户再次发起一个请求的时候,此时请求如果被分配到服务器B上,则就不会查询到该用户的登录状态,就会出现登录失败的情况!

一种可以想到的方式就是将多个Web服务器上存储的Session统一存储到某一存储介质中,保证进集群中的每一台机器都可以看到所有相同Session数据,这里的同步体现在所有的Session存储在同一的存储介质里边。

幸运的是我们常用的Tomcat容器已经为我们提供了一个接口,可以让我们实现将Session存储到除当前服务器之外的其他存储介质上,例如Redis等。

Spring Session的本质就是通过实现Tomcat提供的该接口将Session存储到Redis中,以此来实现Session的统一存储管理

下面的代码演示了如何基于cookie实现分布式session

@Service
public class MiaoshaUserService {
	
	
	public static final String COOKI_NAME_TOKEN = "token";
	
	@Autowired
	MiaoshaUserDao miaoshaUserDao;
	
	@Autowired
	RedisService redisService;
	
	public MiaoshaUser getById(long id) {
		return miaoshaUserDao.getById(id);
	}
	

	public MiaoshaUser getByToken(HttpServletResponse response, String token) {
		if(StringUtils.isEmpty(token)) {
			return null;
		}
		MiaoshaUser user = redisService.get(MiaoshaUserKey.token, token, MiaoshaUser.class);
		//延长有效期
		if(user != null) {
			addCookie(response, token, user);
		}
		return user;
	}
	

	public boolean login(HttpServletResponse response, LoginVo loginVo) {
		if(loginVo == null) {
			throw new GlobalException(CodeMsg.SERVER_ERROR);
		}
		String mobile = loginVo.getMobile();
		String formPass = loginVo.getPassword();
		//判断手机号是否存在
		MiaoshaUser user = getById(Long.parseLong(mobile));
		if(user == null) {
			throw new GlobalException(CodeMsg.MOBILE_NOT_EXIST);
		}
		//验证密码
		String dbPass = user.getPassword();
		String saltDB = user.getSalt();
		String calcPass = MD5Util.formPassToDBPass(formPass, saltDB);
		if(!calcPass.equals(dbPass)) {
			throw new GlobalException(CodeMsg.PASSWORD_ERROR);
		}
		//生成cookie
		String token	 = UUIDUtil.uuid();
		addCookie(response, token, user);
		return true;
	}
	
	private void addCookie(HttpServletResponse response, String token, MiaoshaUser user) {
		redisService.set(MiaoshaUserKey.token, token, user);
		Cookie cookie = new Cookie(COOKI_NAME_TOKEN, token);
		cookie.setMaxAge(MiaoshaUserKey.token.expireSeconds());
		cookie.setPath("/");
		response.addCookie(cookie);
	}

}

根据请求的参数类型进行拦截 

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

@Configuration
public class WebConfig  extends WebMvcConfigurerAdapter{
	
	@Autowired
	UserArgumentResolver userArgumentResolver;
	
	@Override
	public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
		argumentResolvers.add(userArgumentResolver);
	}
	
	
}
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

import com.imooc.miaosha.domain.MiaoshaUser;
import com.imooc.miaosha.service.MiaoshaUserService;

@Service
public class UserArgumentResolver implements HandlerMethodArgumentResolver {

	@Autowired
	MiaoshaUserService userService;
	
	public boolean supportsParameter(MethodParameter parameter) {
		//有参数是MiaoshaUser类型时执行resolveArgument方法
		Class<?> clazz = parameter.getParameterType();
		return clazz==MiaoshaUser.class;
	}

	public Object (MethodParameter parameter, ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
		HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
		HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
		
		String paramToken = request.getParameter(MiaoshaUserService.COOKI_NAME_TOKEN);
		String cookieToken = getCookieValue(request, MiaoshaUserService.COOKI_NAME_TOKEN);
		if(StringUtils.isEmpty(cookieToken) && StringUtils.isEmpty(paramToken)) {
			return null;
		}
		String token = StringUtils.isEmpty(paramToken)?cookieToken:paramToken;
		return userService.getByToken(response, token);
	}

	private String getCookieValue(HttpServletRequest request, String cookiName) {
		Cookie[]  cookies = request.getCookies();
		for(Cookie cookie : cookies) {
			if(cookie.getName().equals(cookiName)) {
				return cookie.getValue();
			}
		}
		return null;
	}

}

请求入口 

@Controller
@RequestMapping("/goods")
public class GoodsController {

 
	
    @RequestMapping("/to_list")
    public String list(Model model,MiaoshaUser user) {
    	model.addAttribute("user", user);
        return "goods_list";
    }
    
}
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

数据与后端架构提升之路

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值