spring面试题
1.Spring的核心?
答:控制反转(IoC)和面向切面(AOP)
2.什么是IOC?
答:
1.把对象的创建交给外部的容器,程序中只需要接收获取对象即可。如类A要调用类B的方法,以前我们都是在类A中,通过自身new一个类B,然后在调用类B的方法,现在我们把new类B的事情交给spring来做,使用java的反射机制,根据配置文件在运行时动态的去创建对象以及管理对象,并调用对象的方法.
3.spring常用的注入方式
答:
常用的注入方式主要有三种:构造方法注入,setter注入,基于注解的注入
1.构造方法注入
类中编写有参构造方法,在xml中配置对应bean的构造器参数
2.setter注入
类中编写set方法,在xml配置对应bean的方法参数
3.基于注解的注入
使用@Resource,@Autowired进行依赖注入
4.什么是AOP?
答:
AOP主要用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),可用于日志、事务等处理。比如做日志切面类,事务的切面类.
如何做一个切面类呢?
1.使用@Aspect注解将一个java类定义为切面类
2.使用@Pointcut定义一个切入点,可以是一个规则表达式
3.根据需要在切入点不同位置的切入内容,就是根据aop的通知类型进行切入。
比如:
【前置通知】 在执行目标对象方法之前执行
【后置通知】 在执行目标对象方法之后执行, 出现异常不执行。
【异常通知】 在执行目标对象方法出现异常时候才执行
【最终通知】 在执行目标对象方法之后执行,始终执行
【环绕通知】 环绕目标方法执行(相当于前面四种通知的结合体)
使用@Before在切入点开始处切入内容
使用@After在切入点结尾处切入内容
使用@AfterReturning在切入点return内容之后切入内容(可以用来对处理返回值做一些加工处理)
使用@Around在切入点前后切入内容,并自己控制何时执行切入点自身的内容
使用@AfterThrowing用来处理当切入内容部分抛出异常之后的处理逻辑
import java.util.Arrays;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
/**
* 项目日志 切面
* 2019年1月5日
*/
@Aspect
@Component
public class WebLogAspect {
private static final Logger logger = LoggerFactory.getLogger(WebLogAspect.class);
private static final String PLATFORM_NAME = "xxx项目名称";
/**
* 统计时间
*/
ThreadLocal<Long> startTime = new ThreadLocal<>();
@Pointcut("execution(public * com.web.controller..*.*(..))")
public void webLog(){}
@Before("webLog()")
public void doBefore(JoinPoint joinPoint) throws Throwable {
startTime.set(System.currentTimeMillis());
// 接收到请求,记录请求内容
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
//打印请求参数
Map<String, String[]> paramMap = request.getParameterMap();
if(paramMap != null && paramMap.size() > 0) {
StringBuffer paramSbf = new StringBuffer();
for(String mapKey:paramMap.keySet()) {
String[] mapValue = paramMap.get(mapKey);
//添加判断
if(mapValue != null && mapValue.length > 0) {
for(String paramStr:mapValue) {
if(StringUtils.isNotBlank(paramStr)) {
paramSbf.append("参数"+mapKey+"=");
paramSbf.append(paramStr);
paramSbf.append(";");
}
}
}//END if
}//END for
//打印日志参数
logger.info(PLATFORM_NAME+"-->request请求参数PARAM : " + paramSbf);
}//END if
// 记录下请求内容
logger.info(PLATFORM_NAME+"-->request请求URL : " + request.getRequestURL().toString());
logger.info(PLATFORM_NAME+"-->request请求方法HTTP_METHOD : " + request.getMethod());
logger.info(PLATFORM_NAME+"-->request请求方法IP : " + getIP(request));
logger.info(PLATFORM_NAME+"-->request请求类方法CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
logger.info(PLATFORM_NAME+"-->request请求ARGS : " + Arrays.toString(joinPoint.getArgs()));
}
@AfterReturning(returning = "ret", pointcut = "webLog()")
public void doAfterReturning(Object ret) throws Throwable {
// 处理完请求,返回内容
logger.info(PLATFORM_NAME+"-->response请求响应结果RESULT: " + ret);
logger.info(PLATFORM_NAME+"-->response请求响应时间= 【" + (System.currentTimeMillis() - startTime.get())+"】毫秒");
}
/**
*
* @param request
* @return
*/
private static String getIP(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
}
5.spring 支持几种 bean 的作用域?
singleton(单例模式):bean在每个spring ioc容器中只有一个实例
prototype(原型模式):一个bean的定义可以有多个实例
request:每次http请求都会创建一个bean,该作用域仅在基于web的SpringApplicationContext情形下有效
session:在Http Session中,一个bean定义对应一个实例,该作用域仅在基于web的SpringApplication情形下有效
global-session(全局会话):在一个全局的HTTP Session中,一个bean定义对应一个实例
6.spring事务
spring事务包括声明式事务和编程式事务
7.怎样做spring声明式事务
xml方式
1.配置事务管理器
2.配置事务通知规则
3.Aop配置(Aop配置 = 切入点表达式 + 事务通知规则)
注解方式
1.配置事务管理器
2.事务注解支持
3.方法上使用@Transactional
springboot方式
1.使用注解 @EnableTransactionManagement 开启事务支持后
2.方法上使用@Transactional
8.spring的传播事务
1.PROPAGATION_REQUIRED(默认的事务传播行为)
含义:表示当前方法必须运行在事务中。如果当前事务存在,方法将会在该事务中运行,否则,会启动一个新的事务。
举例有两个方法:
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
methodB();
// do something
}
@Transactional(propagation = Propagation.REQUIRED)
public void methodB() {
// do something
}
单独调用methodB方法时,因为当前上下文不存在事务,所以会开启一个新的事务。
调用methodA方法时,因为当前上下文不存在事务,所以会开启一个新的事务。当执行到methodB时,methodB发现当前上下文有事务,因此就加入到当前事务中来。
2.PROPAGATION_SUPPORTS
含义:如果存在一个事务,支持当前事务。如果没有事务,则非事务的执行。
举例有两个方法:
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
methodB();
// do something
}
// 事务属性为SUPPORTS
@Transactional(propagation = Propagation.SUPPORTS)
public void methodB() {
// do something
}
单纯的调用methodB时,methodB方法是非事务的执行的。当调用methdA时,methodB则加入了methodA的事务中,事务地执行。
3.PROPAGATION_MANDATORY
含义:如果已经存在一个事务,支持当前事务。如果没有一个活动的事务,则抛出异常
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
methodB();
// do something
}
// 事务属性为MANDATORY
@Transactional(propagation = Propagation.MANDATORY)
public void methodB() {
// do something
}
当单独调用methodB时,因为当前没有一个活动的事务,则会抛出异常throw new IllegalTransactionStateException(“Transaction propagation ‘mandatory’ but no existing transaction found”);当调用methodA时,methodB则加入到methodA的事务中,事务地执行
4.PROPAGATION_REQUIRES_NEW
如果一个事务已经存在,则先将这个存在的事务挂起,自己开启新事务,自己事务执行完,再恢复挂起的事务
例子:
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
doSomeThingA();
methodB();
doSomeThingB();
// do something else
}
// 事务属性为REQUIRES_NEW
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB() {
// do something
}
当调用
main{
methodA();
}
相当于调用
main(){
TransactionManager tm = null;
try{
//获得一个JTA事务管理器
tm = getTransactionManager();
tm.begin();//开启一个新的事务
Transaction ts1 = tm.getTransaction();
doSomeThing();
tm.suspend();//挂起当前事务
try{
tm.begin();//重新开启第二个事务
Transaction ts2 = tm.getTransaction();
methodB();
ts2.commit();//提交第二个事务
} Catch(RunTimeException ex) {
ts2.rollback();//回滚第二个事务
} finally {
//释放资源
}
//methodB执行完后,恢复第一个事务
tm.resume(ts1);
doSomeThingB();
ts1.commit();//提交第一个事务
} catch(RunTimeException ex) {
ts1.rollback();//回滚第一个事务
} finally {
//释放资源
}
}
在这里,我把ts1称为外层事务,ts2称为内层事务。从上面的代码可以看出,ts2与ts1是两个独立的事务,互不相干。Ts2是否成功并不依赖于 ts1。如果methodA方法在调用methodB方法后的doSomeThingB方法失败了,而methodB方法所做的结果依然被提交。而除了 methodB之外的其它代码导致的结果却被回滚了
5.PROPAGATION_NOT_SUPPORTED
总是非事务地执行,并挂起任何存在的事务
6.PROPAGATION_NEVER
总是非事务地执行,如果存在一个活动事务,则抛出异常。
7.PROPAGATION_NESTED
如果当前已经存在一个事务,那么这个方法将在嵌套事务中运行,外层事务的回滚可以引起内层事务的回滚。而内层事务的异常并不会导致外层事务的回滚。如果不存在事务,就跟PROPAGATION_REQUIRED一样。