2019java后端面试集合篇最值得收藏的(四)

Java面试最值得收藏的文章(共4部分):

2018java后端面试集合篇最值得收藏的(一)

2018java后端面试集合篇最值得收藏的(二)

2018java后端面试集合篇最值得收藏的(三)

2018java后端面试集合篇最值得收藏的(四)

. 数据库篇

mysql优化:

   1.通过开启慢日志查询定位执行速度慢的sql语句,进行分析

在my.ini中:

#开启慢日志

slow_query_log=1

#指明慢日志的地址

slow-query-log-file=d:\mysqlslow.log

#记录没有使用索引的sql语句

#log_queries_not_using_indexes=1

#将查询时间大于1秒的sql语句进行记录

long_query_time=1

把超过1秒的记录在慢查询日志中

可以用mysqlreport来分析。

    2.通过explain查看执行慢的sql语句的执行计划

mysql的执行计划是通过explain select sql语句来分析该sql语句执行过程及其性能。

type这个字段比较重要,它表示MySQL在表中找到所需行的方式.

ALL代表扫描全表

index代表扫描全部索引树

range扫描部分索引常见于between<>等的查询

ref非唯一性索引扫描,返回匹配某个单独值的所有行。常见于使用非唯一索引即唯一索引的非唯一前缀进行的查找

eq_ref唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常见于主键或唯一索引扫描

const, system: 当MySQL对查询某部分进行优化,并转换为一个常量时,使用这些类型访问。如将主键置于where列表中,MySQL就能将该查询转换为一个常量。system是const类型的特例,当查询的表只有一行的情况下, 使用system。

   

key代表mysql实际使用的索引。

    3.进行sql优化,对频繁查询的字段建立索引来提高查询速度

 

 

外键必须加索引。

  1. 避免在 where 子句中对有索引的字段进行运算,这会导致索引失效,从而进行全表扫描。
  2. 在 where 及 order by 涉及的列上建立索引,要尽量避免全表扫描。
  3. 在设计表时要避免表中字段出现null的情况,通常要为其设置默认值,避免在查找时放弃使用索引而进行全表扫描。
  4. SELECT语句中避免使用'*’,只查询需要返回的字段 ,这样可以减少解析sql语句的时间。
  5. 用NOT EXISTS 替换 NOT IN 操作符,用 EXISTS  替换 IN

    4.通过分区来对大表进行操作,提高性能

分区支持的种类比较多,如Range(范围),Hash(哈希),List(预定义列表)

分区等。我们在项目中使用的是Range范围分区。考虑到日志表会随着系统的运行

时间延长而不断的加大。所以我们就按照插入时间对日志表做了范围分区。结合

PARTITION BY RANGE以及VALUES LESS THAN以每年为分界线进行分区。

 数据库设计:

1.根据业务需求,将表与表之间的关系可以分类3大类,分别是一对一,一对多和多对多。在一对一设计时候我们即可以设计成一张表,也可以把一张表中不常用的大字段切分到单独的一张子表中通过一对一主键关联进行连接。如商品表可以将商品的基本信息和商品的描述分为两张表,通过一对一主键关联来建立关联关系。当表与表之间的关系是一对多时通常需要建立两张表并通过主外键进行关联而外键在多的一端。如果表与表之间的关系是多对多则需要通过中间表进行关联。

   

 2.遵守数据库设计的三范式。

第一范式(1NF):强调的是列的原子性,即列不能够再分成其他几列。

     第二范式(2NF): 首先是满足第一范式,另外包含两部分内容,一是表必须有一个主键;二是没有包含在主键中的列必须完全依赖于主键,而不是部分依赖。

     第三范式(3NF): 首先满足第二范式,非主键列直接依赖于主键,消除传递依赖。

3.对于频繁查询的字段加索引并且在编写sql语句时注意sql优化。

外键必须加索引。

  1. 避免在 where 子句中对有索引的字段进行运算,这会导致索引失效,从而进行全表扫描。
  2. 在 where 及 order by 涉及的列上建立索引,要尽量避免全表扫描。
  3. 在设计表时要避免表中字段出现null的情况,通常要为其设置默认值,避免在查找时放弃使用索引而进行全表扫描。
  4. SELECT语句中避免使用'*’,只查询需要返回的字段 ,这样可以减少oracle解析sql语句的时间。
  5. 用NOT EXISTS 替换 NOT IN 操作符,用 EXISTS  替换 IN

4.对于数据量大的表进行分区

分区支持的种类比较多,如Range(范围),Hash(哈希),List(预定义列表)

分区等。我们在项目中使用的是Range范围分区。考虑到日志表会随着系统的运行

时间延长而不断的加大。所以我们就按照插入时间对日志表做了范围分区。结合

PARTITION BY RANGE以及VALUES LESS THAN以每年为分界线进行分区。

 

分区和分表的区别:

分表就是按照一定的规则把一张大表给分成多张小表,在数据库中看到的是几张不同的表,在硬盘上也是不同的文件,因此在进行增删改查操作的时候要根据同样的规则去找到具体操作的表。

分区是按照一定的规则把一张大表分成不同的区块,在数据库中看到的还是一张表,只不过在硬盘上存储的时候分成了几个不同的文件。这样在进行增删改查操作的时候就像操作一张普通的表一样简单方便。

他们都是为了处理单张表的大数据量问题,都能提高性能。

 

表锁和行锁的区别:

【参照:http://blog.csdn.net/ufojoan/article/details/15336427

表锁 锁定的是整张表,行锁 只是锁定指定行。

 

 

悲观锁/乐观锁:

悲观锁(Pessimistic Lock), 每次去查询数据的时候都认为别人会修改,所以每次在查询数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。传统的关系型数据库里边就用到了这种锁机制,比如通过select ....for update进行数据锁定。

乐观锁(Optimistic Lock), 每次去查询数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号,时间戳等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量。

MyISAM/Innodb的区别:

你知道mysql的存储引擎都有哪些吗?[MyISAM/Innodb]

MyISAM用的是表锁,InnoDB用的是行锁。     

MyISAM不支持事务处理,不支持外键而InnoDB支持。

Spring中事务的传播特性以及隔离级别:

【参照:http://blog.csdn.net/qq_33290787/article/details/51924963

spring中事务的传播特性好像有5个左右,

我做项目的时候使用最多的就是PROPAGATION_REQUIRED, 

它所代表的意思支持当前事务,如果当前没有事务,就新建一个事务。

 

spring中事务的隔离级别有5个,默认使用的是ISOLATION_DEFAULT, 

它代表使用数据库默认的事务隔离级别,也是我们项目中最常使用的。

除此之外还有

读未提交:

它充许另外一个事务可以看到这个事务未提交的数据,

这种隔离级别会产生脏读,不可重复读和幻像读。

读提交:

保证一个事务修改的数据提交后才能被另外一个事务读取,

