结束
一次完整的面试流程就是这样啦,小编综合了腾讯的面试题做了一份前端面试题PDF文档,里面有面试题的详细解析,分享给小伙伴们,有没有需要的小伙伴们都去领取!
开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】
- 用户发出
http
请求;tomcat
读取web.xml
配置文件;- 通过过滤器读取
Struts.xml
配置文件至内存;- 根据
web.xml
配置,该请求被核心控制器FilterDispatcher
接收(拦截这些请求交由Struts.xml
处理);struts.xml
配置接收到这些请求,找到需要调用的action
类和方法,并通过依赖注入方式,将值注入action
(到对应的servlet
中进行处理);action
调用业务逻辑组件处理业务逻辑,比如基本的数据增删改查、其他的业务处理;action
执行完毕,根据struts.xml
中的配置找到对应的返回结果result
,并跳转到相应页面;
4.1.2 struts2 与 struts1 的区别
从action
类上分析:
- 1.
Struts1
要求Action
类继承一个抽象基类。Struts1
的一个普遍问题是使用抽象类编程而不是接口。Struts 2
Action
类可以实现一个Action
接口,也可实现其他接口,使可选和定制的服务成为可能。Struts2
提供一个ActionSupport
基类去实现常用的接口。Action
接口不是必须的,任何有execute
标识的POJO
对象(就是简单的javabean
)都可以用作Struts2
的Action
对象。
从Servlet
依赖分析:
Struts1 Action
依赖于Servlet API
,因为当一个Action
被调用时,HttpServletRequest
和HttpServletResponse
被传递给execute
方法。Struts 2 Action
不依赖于容器,允许Action
脱离容器单独被测试。如果需要,Struts2 Action
仍然可以访问初始的request
和response
。但是,其他的元素减少或者消除了直接访问HttpServetRequest
和HttpServletResponse
的必要性。
从action
线程模式分析:
Struts1 Action
是单例模式并且必须是线程安全的,因为仅有Action
的一个实例来处理所有的请求。单例策略限制了Struts1 Action
能做的事,并且要在开发时特别小心。Action
资源必须是线程安全的或同步的。Struts2 Action
对象为每一个请求产生一个实例,因此不存在线程安全问题。(实际上,servlet
容器给每个请求产生许多可丢弃的对象,并且不会导致性能和垃圾回收问题)
4.2 Spring
IOC容器的加载过程?
- 创建
IOC
配置文件的抽象资源; - 创建一个
BeanFactory
; - 把读取配置信息的
BeanDefinitionReader
,这里是XmlBeanDefinitionReader
配置给BeanFactory
; - 从定义好的资源位置读入配置信息,具体的解析过程由
XmlBeanDefinitionReade
来完成,这样完成整个载入bean
定义的过程;
说明
XmlBeanFactory
,ClasspathXmlApplicationContext
是IOC
容器实现的两种方式,XmlBeanFactory
是基本的IOC
容器的实现,ApplicationContext
实现的IOC
容器可以提供很多个高级特性(IOC
容器的加载主要以ApplicationContext
实现。)SpringIOC
容器管理了我们定义的各种Bean
对象及其相互关系,Bean
对象在Spring
实现中是以BeanDefinition
来描述;
4.2.1 IOC 容器的实现过程
IOC
容器的初始化过程(定义各种bean
对象以及bean
对象间的关系),整个过程分成resource
的定位、载入和解析。IOC
容器的依赖注入,Bean
的名字通过ApplicationContext
容器,getbean
获得对应的bean
。
4.2.2 Spring IOC的理解
传统创建对象的缺陷:
传统创建对象,通过关键字new
获得。但是如果在不同的地方创建很多相同的对象,不仅占用很大的内存,同时影响性能。
改进思路:
仿造工厂模式,需要什么对象,直接拿过来,按照用户需求,组装相应的产品。以后不再通过new
获取对象,而是需要什么对象,就在spring
容器中取就行了。这就是将创建对象这个行为,进行控制反转,交由容器去完成。什么时候需要这些对象,再通过依赖注入的方式去获取这些对象。
依赖注入的几种方式
- 注解方式注入
<!-- applicationContext.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<!-- 打开Spring的Annotation支持 -->
<context:annotation-config/>
<!-- 设定Spring 去哪些包中找Annotation -->
<context:component-scan base-package="org.zttc.itat.spring"/>
</beans>
// 等价于<bean id="userDAO" class="cn.edu.ujn.dao.impl.UserDAO">
// 公共创建bean的annototion
@Component("userDAO")
// 一般用于DAO的注入,Spring3新增注解方式
@Repository("userDAO")
@Scope("prototype")
public class UserDAO implements IUserDAO {
......
}
@Component("userService")
//@Service一般用于业务层
@Service("userService")
public class UserService implements IUserService {
private IUserDAO userDAO;
public IUserDAO getUserDAO() {
return userDAO;
}
// 默认通过名称进行注入,在JSR330中提供了@Inject来进行注入,需导入相应的包
@Resource
public void setUserDAO(IUserDAO userDAO) {
this.userDAO = userDAO;
}
...
}
@Component("userAction")
//MVC的控制层一般使用@Controller
@Controller("userAction")
@Scope("prototype")
public class UserAction extends ActionSupport {
......
}
注:项目较大时,按照模块进行划分,模块中再按层划分,而非按层(MVC)进行划分;中小型项目按层划分。
2. Setter方法注入
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<!--
创建如下bean等于完成了:HelloWorld helloWorld = new HelloWorld()
-->
<bean id="helloWorld" class="org.zttc.itat.spring.model.HelloWorld" scope="prototype"/>
<!-- 创建了一个User对象user,id为1,username为悟空,如果要注入值不使用ref而是使用value -->
<bean id="user" class="org.zttc.itat.spring.model.User">
<property name="id" value="2"/>
<property name="username" value="八戒"/>
</bean>
<bean id="userDao" class="org.zttc.itat.spring.dao.UserDao"></bean>
<bean id="userJDBCDao" class="org.zttc.itat.spring.dao.UserJDBCDao"></bean>
<!-- autowire=byName表示会根据name来完成注入,
byType表示根据类型注入 ,使用byType注入如果一个类中有两个同类型的对象就会抛出异常
所以在开发中一般都是使用byName
虽然自动注入可以减少配置,但是通过bean文件无法很好了解整个类的结果,所以不建议使用autowire-->
<bean id="userService" class="org.zttc.itat.spring.service.UserService">
<!-- name中的值会在userService对象中调用setXX方法来注入,诸如:name="userDao"
在具体注入时会调用setUserDao(IUserDao userDao)来完成注入
ref="userDao"表示是配置文件中的bean中所创建的DAO的id -->
<property name="userDao" ref="userDao"></property>
</bean>
<!-- 对于UserAction而言,里面的属性的值的状态会根据不同的线程得到不同的值,所以应该使用多例 -->
<bean id="userAction" class="org.zttc.itat.spring.action.UserAction" scope="prototype">
<property name="userService" ref="userService"/>
<property name="user" ref="user"/>
<property name="id" value="12"/>
<!-- 同样可以注入列表,但是也不常用 -->
<property name="names">
<list>
<value>aaa</value>
<value>bbb</value>
<value>ccc</value>
</list>
</property>
</bean>
<!-- 以下是使用构造函数来注入,不常用,基本都是使用set方法注入 -->
<!-- <bean id="userAction" class="org.zttc.itat.spring.action.UserAction" scope="prototype">
<constructor-arg ref="userService"/>
</bean> -->
</beans>
public class UserAction{
private UserService userService;
public String login(){
userService.valifyUser(xxx);
}
public void setUserService(UserService userService){
this.userService = userService;
}
}
3. 构造方法注入
public class UserAction{
private final UserService userService;
public String login(){
userService.valifyUser(xxx);
}
public UserAction(UserService userService){
this.userService = userService;
}
}
三种注入方式对比
- 注解方式注入:适用于中小型项目,不适合大型项目。因为大型项目中存在很多的包、类,书写较复杂,且不易明白项目的整体结构。
- Setter 注入:对于习惯了传统
javabean
开发的程序员,通过setter
方法设定依赖关系更加直观。如果依赖关系较为复杂,那么构造子注入模式的构造函数也会相当庞大,而此时注入模式则更为简洁。- 构造器注入:在构造期间完成一个完整的、合法的对象。 所有依赖关系在构造函数中集中呈现。依赖关系在构造时由容器一次性设定,组件被创建之后一直处于相对“不变”的稳定状态。只有组件的创建者关心其内部依赖关系,对调用者而言,该依赖关系处于“黑盒”之中。如果用到了第三方类库,可能要求我们的组件提供一个默认的构造函数,此时构造器注入模式也不适用。
4.2.3 AOP 切面编程
很多项目上相应的业务处理上上需要事务和日志的管理,然而许多业务上的事务、日志代码又都是一样的,这样就造成了代码重复。
改进思路:
让这些重复的代码抽取出来,让专门的一个类进行管理。
AOP的实现:
在需要的地方加上通知(可以在目标代码前后执行),将相同的业务交由代理类去执行(比如日志、事务),然后再执行目标类,实现了代码复用。
静态代理的缺陷:
相同的业务交由代理类去处理,那么需要日志管理,就要创建一个日志代理类,需要事务管理,就要创建事务代理类……,这样会造成代理类的无限膨胀。
改进措施:
根据这些类,动态创建代理类。所以在这个过程,要实现两个步骤:
1.通过反射机制产生代理类;
2. 代理类要实现一个统一的接口,该接口可对目标类实现额外功能的附加,如在目标类前面加日志、事务等。
基于注解的AOP
实现逻辑如下:
package org.zttc.itat.spring.proxy;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Component("logAspect")//让这个切面类被Spring所管理
@Aspect//申明这个类是一个切面类
public class LogAspect {
/\*\*
\* execution(\* org.zttc.itat.spring.dao.\*.add\*(..))
\* 第一个\*表示任意返回值
\* 第二个\*表示 org.zttc.itat.spring.dao包中的所有类
\* 第三个\*表示以add开头的所有方法
\* (..)表示任意参数
\*/
@Before("execution(\* org.zttc.itat.spring.dao.\*.add\*(..))||" +
"execution(\* org.zttc.itat.spring.dao.\*.delete\*(..))||" +
"execution(\* org.zttc.itat.spring.dao.\*.update\*(..))")
public void logStart(JoinPoint jp) {
//得到执行的对象
System.out.println(jp.getTarget());
//得到执行的方法
System.out.println(jp.getSignature().getName());
Logger.info("加入日志");
}
/\*\*
\* 函数调用完成之后执行
\* @param jp
\*/
@After("execution(\* org.zttc.itat.spring.dao.\*.add\*(..))||" +
"execution(\* org.zttc.itat.spring.dao.\*.delete\*(..))||" +
"execution(\* org.zttc.itat.spring.dao.\*.update\*(..))")
public void logEnd(JoinPoint jp) {
Logger.info("方法调用结束加入日志");
}
/\*\*
\* 函数调用中执行
\* @param pjp
\* @throws Throwable
\*/
@Around("execution(\* org.zttc.itat.spring.dao.\*.add\*(..))||" +
"execution(\* org.zttc.itat.spring.dao.\*.delete\*(..))||" +
"execution(\* org.zttc.itat.spring.dao.\*.update\*(..))")
public void logAround(ProceedingJoinPoint pjp) throws Throwable {
Logger.info("开始在Around中加入日志");
pjp.proceed();//执行程序
Logger.info("结束Around");
}
}
4.2.4 动态代理的实现
动态代理主要实现了两个功能:
- 实现
InvocationHandler
接口中的invoke
方法(主要是被代理类实例、方法、方法参数),该方法就是对被代理对象加的额外操作,如添加日志、权限等。- 在运行时产生一个代理实例(通过反射)。
代码实现如下:
Proxy.newProxyInstance(ClassLoad loader, Class[] interfaces,InvocationHandler h)
// 要代理的真实对象
RealSubject rs = new RealSubject();
// 要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法
InvocationHandler invocationHandler = new DynamicSubject(rs);
Class cl = rs.getClass();
Subject subject = (Subject) Proxy.newProxyInstance(cl.getClassLoader(), cl.getInterfaces(), invocationHandler);
// 代理对象执行方法
subject.request();
过程如下:
- 通过被代理类实例反射获得类类型;
- 然后将这个类类型进行加载(
ClassLoad loader
),实现代理类的接口方法(Class[] interfaces
),实现代理的invoke
方法;
总结:其实动态代理就是通过反射产生代理实例(Proxy.newProxyInstance
),然后通过invoke
实现额外功能的附加和代理方法的聚合。通过被代理类动态生成代理类解决了上面为各个被代理类创建相应的代理类,造成类膨胀的问题。
动态代理实现步骤:
- 创建一个实现接口
InvocationHandler
的类,它必须实现invoke
方法; - 创建被代理的类以及接口(通过被代理类的实例进行反射获得);
- 调用
Proxy
的静态方法,创建一个代理类new ProxyInstance(ClassLoad loader, Class[] interfaces,InvocationHandler h)
。 - 通过代理类返回的实例,去调用被代理类的方法;
4.2.5 jdk代理 和 CGlib动态代理 的区别
1. jdk动态代理
- 只能代理实现了接口
InvocationHandler
的类;- 没有实现
InvocationHandler
接口的类不能实现jdk
的动态代理;
2. CGlib动态代理(通过继承实现)
- 针对类实现代理;
- 对指定目标类产生一个子类,通过方法拦截技术拦截所有父类方法的调用。(其实就是覆写父类的方法,可见
final
修饰的类都不可能实现GCLIB
代理)
4.3 Hibernate
4.3.1 Hibernate执行过程
- 应用程序先调用
Configuration
类,该类读取Hibernate
配置文件(链接数据库)及映射文件(java
对象个数据库表中对象的一一映射)中的信息;- 并用这些信息生成一个
SessionFactory
对象;- 然后从
SessionFactory
对象生成一个Session
对象;- 并用
Session
对象生成Transaction
对象;
4.3.2 Hibernate一级缓存与二级缓存之间的区别(比较mybatis的一二级缓存)
hibernate
的session
提供了一级缓存的功能,默认总是有效的,当应用程序保持持久化实体,修改持久化实体时,session
并不会立即把这种改变提交到数据库,而是缓存在当前的session
中,除非显示调用了session
的flush()
方法或通过close()
方法关闭session
。通过一级缓存,可以减少程序与数据库的交互,从而提高数据库访问的性能。sessionFactory
级别的二级缓存是全局性的,所有的session
可以共享这个二级缓存。不过二级缓存默认是关闭的,需要显示开启并指定需要使用哪种二级缓存实现类(可以使用第三方提供的实现)。一旦开启了二级缓存并设置了需要使用二级缓存的实体类,sessionFactory
就会缓存访问过的该实体类的每个对象,除非缓存的数据超出了指定的缓存空间。
4.3.3 Hibernate 常见优化策略
- 采用合理的缓存策略(二级缓存、查询缓存);
- 采用合理的
session
管理机制;- 尽量使用延迟加载特性(
mybatis
的延迟加载);- 设定合理的批处理参数;
- 如果可以,选用
UUID
作为主键生成器;- 如果可以,选用基于版本号的乐观锁代替悲观锁;
- 在开发过程中,开启
Hibernate_show_sql
选项查看生成的sql,从而了解低层的状况;开发完成后关闭此选项;- 考虑数据库本身的优化,合理的索引,恰当的数据分区策略等都会对持久层的性能带来可观的提升,但是这些需要专业的
DBA
(数据库管理员)提供支持。
4.3.4 hibernate中 get 和 load 的区别(涉及到一个懒加载问题也就是延迟加载)
get
和load
方法是根据id
去获得对应数据。区别如下:
- 执行
get()
方法时就发送sql语句(不支持延迟加载),并且查询的数据存在内存中,在数据库中查不到记录时不会抛出异常,返回一个null
。hibernate
在查询数据的时候(数据在内存或缓存中,就不需要在从数据库中查询了),数据并不存在内存中而是通过cglib
动态产生的一个代理对象,只有对数据进行操作的时候,才加载到内存中,实现了延迟加载,节省了内存开销,提高服务器性能。用到数据时再发sql语句,在数据库中查不到记录会抛异常。
4.3.5 实体对象生命周期
1. translent
(瞬时态)
实体对象在内存中的存在,与数据库中的记录无关,通过session
的save()
或saveOrUpdate
()方法变成持久态;
2. Persisent
(持久态)
该状态下的实体对象与数据库中的数据保持一致;
3. Detached
(托管状态)
操作托管态的数据不能影响到数据库的修改;
五、SSM(Springmvc + spring + mybatis)
5.1 SpringMVC 实现原理
刷面试题
刷题的重要性,不用多说。对于应届生或工作年限不长的人来说,刷面试题一方面能够尽可能地快速自己对某个技术点的理解,另一方面在面试时,有一定几率被问到相同或相似题,另外或多或少也能够为自己面试增加一些自信心,可见适当的刷题是很有必要的。
开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】
-
前端字节跳动真题解析
-
【269页】前端大厂面试题宝典
最后平时要进行自我分析与评价,做好职业规划,不断摸索,提高自己的编程能力和抽象思维能力。大厂面试远没有我们想的那么困难,摆好心态,做好准备,你也可以的。