也是大多数数据库的默认值。可以避免脏读,但会产生不可重复读和幻像读。

重复读:

在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。

串行化:

顺序执行事务。除了防止脏读,不可重复读外,还避免了幻像读。

并发性也最低,但最安全。

不可重复读的重点是修改 :

同样的条件 , 你读取过的数据 , 再次读取出来发现值不一样了 。

幻读的重点在于新增或者删除 :

同样的条件 , 第 1 次和第 2 次读出来的记录数不一样。

 

 

 

SQL语句的执行顺序:

查询中用到的关键词主要包含六个,并且他们的顺序依次为 

select--from--where--group by--having--order by 

 

其中select和from是必须的,其他关键词是可选的,这六个关键词的执行顺序 

与sql语句的书写顺序并不是一样的,而是按照下面的顺序来执行 

from--where--group by--having--select--order by

from:需要从哪个数据表检索数据 

where:过滤表中数据的条件 

group by:如何将上面过滤出的数据分组 

having:对上面已经分组的数据进行过滤的条件  

select:查看结果集中的哪个列,或列的计算结果 

order by :按照什么样的顺序来查看返回的数据

 

框架篇

SpringMVC的运行原理:

整个处理过程从一个HTTP请求开始:

1.Tomcat在启动时加载解析web.xml,找到spring mvc的前端总控制器DispatcherServlet,并且通过DispatcherServlet来加载相关的配置文件信息。

2.DispatcherServlet接收到客户端请求,找到对应HandlerMapping,根据映射规则,找到对应的处理器(Handler)。

3.调用相应处理器中的处理方法,处理该请求后,会返回一个ModelAndView。

4.DispatcherServlet根据得到的ModelAndView中的视图对象,找到一个合适的ViewResolver(视图解析器),根据视图解析器的配置,DispatcherServlet将要显示的数据传给对应的视图,最后显示给用户。

 

SpringMVC与struts2的区别

1、springmvc基于方法开发的,通过方法中的参数来接受前台传递过来的参数值,

   struts2基于类开发的,通过声明全局的私有属性并生成get,set方法来接受前台传递过来的参数值。

 

2、springmvc默认单例,即针对所有请求只创建一个实例,struts2默认是原型,即对于每个

   请求都会创建一个新的实例,所以SpringMVC比struts2性能高。

 

Spring MVC中的注解你都用过哪些

SpringMVC中用到过的注解有

@RequestParam它的作用是接受前台传递的参数并且可以通过defaultValue属性对其设置默认值;在SpringMVC进行文件上传的时候也会通过@RequestParam和MultipartFile结合使用。

@Autowired注解和@Resource注解的作用都是为了进行属性注入,但@Autowired默认是按照类型进行匹配,它是Spring提供的注解,@Resource默认是按照名字进行匹配,它是java提供的注解。

在进行restful接口编程的时候我们还会用到@pathvariable注解从路径中获取参数信息以及用到@ResponseBody注解将实体类自动转换为指定的json格式,@RequestBody将前台传递过来的json格式的数据转换为对应的javabean。

@RestController相当于@Controller和@ResponseBody的组合。

@ControllerAdvice和@ExceptionHandler用来进行统一的异常处理。

除此之外还有@Controller,@Service,@Repository分别在控制层,业务逻辑层和持久层的实现类型添加。最后还有@RequestMapping注解在控制层的方法上添加从而将指定url和方法对应起来。

 

 

谈谈你对Spring的理解/Spring的原理

Spring就相当于一个粘合剂,有两个核心,一个核心是IOC (控制反转),它是基于工厂设计模式,所谓控制反转就是将自己手工完成对象创建(new)的这种任务交给spring容器去完成。和控制反转配套使用的还有一个DI也就是依赖注入。我们可以进行构造函数注入,属性注入等,最常用的还是属性注入。可以注入各种类型Map,List,properties

注入可以通过ByType和ByName分别按照类型和名字进行自动注入。

Spring中的Bean支持单例和原型两种方式,默认是单例的。

可以通过singleton=true/false来进行配置或者通过

scope="singleton",scope="prototype"来配置。

所谓单例:即至始至终在jvm中都只有一个该类的实例。

所谓原型:也叫多例,就每次都会创建一个新的对象实例。

 

另一个核心是AOP(面向切面编程/面向方面编程), AOP是OOP(面向对象编程)的延续,主要应用于日志记录,性能统计,安全控制,事务处理等方面。它是基于代理设计模式,而代理设计模式又分为静态代理和动态代理,静态代理比较简单就是一个接口,分别有一个真实实现和一个代理实现,而动态代理分为基于接口的jdk的动态代理和基于类的cglib的动态代理,Aop默认使用的是基于接口的jdk的动态代理。所谓动态代理,即通过代理类的代理,接口和实现类之间可以不直接发生联系,而可以在运行期(Runtime)实现动态关联。Jdk的动态代理要实现InvocationHandler接口并重写其中的invoke方法。

 

AOP的核心概念:

2019java后端面试集合篇最值得收藏的(四)_第1张图片

 

概念解释:

切面(Aspect): 有切点(PointCut)和通知(Advice)组成,它既包括横切逻辑的定义,也包括了连接点的定义。

切点(Pointcut):一个切点定位多个类中的多个方法。

通知也叫增强(Advice):由方位和横切逻辑构成,所谓的方位指的是前置通知,后置通知,返回后通知,环绕通知,抛出异常后通知

连接点(JoinPoint):由切点和方位构成,用来描述在在哪些类的指定方法之前或之后执行

所谓的方位包括:

前置通知(Before advice):在连接点(join point)之前执行的通知,但这个通知不能阻止连接点前的执行(除非它抛出一个异常)。

 

返回后通知(After returning advice): 在连接点(join point)正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回。

 

抛出异常后通知(After throwing advice):在方法抛出异常退出时执行的通知

 

后置通知(After (finally) advice):当连接点退出的时候执行的通知(不论是正常返回还是异常退出)

 

环绕通知(Around Advice):包围一个连接点(join point)的通知,如方法调用。这是最强大的一种通知类型。环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行

你对ORM是怎么理解的?

ORM被称为对象关系映射,我在项目中用到的ORM框架有Hibernate和Mybatis以及Ibatis.其中O指的就是java对象,R指的就是关系型数据库,M指的是java对象和关系型数据库之间的映射关系。

 

jdbc,ibatis,hibernate的区别:

Hibernate属于全自动, Ibatis属于半自动,Jdbc属于手动,从开发效率上讲hibernate较高,ibatis居中,jdbc较低,从执行效率上讲hibernate较低,ibatis居中,jdbc较高,因为jdbc是手工写sql语句,程序员对sql的控制能力更大,可以根据业务需要进行优化,而ibatis虽然也可以对sql进行优化,但是他里面将resultset封装为实体的过程中采用了反射机制所以一定程度上影响了性能,而hibernate因为高度封装所以开发效率相对较高,但正因为这个原因,所以程序员在对sql语句的控制和优化方面相对比较弱,而且在将resultset封装成实体的过程中也采用了反射机制,所以在性能方面较低。

 

SSM整合的流程【问到SSI整合流程就说我们通常用的是SSM】

在项目中通过在web.xml配置springMVC的核心控制器DispatcherServlet并加载Spring-mvc-controller.xml,并且通过配置Spring的监听器contextLoaderListener加载spring-common.xml,之后新建控制层并在类上加入@Controller和@RequestMapping注解,并通过@Resouce注入service层,在

service的实现类上加入@Service注解并通过@Autowired注入dao层,dao层只有接口并没有实现类,是通过在mybatis中对应的含有sql语句的xml文件中来通过namespace指明要实现的dao层的接口,并使sql语句的id和dao层接口中的方法名一致从而明确调用指定dao层接口时要执行的sql语句。并且在spring-mvc-controller.xml中配置了component-scan对controller进行扫描从而使控制层的注解生效还配置了内部视图解析器从而在控制层进行页面跳转时加上指定的前缀和后缀,在spring-common.xml中配置了dbcp数据库连接池以及sqlSession来加载mapper下所有的xml并对所有的mapper层进行扫描也就是对dao层的扫描,

还通过Aop中的切点表达式对service层进行事务控制,并且对service层进行扫描使其注解生效。

 

SSH整合的流程

在项目中首先是通过在web.xml中配置strtus2的前端控制器filterDispatcher加载struts.xml配置文件并对指定的后缀名进行拦截,并且通过配置spring的监听器contextLoadListener加载spring的相关配置文件如

spring-service.xml,spring-dao.xml,spring-common.xml,之后新建控制层的类继承于BaseAction,而BaseAction继承于ActionSupport,在BaseAction中封装了常用的方法如getRealPath(),outJson()等,

之后控制层注入service层,service层注入dao,dao层继承于HibernateDaoSupport并注入spring-common.xml中配置的sessionFactory,sessionFactory注入dataSource连接数据库,注入hibernate.cfg.xml从而加载hbm.xml文件,除此之外还通过Spring中的Aop配置了事务并且通过切点表达式对Servcie层代码进行控制。

 

mybatis中的#和$的区别

1. #将传入的数据根据类型进行相应的转换,如果类型不匹配则报错。如果传入的是字符串类型则会自动加上双引号。

2. $将传入的数据直接显示在sql中。

3. #方式能够很大程度上防止sql注入,$方式无法防止Sql注入,所以一般能用#的就别用$。

 

junit单元测试:

我在编写完自己的功能模块后,为了保证代码的准确性,一般都会使用junit进行单元测试,

当时使用的是junit4这种基于注解的方式来进行单元测试。为了和spring集成获取配置的bean,

通常使用 @RunWith来加载springjunit这个核心类,使用 @ContextConfiguration来加载

相关的配置的文件,通过 @Resource按名字来注入具体的bean,最后在需要测试的方法上面加上

@Test 来进行单元测试。并且在编写单元测试的时候还要遵守一定的原则如:

源代码和测试代码需要分开;测试类和目标源代码的类应该位于同一个包下面,即它们的包名应该一样;

测试的类名之前或之后加上Test,测试的方法名通常也以test开头。

 

基础篇

HashMap的底层代码/原理【http://zhangshixi.iteye.com/blog/672697

HashMap底层就是一个数组结构,数组中的每一项又是一个链表。

当新建一个HashMap的时候,就会初始化一个数组。

Entry就是数组中的元素,每个 Entry 其实就是一个key-value对,

它持有一个指向下一个元素的引用,这就构成了链表。

hashMap在底层将一个key-value当成一个整体进行处理,这个整体就是一个entry对象,

HashMap底层采用一个 Entry[] 数组来保存所有的 key-value 对,

当需要存储一个 Entry 对象时,会根据hash算法来决定其在数组中的存储位置,

再根据equals方法决定其在该数组位置上的链表中的存储位置;当需要取出一个Entry时,

也会根据hash算法找到其在数组中的存储位置,

再根据equals方法从该位置上的链表中取出该Entry。

默认是构建一个初始容量为 16,负载因子为 0.75 的 HashMap。

也就是说,默认情况下,数组大小为16,那么当HashMap中元素个数超过16*0.75=12的时候,

就把数组的大小扩展为 2*16=32,即扩大一倍,然后重新计算每个元素在数组中的位置,

而这是一个非常消耗性能的操作,所以如果我们已经预知HashMap中元素的个数,

那么预设元素的个数能够有效的提高HashMap的性能。

 

ConcurrentHashMap原理:【线程安全的hashmap】

在java并发包里面,ConcurrentHashMap是线程安全的hashMap,通过

引入分段锁的概念将一个大的Map拆分成多个小的HashTable.在存值

和取值得时候通过key.hashCode()来计算key及其对应的值应该放到

哪个HashTable中。默认情况下ConcurrentHashMap会创建16个分段数组集合。

这样在进行操作的时候如果有3个线程A B C,这时候A B两个线程根据key.hashCode()

可能被分配到同一个hashtable中,这样A在进行操作的时候B就会阻塞。但C这个

线程可能被分配到了另外一个hashtable中,这样C就可以直接执行而不会阻塞。

所以效率就得到了极大的提升。默认情况下可以提高16倍。

 

StringBuffer StringBuilder String 区别

1.StringBuilder执行效率高于StringBuffer高于String.

2.String是一个常量,是不可变的,所以对于每一次+=赋值都会创建一个新的对象,StringBuffer和StringBuilder都是可变的,当进行字符串拼接时采用append方法,在原来的基础上进行追加,所以性能比String要高,又因为StringBuffer  是线程安全的而StringBuilder是线程非安全的,所以StringBuilder的效率高StringBuffer.

3.对于大数据量的字符串的拼接,采用StringBuffer,StringBuilder.

 

Get和Post的区别

1.get是从服务器上获取数据,post是向服务器传送数据

2.get传送的数据量较小,不能大于2KB。post传送的数据量较大,一般被默认为不受限制。

3.get安全性非常低,post安全性较高。但是执行效率却比Post方法好。

4.在进行文件上传时只能使用post而不能是get。

 

List,Set,Collection,Collections

List和Set都是接口,他们都继承于接口Collection,List是一个有序的可重复的集合,而Set的无序的不可重复的集合。 Collection是集合的顶层接口,Collections是一个封装了众多关于集合操作的静态方法的工具类,因为构造方法是私有的,所以 不能实例化。

 

List接口实现类有ArrayList,LinkedList,Vector。ArrayList和Vector是基于数组实现的,所以查询的时候速度快,而在进行增加和删除的时候速度较慢LinkedList是基于链式存储结构,所以在进行查询的时候速度较慢但在进行增加和删除的时候速度较快。又因为Vector是线程安全的,所以他和ArrayList相比而言,查询效率要低。

 

CopyOnWriteArrayList及其原理:[线程安全的ArrayList]

CopyOnWriteArrayList是一个线程安全、并且在读操作时无锁的ArrayList。

在调用add方法时,

并没有使用synchronized关键字而是使用ReentrantLock来保证线程安全。并且每次调用add方法都会创建一个新的object数组,此数组的大小为当前数组大小加1,将之前数组中的内容复制到新的数组中,并将新增加的对象放入数组末尾,最后做引用切换将新创建的数组对象赋值给全局的数组对象。

 

 

Hashtable与HashMap的区别 [补充常识:LinkedHashMap是有序的]

1.Map是一个以键值对存储的接口。Map下有两个具体的实现,分别是HashMap和HashTable.

2.HashMap是线程非安全的,HashTable是线程安全的,所以HashMap的效率高于HashTable.

3.HashMap允许键或值为空,而HashTable不允许键或值为空

 

 

OSI七层模型

应用层

表示层

会话层

传输层

网络层

数据链路层

物理层

 

Http协议原理:

HTTP是一个超文本传输协议,属于OSI七层模型的应用层,由请求和响应构成,

是一个标准的客户端服务器模型。HTTP是无状态的也就是说同一个客户端的这次请求和上次请求是没有对应关系。

 

http的工作流程:

当发送一个http请求时,首先客户机和服务器会建立连接,

之后发送请求到服务器,请求中包含了要访问的url地址,请求的方式(get/post),

以及要传递的参数和头信息,服务器接到请求后会进行响应,

包括状态行,状态码,响应头,以及要响应的主体内容。客户端接收

到请求后将其展示到浏览器上然后断开和服务器端的连接。

 

简单说就是:建立连接--》发送请求--》响应--》断开连接

 

什么是长连接、短连接?

在HTTP/1.0中,默认使用的是短连接。也就是说,浏览器和服务器每进行一次HTTP操作,就建立一次连接,

但任务结束就中断连接。

 

从 HTTP/1.1起,默认使用长连接。使用长连接的HTTP协议,会在响应头有加入这行代码:

Connection:keep-alive 

在使用长连接的情况下,当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的 TCP连接不会关闭,

如果客户端再次访问这个服务器上的网页,会继续使用这一条已经建立的连接。

Keep-Alive不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如Apache)中设定这个时间。

实现长连接要客户端和服务端都支持长连接。

 

HTTP协议的长连接和短连接,实质上是TCP协议的长连接和短连接。

 

HTTPS和HTTP的区别主要如下:

1、https协议需要到CA申请证书,一般免费证书较少,因而需要一定费用。

2、http是超文本传输协议,信息是明文传输,https则是SSL+HTTP协议构成的加密传输协议,更加安全

3、http和https用的端口也不一样,前者是默认端口号80,后者是默认端口号443。

JS/ajax相关

你对ajax的理解:

AJAX 全称: 异步JavaScript及 XML。

Ajax的核心是JavaScript中的XmlHttpRequest(XHR)。

使用ajax可以提高用户的体验度,进行异步数据传输从而

提高性能。ajax不能跨域。所谓的不能

跨域就是不能跨多个网站(多个域名),不能跨多个项目。

可以通过jsonp来解决ajax跨域的

问题,而jsonp的实质就是通过动态添加script标签来实现的。

ajax同步异步的属性:

async:true[异步] false[同步]

ajax跨域解决方案及其本质:

.

如何将后台传递的数据转换为json对象:

通过eval可以将后台返回的json格式的字符串转换为json对象。

ajax的超时:

jquery的ajax默认情况下是没有超时限制的,可以通过timeout【单位:毫秒】设置超时时间。

ajax的同步异步:

同步可以理解为执行了方法1,再执行方法2,是顺次执行;异步可以理解为执行方法1的同时执行方法2,是并行执行。

你对jquery的理解:

Jquery是一个js框架,拥有跨浏览器的特性,可以兼容各种浏览器,

可以使用它的append方法、remove方法、insertAfter方法操作文档对象、

通过id选择器$("#id")以及类选择器$(".class")还有标签选择器$("标签名")

可以选择DOM元素、通过fadeIn以及fadeOut制作淡入淡出的动画效果、

通过bind来对指定元素绑定事件、通过$.get,$.post以及$.ajax发送ajax

异步请求。

 

 

 

线程/线程池专题

1.java中实现线程的方式

在java中实现线程有两种方式:继承Thread类,实现Runable接口,一个java main程序默认会开启两个线程一个是主线程,一个垃圾回收线程。

2.线程不安全与安全:

多个线程访问同一个资源,导致结果和期望值不同,我们就说它是线程不安全的,反之我们就说它是 线程安全的。

了解:

a.多个线程访问同一个资源(这里的资源通常指的是全局变量或者静态变量),如果每次运行结果和单线程运行的结果是一样的,就是线程安全的。

 

b.线程安全问题都是由全局变量及静态变量引起的。

 

c.若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;

若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。

 

3.线程的状态

 

1、新建状态(New):新创建了一个线程对象。

2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。

3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。

4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:

(一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。

(二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。

(三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。

5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期

2019java后端面试集合篇最值得收藏的(四)_第2张图片

 

 

 

了解:

1、线程的实现有两种方式,一是继承Thread类,二是实现Runnable接口,但不管怎样,当我们new了这个对象后,线程就进入了初始状态;

2、当该对象调用了start()方法,就进入可运行状态;

3、进入可运行状态后,当该对象被操作系统选中,获得CPU时间片就会进入运行状态;

4、进入运行状态后情况就比较复杂了

 4.1、run()方法或main()方法结束后,线程就进入终止状态;

 4.2、当线程调用了自身的sleep()方法或其他线程的join()方法,就会进入阻塞状态(该状态既停止当前线程,但并不释放所占有的资源)。当sleep()结束或join()结束后,该线程进入可运行状态,继续等待OS分配时间片;

 4.3、线程调用了yield()方法,意思是放弃当前获得的CPU时间片,回到可运行状态,这时与其他进程处于同等竞争状态,OS有可能会接着又让这个进程进入运行状态;

 4.4、当线程刚进入可运行状态(注意,还没运行),发现将要调用的资源被synchronized(同步),获取不到锁标记,将会立即进入锁池状态,等待获取锁标记(这时的锁池里也许已经有了其他线程在等待获取锁标记,这时它们处于队列状态,既先到先得),一旦线程获得锁标记后,就转入可运行状态,等待OS分配CPU时间片;

 4.5、当线程调用wait()方法后会进入等待队列(进入这个状态会释放所占有的所有资源,与阻塞状态不同),进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify()或notifyAll()方法才能被唤醒(由于notify()只是唤醒一个线程,但我们由不能确定具体唤醒的是哪一个线程,也许我们需要唤醒的线程不能够被唤醒,因此在实际使用时,一般都用notifyAll()方法,唤醒有所线程),线程被唤醒后会进入锁池,等待获取锁标记。

2019java后端面试集合篇最值得收藏的(四)_第3张图片

 

 

 

补充:(wait和sleep的区别)

wait时会释放锁资源但sleep不会释放锁资源,wait通常和notify以及notifyAll结合使用,需要notify或者notifyAll对其进行唤醒,sleep通常在指定的时间内自动唤醒。

 

4.解决线程安全的问题的方案:

 

a.通过加锁(synchronized)的方式解决线程安全问题

1.synchronized 方法 
2.synchronized 块(同步代码块)

b.避免使用全局变量

c.使用ThreadLocal(参考:http://blog.csdn.net/drifterj/article/details/7782706)

1. 为多线程并发的互斥控制提供了另一种全新的解决思路

2. 通过ThreadLocal为其他模块的API传递参数

 

5.java线程池 (可参考:http://www.oschina.net/question/565065_86540)

 

1.减少了创建和销毁线程的次数,

每个线程都可以被重复利用,

可执行多个任务。

 

 

2.可以根据系统的承受能力,

调整线程池中线程的数目,

防止因为消耗过多的内存,

而导致服务器宕机

 

(每个线程需要大约1MB内存,线程开的越多,

消耗的内存也就越大,最后宕机)。

 

我们在项目中使用java线程池是为了减少创建和销毁线程的次数,重复利用已经存在的线程,
提高应用程序的性能。通常我们使用的线程池是ThreadPoolExecutor,通过它的execute方法向线程池中提交要执行的任务。

 

 

【补充】线程池中的阻塞队列:我知道的有两种队列,一种是 LinkedBlockingQueue无界队列,一种是ArrayBlockingQueue有界队列。

 

应用场景一:

因为我们定期要向系统中的用户发送短信或者邮件信息进行商品的推广和促销,考虑到用户的数据量会不断增大,为了提高性能,我们就以1000条数据为一个批次,分批从用户表中取出数据,结合spring定时器和线程池进行发送。

 

 

应用场景二:【了解】

我们通常将这种大数据量的操作就通过线程池执行从而提高执行效率。在之前的项目中遇到过要

将指定的产品图片生成缩略图并且加上水印,考虑到图片的数量比较大,每次要处理的图片在5万张左右,所以我就采用了线程池这项技术,将5万张图片分为多个批次,每批次1000张,然后交给线程池中的多个线程去并行处理,这样就大大缩短了生成缩略图的时间。

 

 

 

6.死锁(可参考:http://www.cnblogs.com/simonhaninmelbourne/archive/2012/11/24/2786215.html)

 

死锁是因为多线程访问共享资源,由于访问的顺序不当所造成的,通常是一个线程锁定了一个资源A,而又想去锁定资源B;在另一个线程中,锁定了资源B,而又想去锁定资源A以完成自身的操作,两个线程都想得到对方的资源,而不愿释放自己的资源,造成两个线程都在等待,而无法执行的情况。

死锁产生的原因:是由访问共享资源顺序不当所造成的。

简单的说:所谓死锁,是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。

 

高性能高并发

 

项目模块

前台的评论模块【MongoDB】:

因为整个电商平台商品的评论数据量比较大并且对于评论这部分数据在操作过程中不需要严格的事务控制,所以我们就

考虑到使用Mongodb这个非关系型数据库存储评论信息,包含的字段有评论id,商品id,标题,评分,内容,评论人,发布时间等。

为了提高可用性和高并发用了3台服务器做了mongodb的副本集,其中一台作为主节点,另外两台作为副本节点,这样在任何一台mongodb服务器宕机时就会自动进行故障转移,不会影响应用程序对mongodb的操作。

[补充:如果问到副本集是怎么搭建的,就说我们有专门的运维人员来负责搭建,我只是负责用java程序去进行操作]

统计分析模块:

我负责统计文章录入量和商品录入量,可以分别按照年月日进行统计结合日期范围的查询 条件完成指定时间范围内的统计并且还可以根据类型进行统计分析。

 

当时采用的是Echarts图形报表,根据业务需求在统计的时候完成图表联动。如根据查询条件查询出指定月份范围内指定分类的商品录入量进行柱状图的展示,当点击某月的柱状图时,可以通过饼状图显示该月中该分类下排名前10的各个品牌的录入量。点击更多可以查询所有品牌录入量。

 

AOP在项目中的应用[后台日志管理模块]:

AOP的核心是切面,而切面中包含了切点和通知,切点是为了对指定类的指定方法进行拦截,通知中包含方位和横切逻辑,所谓方位指的是前置通知,后置通知,环绕通知等,横切逻辑指的是公共部分的代码如日志,事务等。

 

在项目中我们通常使用AOP进行事务方面的控制和日志的统一处理。在事务控制方面是通过Spring自带的事务管理器,配置切点表达式,对service层指定的方法如增删该进行事务控制,对查询进行只读事务控制从而提高性能。在日志的统一处理方面,我首先配置log4j.properties并指定日志级别为info,将日志输出到控制台以及指定的日志文件中。接着自己写一个日志的切面类LogAspect,并通过ProceedingJoinPoint【连接点】获取目标类名以及执行的方法名,通过调用LOG.info方法记录进入方法时的日志信息。为了记录出现异常时的错误日志,通过对proceed方法进行trycatch捕获,在catch中用LOG.error记录异常信息。在spring-mvc-controller.xml中配置aop-config,并通过aop-pointcut的切点表达式对所有的Controller和里面的方法进行拦截,最后通过aop-around配置环绕通知,并通过里面的method属性指明要调用切面类中的方法名。

 

为了进行日志记录的时候能够获取请求的url地址,请求参数以及登录用户的一些信息,所以我就需要在LogAspect中获取request对象。当时我采用的方法是通过ThreadLocal和filter过滤器结合起来完成的。ThreadLocal的本质就相当于一个map,它的key为当前线程,value为存入的值。当时我新建了一个WebContext类,在该类中声明了一个
ThreadLocal对象,再结合过滤器在doFilter方法中调用WebContext的setRequest方法将当前的请求对象和线程进行绑定。这样我就可以在LogAspect中通过WebContext的getRequest方法来获得HttpServletRequest对象进而来获取请求的url地址,请求参数等信息。

 

为了方便管理员进行日志的查看,我们还将日志存储到了mysql数据库当中,后台就可以查询到操作的日志信息,但是不能删除和修改只能看。考虑到日志表后期会不断增大为了提高查询性能我们对日志表以月为单位进行了分区。

 

Restful的概述以及项目中应用:

近期开发的项目有一部分是需要给前端提供接口,本来是想通过webservice的形式来提供接口进行数据的

输出,但项目经理要求用restful这种ROA面向资源编程的形式开发接口进行数据的提供,所以我就到网上

查阅了相关的资料,因为我们的项目是基于SpringMVC的,所以最终我就采用了基于SpringMVC进行restful

接口的开发。用到的注解有 @RestController @PathVariable @RequestBody 这些,其中 @RestController

就相当于 @Controller 和 @ResponseBody的组合体,这样就可以避免在各个方法上加入 @ResponseBody注解了,@PathVariable是为了从路径中获取参数信息, @RequestBody 起到的作用就是将前端提交过来的json数据根据相关的配置文件利用jackson这个工具自动转换为对应的javabean实体。

 

再者restful是基于HTTP协议现有的Get动作进行查询,Post动作进行增加,Put动作进行修改,Delete动作进行删除。返回结果我们采用JSON格式的数据,其中用code表示状态码,用message表示提示信息,用data表示返回的数据。为了方便返回的指定格式的JSON数据,我 们自己封装了一个Response对象,该实体对象中就包含code,message,data三项属性,这样我们在Controller层的各个方法中,统一返回的都是我们自定义的Response对象,并且我们也通过自定义的枚举对象StatusEnum来方便进行code和message的统一赋值。

 

写完接口之后我们通常会通过PostMan或者通过junit单元测试使用httpClient去测试接口的正确性,并且会自己编写接口文档,在编写接口文档时通常需要写清楚  接口的业务描述,接口的请求地址,请求方式,请求参数的含义以及接口的响应格式【json/xml】,响应内容,响应内容的各个字段含义。

 

最后我们为了保证restful接口的安全性,采用了基于token的认证方式,它的流程是

用户登录成功后在服务端生成token并将其存入redis中之后服务器返回token给客户端,客户端将token保存在本地,发起后续的相关请求时,将token随着请求头一起发送给服务器,服务器端通过Spring的AOP拦截所有Controller中的方法检查token的有效性,有效则返回数据,若无效则返回相关的错误提示信息。

我们的token是通过uuid来保证唯一性,并且将其存入redis中保证性能,通过uuid来充当key,通过userName来充当值。

 

我们还使用Spring AOP来进行统一的日志处理,写了一个LogAspect类可以记录请求的url路径,请求参数,请求的Controller类中的方法等等;并且使用Spring的@ControllerAdvice以及@ExceptionHandler进行统一的异常处理。

 

这就是整个restful接口的编写过程。当时我是将商品信息,文章信息,分类信息,品牌信息发布出去。

 

shiro

在最近做的一个项目中,我们用shiro充当安全框架进行权限的管理控制。

 

为了在项目中使用Shiro我们在web.xml中配置了shiroFilter使其对所有请求都进行安全控制。

之后我们在shiro的配置文件中配置一个id为shiroFilter的bean,这点要保证和web.xml中filter的名字一致。

 

在进行权限管理时整体上来说分为认证和授权两大核心。

 

认证就是只有用户经过了登录页面的验证才能成为合法用户继而访问后台受保护的资源。

我们在shiro的配置文件中配置基于url路径的安全认证。对一些静态资源如js/css等,

包括登录以及验证码设置为匿名访问。对于其他的url路径设置为authc【认证】即只有经过正常

的登录并且验证成功后才能访问。

为了保证数据库中用户密码的安全性,我们对其密码进行了md5加密处理,又因为单纯的md5加密

比较容易破解,所以我们使用了密码+盐【salt】的方式,这里面的盐是由用户名+随机数构成的,并且

还进行了2次迭代即md5(md5(密码+盐)))这样就更增加了安全性。在用户添加和重置用户密码时

调用PasswordHelper将加密后的密码以及生成的盐即salt都存储到数据库的用户表中。

 

在用户进行登录认证时我们会在登录方法中传入用户名和密码并创建一个UsernamePasswordToken,

之后通过SecurityUtils.getSubject()获得subject对象,接着就可以通过subject获取session信息

进而获取验证码和用户登录时候输入的验证码进行对比,最后调用subject.login()方法。这时

就会去执行我们自定义的UserRealm【继承于AuthorizingRealm】对象中的doGetAuthenticationInfo认证方法。

在该认证方法中token获取用户名,并通过注入的userService根据用户名来获取用户信息,最后将

用户对象,密码,盐构建成一个SimpleAuthenticationInfo返回即可进行验证判断。再通过

subject.isAuthenticated()判断是否通过认证从跳转到合适的页面。

================================================================================================

授权指的是针对不同的用户给予不同的操作权限。

 

我们采用RBAC【Resource-Based Access Control】这种基于资源的访问控制。

 

在数据库设计时涉及到5张核心表,即用户表--》用户角色关联表--》角色表--》角色权限关联表--》权限表【菜单表】。

在后台的系统管理模块中包含用户管理,角色管理,菜单管理,给用户赋角色,给角色赋权限等操作。

这样就建立起了用户和角色之间多对多的关系以及角色和权限之间多对多的关系。

 

角色起到的作用就是包含一组权限,这样当用户不需要这个权限的时候只需要在给角色赋权限的操作中去掉该

权限即可,无需改动任何代码。在前台展示页面中通过shiro:hasPermission标签来判断按钮

是否能够显示出来,从而可以将权限控制到按钮级别。

 

我们的权限表也就是菜单表,在设计菜单表的时候我们有id,pid,menuName,menuUrl,type[menu,button]两种类型,

permission[资源:操作 如product:create article:create article:delete]这几个字段构成。

 

我们后台系统管理的目的就是维护数据。

 

这样当用户登录认证后,我们可以根据用户id作为查询条件,将用户角色关联表,角色表,角色菜单关联表以及

菜单表进行多表联查获得该用户所拥有的菜单信息。从而达到不同的用户显示不同的菜单树。

 

当用户在地址栏输入要访问的URL时,跳转到具体Controller的方法中,因为我们在Controller的方法中都加入了

@RequiresPermissions这个注解,所以shiro就会根据这个注解调用UserRealm中的doGetAuthorizationInfo

方法,该方法中根据用户id查询用户所拥有的权限信息,并将这些权限信息都添加到SimpleAuthorizationInfo。

之后shiro会将该用户所拥有的权限和访问该url所需要的权限做个对比。如果拥有权限则可以访问,否则将会

抛出一个UnauthorizedException未授权的异常。

 

我通过 @ControllerAdvice这个注解结合 @ExceptionHandler会捕获所有控制层抛出来的指定异常,然后根据异常

信息跳转到前台页面显示该用户无权限访问。

=================================================================================================

了解:

shiro核心理论:

Subject:主体,代表了当前“用户”

 

SecurityManager:管理所有的subject

 

Realm:从Realm获取安全数据【如用户、角色、权限】

 

Shiro的对外API核心就是Subject,Subject的所有交互都会委托给SecurityManager,

SecurityManager才是实际的执行者。

 

SecurityManager它是Shiro的核心,就相当于SpringMVC中的DispatcherServlet前端控制器。

 

可以把Realm看成DataSource,即安全数据源。

 

dubbo的概述以及在项目中的应用:

我们在项目中使用dubbo+zookeeper来构建分布式的服务/微服务。

zookeeper用来充当注册中心。默认端口号是2181。

dubbo是一个RPC框架,可以支持很多协议,

如dubbo,rmi,hessian,http等。考虑到我们提供的是

小数据量大并发的服务调用,所以我们就选择了默认的dubbo协议。

 

dubbo协议是通过hessian进行对象的二进制序列化,所以我们的java

对象都实现了Serializable接口。通过使用netty的nio【非阻塞IO】方式

进行异步传输,它是基于tcp协议,提供者和消费者之间建立单一长连接。

 

dubbo框架的体系结构有5个核心组成部分,分别是提供者provider,它的

作用是为消费者提供数据。注册中心registry,它的作用是用来

注册和发现服务。消费者consumer,它的作用是调用远程提供者提供的服务。

监控中心Monitor用来统计服务的调用次数以及调用时间,还有container

用来充当容器来加载,运行服务提供者。

 

 

我们在项目中具体开发时候以商品服务为例。我们首先建立一个shop-product-api项目

里面包含商品接口以及商品实体类。

 

再者我们建立shop-product-service项目使其通过maven的pom文件依赖shop-product-api,

实现其中的接口,用来充当服务提供者。新建dubbo-provider.xml配置文件,通过dubbo:application

配置提供者应用名,通过dubbo:registry配置注册中心的地址,通过dubbo:protocol配置协议,

以及通过dubbo:service来暴露要发布的接口。

 

最后我们在需要使用dubbo接口的项目中配置消费者信息,新建dubbo-consumer.xml文件,通过

dubbo:application配置消费者应用名,通过dubbo:registry指明要订阅的注册中心地址,通过

dubbo:reference指定要订阅的服务接口。

 

除此之外考虑到dubbo的健壮性和性能我们对它的参数项进行的调优。

通过在dubbo:protocol中threadpool="fixed" threads="200"来启用线程池,

通过在dubbo:service中connections=5来指定建立长连接的数量。

 

配置dubbo集群来提高健壮性以及可用性。

dubbo默认的集群容错机制是Failover即失败自动切换,默认的重试次数为2,可以通过retries调整。

dubbo默认的负载均衡策略是Random随机,可以按权重设置随机概率。

我们在写完dubbo提供者之后,为了测试接口的正确性,我们会进行直连测试。首先会在提供者端,

通过将dubbo:registry的register设置为false,使其只订阅服务而不注册现在正在开发的服务;

 

在消费者端,

通过设置dubbo:reference的url,直连提供者进行测试。

 

被动说:

所谓dubbo集群就是将dubbo的提供者部署多份,在不同的机器上或者说在同一台机器上用不同的端口号。

从而在启动时可以向注册中心进行注册,这样结合dubbo的集群容错策略以及负载均衡策略可以提高

可用性。

 

dubbo负载均衡策略:随机,轮询,最少活跃调用数。

dubbo的集群容错:失败自动切换,快速失败,失败安全。

 

Linux上部署项目:

    1.Linux上安装jdk,配置环境变量

    2.通过SSH将tomcat压缩包上传到Linux服务器,通过unzip解压缩

    3.开启防火墙中的8080端口号

    4.通过修改conf/server.xml部署项目

    5.通过chmod +x *.sh开启执行权限

    6.通过./startup.sh启动tomcat访问项目,

    并通过tail -f catalina.out来查看启动日志。

 

你对Maven是怎么理解的

maven是一个项目管理工具,其核心特点就是通过

maven可以进行包的依赖管理,保证jar包版本的一致性,以及可以使多个项目共享

jar包,从而能够在开发大型j2ee应用的时候,减小项目的大小,

maven根据“约定优于配置”的特性,对其项目的编译打包部署进行了

更为抽象的封装,直接使用系统预定好的mvn clean,compile,test,package等命令进行项目的操作。

为了保证团队中的成员能够节省下载jar包所需要的时间,

于是我就采用nexus搭建了在局域网内的maven私服,然后通过配置settings.xml中

建立mirror镜像,将所有下载jar包的请求都转发到maven私服上,之后通过在pom.xml

即(project object model)中配置项目所依赖的jar包,从而达到在构建项目的时候,

先从本地仓库中查找,如果不存在从内部私服查找,如果不存在最后再从外网central

服务器查找的机制,达到了节省下载带宽,提高开发效率,以及jar包重用的目的。

 

 

 

前台的日志管理模块:【15k及其以上必说】

前台的日志模块的核心价值是为了统计用户的行为,方便进行用户行为分析。 考虑到对日志的统一处理以及前台访问量巨大导致的大并发和大数据量的问题。 当时是结合AOP和Mongodb来完成了这项功能。

存日志的时候是通过SpringAOP写了个切面类,之后在切面类可以获取用户使用的

浏览器的类型如是IE还是谷歌,还是火狐;以及用户使用的设备类型如是手机,

还是说是平板,还是说是PC;包括用户浏览过的商品信息,用户购买的商品信息等。将这些信息通过封装好的

MongodbUtil将其插入到mongodb数据库中方便其他系统后续对其进行分析。

考虑到mongodb的高可用性我们搭建了3台mongodb数据库来实现副本集,这样

不仅可以达到故障自动转移的特性而且也可以通过读写分离提高性能,即便

主服务器宕机了,还会通过投票选举出下一个主服务器继续提供服务。考虑到

后续数据量的不断增加,为了方便扩容我们还建立了3个分片。

图片管理模块:【15k及其以上必说】

考虑到我们的项目最后要部署到多台tomcat通过nginx来实现负载均衡,

为了对项目中的文件以及图片进行统一的管理,我们就用mongodb来充当文件服务器。

对于大部分的单张图片和单个文件来说小于16M,所以我们就以常规的方式来将

文件转换为二进制的字节数组进行保存。考虑到高可用性以及为了应对后期随着文件数量的不断

增加而能够方便进行扩容,我们建立了3个分片并将分片和副本集做了整合,每个分片都是一个副本集,这样

不仅满足了大数据量的存储也避免了分片中单台机器导致的单点故障问题。考虑到可能要处理

大于16M的文件,所以又增加了支持大文件存储的gridfs,这样即便再大的文件也会被gridfs分解为多个

chunk进行存储。

Mongodb概述:

Mongodb是一个nosql数据库,我们在项目中通常用它来存储评论信息,

【评论id,商品id,标题,评分,内容,评论人信息,评论的发布时间】

因为每个商品都会有评论信息,而且某些热门商品的评论信息可能是数千条,

mongodb正好适用于这种大数据量、高并发、弱事务的互联网应用。考虑

 

不仅可以达到故障自动转移的特性而且也可以通过读写分离提高性能,即便

主服务器宕机了,还会通过投票选举出下一个主服务器继续提供服务。

再者考虑到我们的项目最后要部署到多台tomcat通过nginx来实现负载均衡,

为了对项目中的文件以及图片进行统一的管理,我们就用mongodb来充当文件服务器。

对于大部分的单张图片和单个文件来说小于16M,所以我们就以常规的方式来将

文件转换为二进制的字节数组进行保存。考虑到高可用性以及为了应对后期随着文件数量的不断

增加而能够方便进行扩容,我们建立了3个分片并将分片和副本集做了整合,每个分片都是一个副本集,这样

不仅满足了大数据量的存储也避免了分片中单台机器导致的单点故障问题。考虑到可能要处理

大于16M的文件,所以又增加了支持大文件存储的gridfs,这样即便再大的文件也会被gridfs分解为多个

chunk进行存储。

商品管理模块的开发,这个模块包含:

商品管理,商品分类管理,商品参数管理,商品属性管理,规格管理,品牌管理;

在数据库中涉及到的表有产品表mm.m. .bv,产品图片表,品牌表,分类表,产品参数组表,产品参数表,产品参数值表等。通常先在商品分类管理模块中建立产品的分类,该分类是一个树形表结构,我通过全部取出数据并对取出的数据进行递归算法生成树形结构的数据,并将其数据渲染为具有层次嵌套关系的下拉列表以及表格控件从而在页面进行使用,因为在后台的很多地方都使用到分类信息,而分类信息又不容易发生变化,所以我就结合oscache将分类信息进行缓存,并封装到BaseController中,从而方便在其他Controller中直接调用该方法。

建立分类后可以在商品参数管理模块中建立和指定分类绑定的参数组以及参数信息,之后在添加产品时就可以选择产品所属的分类然后就会出现和该分类绑定的参74 数组以及参数信息供公司内部业务人员填写,保存后将参数信息保存到参数值表中,普通用户可以在 产品详情页 的 商品参数 中找到。

规格管理,属性管理的操作流程和上述流程类似。

 

报表统计(spring定时器+poi+javamail+echarts)

我在做报表统计这个模块的时候采用了Echarts这项技术,使用了柱状图,饼状图,组合图按照时间以及分类进行了各种统计(销售渠道,客户群体,客户偏好,信息来源,产品分类,产品月销量,季度销量,年销量),除此之外因为经理要求要在每月月底将指定分类的产品信息通过excel的形式发送到相关人员的邮箱中,所以我就采用了spring定时器+poi+javamail的形式完成了上述要求。

 

统计分析模块:

统计模块由 会员统计,店铺统计,销量统计等构成,

其中会员统计可以按天、周、月通过echarts的

折线图对新增的会员进行统计分析,通过echarts的饼状图

对会员所在区域进行统计,并且可以根据会员

的下单量,下单商品件数,下单金额分别进行统计分析

计算出买家排行top10,并支持将这会员信息通过poi导出

excel结合javamail发送到指定人的邮箱中。

 

redis最新版描述

redis是一个基于key,value的支持多种数据类型的可进行持久化的

内存数据库。它支持hash,set,list,string,sortedset等

我们在项目中应用的时候为了保证redis服务器的安全通常会在

redis.conf配置文件中绑定具体的ip地址这样只有该ip地址才能

访问redis服务器,并且设置长度为20位左右的密码,从而保证

只有进行了密码授权才能进行相关的操作。

为了保证redis不会因为占用内存过大而导致系统宕机,通常在

将redis当做缓存服务器使用时,设置存储数据的过期时间,并且

通过设置maxmemory【最大内存】和maxmemory-policy【数据清除策略】

为allkeys-lru来达到预期的效果。

我们在项目中通常使用redis来充当缓存服务器来缓存分类列表,

品牌列表,热销商品,推荐商品以及该商品的关联商品等等。

使用了jedis作为客户端,并考虑到性能问题使用了jedis连接池。

考虑到redis服务器的高可用性,我们做了redis的主从复制,并且

通过加入哨兵来使redis主服务器宕机时,从服务器自动转换为主服务器

继续提供服务。

 

前台的评论模块【MongoDB】:

因为整个电商平台商品的评论数据量比较大并且对于评论这部分数据在操作过程中不需要严格的事务控制,所以我们就

考虑到使用Mongodb这个非关系型数据库存储评论信息,包含的字段评分,内容,评论人,发布时间等。

为了提高可用性和高并发用了3台服务器做了mongodb的副本集,其中一台作为主节点,另外两台作为副本节点,这样在任何一台mongodb服务器宕机时就会自动进行故障转移,不会影响应用程序对mongodb的操作。

[补充:如果问到副本集是怎么搭建的,就说我们有专门的运维人员来负责搭建,我只是负责用java程序去进行操作]

 

JMS最新版描述:

JMS是java消息服务的简称,定义了一系列的接口,是一个消息服务的标准。

ActiveMQ被称为一个JMS Provider,实现了JMS所定义的一系列接口。

MQ是消息队列的简称,分为很多种类,ActiveMQ就是其中之一。

 

MQ在项目中最主要的作用是用来进行项目之间的解耦,异步处理以及

应对高并发进行缓冲,可以起到削峰填谷的作用。它有两种工作模式

P2P以及发布订阅。P2P的模式相当于一对一,也就是一条消息只能被

一个消费者消费,一旦被消费后就会从消息队列中删除;支持消费者离线,

也就是说生产者发送消息到消息队列时即便消费者未启动也不影响,

这条消息会在消息队列中存放着,等到消费者启动后再获取该消息进行消费。

发布订阅模式相当于一对多,也就是一条消息可以同时被多个订阅者消费。

我们在用户注册成功后给用户发邮件这块就使用了ActiveMQ的P2P模式,

常规情况下用户注册后数据插入数据库,并调用邮件发送接口发送邮件,

成功后跳转到注册成功页面,但在发送邮件时如果因为网络等某些原因

导致发送速度过慢那么就会造成用户长久等待从而降低用户的体验度,

所以我们就使用了ActiveMQ来进行异步处理,当将数据插入数据库后,

直接将消息发送到消息队列中,然后马上跳转到注册成功页面,

之后再通过另外一个应用去读取消息队列中的消息进行邮件的发送。

 

我们的电商平台考虑到扩展性和高可用性的特性采用了分布式的架构,将电商平台分为

订单系统,积分系统,库存系统,短信系统等等。当时在订单系统中下订单时需要同时

向积分系统增加积分,库存系统中对该商品减库存以及通过短息系统来给用户发送短息。

考虑到如果使用webservice这种同步远程调用技术的话,如果这些系统中有一个有问题则

会产生连锁反应导致用户下订单都不能成功。所以我就采用ActiveMQ来充当消息中间件

完成各个系统间解耦。在用户下订单后将相关的订单信息发送到消息队列中,而其他各个系统

充当消费者接收消息队列中的消息并完成后续的减库存,发短信,增加积分的功能。

 

你可能感兴趣的:(java面试开箱即用)