结合上一篇spring篇一起食用更好喔~
面向切面编程(AOP)
无侵入式编程
AOP (Aspect Oriented Programing) 称为:面向切面编程,它是一种编程思想。
AOP采取横向抽取机制,取代了传统纵向继承体系重复性代码的编写方式(例如性能监视、事务管理、安全检查、缓存、日志记录等)。
Spring AOP就是基于动态代理的,如果要代理的对象,实现了某个接⼝,那么Spring AOP会使⽤JDKProxy,去创建代理对象,⽽对于没有实现接⼝的对象,就⽆法使⽤ JDK Proxy 去进⾏代理了,这时候Spring AOP会使⽤基于asm框架字节流的Cglib动态代理 ,这时候Spring AOP会使⽤ Cglib ⽣成⼀个被代理对象的⼦类来作为代理。
AOP 思想: 基于代理思想,对原来目标对象,创建代理对象,在不修改原对象代码情况下,通过代理对象,调用增强功能的代码,从而对原有业务方法进行增强 ! 切面:需要代理一些方法和增强代码 。
AOP的应用场景
场景一: 记录日志 场景二: 监控方法运行时间 (监控性能) 场景三: 权限控制(通过Around,可以在执行方法前进行权限验证) 场景四: 缓存优化 (第一次调用查询数据库,将查询结果放入内存对象, 第二次调用, 直接从内存对象返回,不需要查询数据库 ) 场景五: 事务管理 (调用方法前开启事务, 调用方法后提交或者回滚、关闭事务 )
AOP通知类型
- 前置通知 Before advice:在连接点前面执行,前置通知不会影响连接点的执行,除非此处抛出异常
- 后置通知 After returning advice:在连接点正常执行完成后执行,如果连接点抛出异常,则不会执行
- 异常通知 After throwing advice:在连接点抛出异常后执行
- 最终通知 After (finally) advice:在连接点执行完成后执行,不管是正常执行完成,还是抛出异常,都会执行返回通知中的内容
- 环绕通知 Around advice:环绕通知围绕在连接点前后,能在方法调用前后自定义一些操作,还需要负责决定是继续处理 join point (调用 ProceedingJoinPoint 的 proceed 方法)还是中断执行
Spring AOP编程两种方式
- Spring AOP使用纯Java实现,不需要专门的编译过程和类加载器,在运行期通过代理方式向目标类植入增强代码。
- AsPectJ是一个基于Java语言的AOP框架,Spring2.0开始,Spring AOP引入对Aspect的支持。
使用注解@Aspect流程
1.切面类 @Aspect: 定义切面类,加上@Aspect、@Component注解
2.切点 @Pointcut
3.Advice通知,在切入点上执行的增强处理,主要有五个注解
4.JoinPoint :方法中的参数JoinPoint为连接点对象,它可以获取当前切入的方法的参数、代理类等信息,因此可以记录一些信息,验证一些信息等;
代码语言:javascript复制@Aspect
@Component
//设置注解执行的顺序
@Order(1)
public class AspectTest {
/**
* 定义切点,切点为对应controller
*/
@Pointcut("execution(public * com.example.zcs.Aop.controller.*.*(..))")
public void aopPointCut(){
}
@Before("aopPointCut()")
public void testbefor(JoinPoint joinPoint) {
illegalParam(joinPoint);
System.out.println("执行方法之前执行。。。。。");
}
@After("aopPointCut()")
public void testAfter(JoinPoint joinPoint) {
//illegalParam(joinPoint);
System.out.println("执行方法之后执行。。。。。");
}
/**
*获取请求参数
* @param joinPoint
* @return
*/
private static void illegalParam(JoinPoint joinPoint) {
if(joinPoint == null){
return;
}
boolean flag = false;
try{
// 参数值
Object[] args = joinPoint.getArgs();
if (args != null) {
for (Object o : args) {
System.out.println(o);
}
}
}catch(Exception e){
}
}
}
AOP的相关术语
Aspect(切面) : 是通知和切入点的结合,通知和切入点共同定义了关于切面的全部内容—它的功能、在何时和何地完成其功能
joinpoint(连接点) :所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点.
Pointcut(切入点) :所谓切入点是指我们要对哪些joinpoint进行拦截的定义.
通知定义了切面的”什么”和”何时”,切入点就定义了”何地”.
Advice(通知) :所谓通知是指拦截到joinpoint之后所要做的事情就是通知.通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能)
Target(目标对象) :代理的目标对象
weaving(织入) :是指把切面应用到目标对象来创建新的代理对象的过程.切面在指定的连接点织入到目标对象
Introduction(引介) :在不修改类代码的前提下, Introduction可以在运行期为类动态地添加一些方法或Field.
AOP编程底层实现机制
AOP 就是要对目标进行代理对象的创建, Spring AOP是基于动态代理的,基于两种动态代理机制: JDK动态代理和CGLIB动态代理
JDK动态代理
JDK动态代理,针对目标对象的接口进行代理 ,动态生成接口的实现类 !(必须有接口)
JDK动态代理的缺点: 只能面向接口代理,不能直接对目标类进行代理 ,如果没有接口,则不能使用JDK代理。
Cglib动态代理
Cglib动态代理基于父类,Cglib的引入为了解决类的直接代理问题(生成代理子类),不需要接口也可以代理 !
什么是cglib ? CGLIB(Code Generation Library)是一个开源项目!是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。
静态代理
静态代理相当于是多写了一个代理类,在调用的时候调用的是代理类,在代理类中的处理还是原生的处理逻辑,不过在前后添加上需要添加的代码。 缺点:需要为每一个被代理的对象都创建一个代理类。
特点:
代理角色和真实角色都需要实现同一个接口,
真实角色专注于自己的事情,
代理角色目的就是帮助真实角色完成一件事情
多线程的实现方式2:实现一个接口Runnable 使用的就是"静态代理"的思想
代理知识小结
区别:
- Jdk代理:基于接口的代理,一定是基于接口,会生成目标对象的接口类型的子对象。
- Cglib代理:基于类的代理,不需要基于接口,会生成目标对象类型的子对象。
- Spring AOP 优先对接口进行代理 (使用Jdk动态代理)
- 如果目标对象没有实现任何接口,才会对类进行代理 (使用cglib动态代理)
BeanFactory 和 FactoryBean 的区别
- BeanFactory: 是 IOC 容器,并且提供方法支持外部程序对这些 bean 的访问,在程序启动时 根据传入的参数产生各种类型的 bean,并添加到 IOC容器(实现 BeanFactory接口的类) 的 singletonObject 属性中。
- FactoryBean: 首先是个 bean,也存放在 BeanFactory 中。它具有工厂方法的功能,在程序运行中 产生指定(一种)类型的 bean,并添加到了 IOC容器中的 factoryBeanObjectCache 属性中。 首先 FactoryBean 是一个 bean,但它又不仅仅是个 bean。它是一个可以 创建 或 修饰 其他对象的”工厂 bean“,这跟设计模式中的工厂模式或者装饰器模式很相似,它可以创建除自身以外的其他对象。
BeanFactory和ApplicationContext有什么区别?
ApplicationContext是BeanFactory的子接口
ApplicationContext提供了更完整的功能:
①继承MessageSource,因此支持国际化。
②统一的资源文件访问方式。
③提供在监听器中注册bean的事件。
④同时加载多个配置文件。
⑤载入多个(有继承关系)上下文,使得每一个上下文都专注于一个特定的层次,比如应用的web层。
- BeanFactroy采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化。这样,我们就不能发现一些存在的Spring的配置问题。如果Bean的某一个属性没有注入,BeanFacotry加载后,直至第一次使用调用getBean方法才会抛出异常。
- ApplicationContext,它是在容器启动时,一次性创建了所有的Bean。这样,在容器启动时,我们就可以发现Spring中存在的配置错误,这样有利于检查所依赖属性是否注入。ApplicationContext启动后预载入所有的单实例Bean,通过预载入单实例bean ,确保当你需要的时候,你就不用等待,因为它们已经创建好了。
- 相对于基本的BeanFactory,ApplicationContext 唯一的不足是占用内存空间。当应用程序配置Bean较多时,程序启动较慢。 BeanFactory通常以编程的方式被创建,ApplicationContext还能以声明的方式创建,如使用ContextLoader。
@RequestBody、@RequestParam、@ResponseBody
RequestBody注解的主要作用就是用于接收前端的参数,当我们使用post请求的时候,我们会将参数放在request body中,此时我们就需要在Controller的方法的参数前面加上@RequestBody用来接受到前端传过来的request body中的值 (将请求体中的JSON数据自动解析成Java对象)
RequestParam接收的参数主要是来自request Header中,即请求头中。通常用于get请求中,我们都知道get请求的参数都是写在url中的,例如:http://localhost:8080/my/api/testMethod?name=雷神&age=3 该url我们可以看到有name和age两种属性,那么当我们将此请求发送到后台服务以后,现在解释一下@RequestParam的括号中的三个参数的意思,value值得就是请求的url中必须要有的参数名,相当于key值;required表示的是是否为必须,也就是说参数在url中是否为必须,默认的是true;defaultValue指的则是参数的默认值; (将请求参数的值映射到控制器方法的参数上,从而方便地获取和使用这些参数)
Responsebody 注解表示该方法的返回的结果直接写入 HTTP 响应正文(ResponseBody)中,一般在异步获取数据时使用;通常是在使用 @RequestMapping 后,返回值通常解析为跳转路径,加上 @Responsebody 后返回结果不会被解析为跳转路径,而是直接写入HTTP 响应正文中。ResponseBody的作用是将后端以return
返回的javabean类型数据转为json类型数据。将java对象转为json格式的数据
cookie和session的区别
cookie不是很安全,它的数据是存放在客户的浏览器上,单个cookie保存的数据不能超过4K。
session比较安全,它会在一定时间内保存在服务器上,但是当访问增多,会比较占用服务器的性能,所以考虑到减轻服务器性能方面,应当使用cookie。
Servlet的生命周期
加载类—>实例化(为对象分配空间)—>初始化(为对象的属性赋值)—>请求响应(服务阶段)—>销毁
Jsp和Servlet的区别
Servlet的应用逻辑是在Java文件中,并且完全从表示层中的HTML里分离开来。
而JSP的情况是Java和HTML可以组合成一个扩展名为.jsp的文件。
JSP侧重于视图,Servlet主要用于控制逻辑
Servlet更多的是类似于一个Controller,用来做控制。
SpringMvc执行流程
- 客户端(浏览器)发送请求,直接请求到 DispatcherServlet(前端控制器) 。
- DispatcherServlet 根据请求信息调⽤ HandlerMapping ,解析请求对应的 Handler 。
- 解析到对应的 Handler (也就是 Controller 控制器)后,开始由HandlerAdapter 适配器处理。
- HandlerAdapter 会根据 Handler 来调⽤真正的处理器来处理请求,并处理相应的业务逻辑。
- 处理器处理完业务后,会返回⼀个 ModelAndView 对象, Model 是返回的数据对象
- ViewResolver 会根据逻辑 View 查找实际的 View 。
- DispaterServlet 把返回的 Model 传给 View (视图渲染)。
- 把 View 返回给请求者(浏览器)
@RequestMapping是怎么使用
在Controller类的方法下使用这个注解,作用就是映射URL路径,将http的请求地址映射到控制器。返回值交给视图解析器解析,如果配合@ResponseBody则返回JSON或者XML数据
面试问题:
1.如果一个接口有多个实现类,在springboot中如何调用不同实现类中的方法
代码语言:javascript复制public interface Animal {
//动物的叫声
public void call();
//动物吃的东西
public void eat();
}
实现类1
@Service("dogImpl")
public class Dog implements Animal {
@Override
public void call() {
System.out.println("汪汪汪......");
}
@Override
public void eat() {
System.out.println("骨头");
}
}
实现类2
@Service("catImpl")
public class Cat implements Animal {
@Override
public void call() {
System.out.println("喵喵喵......");
}
@Override
public void eat() {
System.out.println("鱼");
}
}
方法1 指明实现类的优先级
在写实现类的时候事先指明实现类的优先级,注入的时候就会使用优先级高的实现类。在调用的类中注入接口,默认使用的是Primary 标注的实现类的方法
代码语言:javascript复制@Service("dog")
@Primary
public class Dog implements Animal {
.......
}
方法2 通过@Autowride和@Qualifier两个注解配合使用
在调用处使用这两个注解
代码语言:javascript复制@Autowired
@Qualifier("dog")
private Animal animal; //正常启动
注:注解@Qualifier内的值是实现类的默认名
方法3 使用@Resource注解,默认类名区分
在调用处使用此注解
代码语言:javascript复制@Resource(name = "dog")
private Animal animal; //正常启动
注:注解@Qualifier内的值是实现类中@Service指定的名字
2.拦截器的配置
- 实现HandlerInterceptor接口的拦截器
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {
// 在请求处理之前进行拦截处理
return true; // 返回true表示继续执行请求处理,返回false表示中断请求处理
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,ModelAndView modelAndView) throws Exception {
// 在请求处理之后进行拦截处理
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,Exception ex) throws Exception {
// 在请求处理完成之后进行拦截处理
}
}
代码语言:javascript复制@Configuration
public class SpringMvcSupport extends webMvcConfigurationSupport {
@Autowired
private MyInterceptor myInterceptor;
@verride
protected void addInterceptors( InterceptorRegistry registry){
registry.addInterceptor(projectInterceptor).addPathPatterns("/路径");
}
}
- 使用注解的拦截器
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
}
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {
// 判断请求方法是否有MyAnnotation注解
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
if (method.isAnnotationPresent(MyAnnotation.class)) {
// 在请求处理之前进行拦截处理
return true; // 返回true表示继续执行请求处理,返回false表示中断请求处理
}
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,ModelAndView modelAndView) throws Exception {
// 在请求处理之后进行拦截处理
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,Exception ex) throws Exception {
// 在请求处理完成之后进行拦截处理
}
}
@Controller
public class MyController {
@RequestMapping("/test")
@MyAnnotation
public String test() {
// 处理请求
return "test";
}
}
3.分页功能实现
代码语言:javascript复制@Test
void testGetPage(){
IPage page = new Page( "current": 1,"size": 5);
bookDao.selectPage(page, "queryWrapper": null);
page.getCurrent();//获取当前页
page.getSize();//获取每页数量
page.getTotal();//获取总共数据条数
page.getPages();//获取一共多少页
page.getRecords();//获取的数据
}
//配置拦截器实现分页
@Configuration
public class MPConfig{
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor( ));
return interceptor;
}
}
4.如何定义一个全局异常处理类?
想要定义一个全局异常处理类的话,我们需要在这个类上添加@ContaollerAdvice注解,然后定义一些用于捕捉不同异常类型的方法,在这些方法上添加@ExceptionHandler(value = 异常类型.class)和@ResponseBody注解,方法参数是HttpServletRequest和异常类型,然后将异常消息进行处理。
如果我们需要自定义异常的话,就写一个自定义异常类,该类需要继承一个异常接口,类属性包括final类型的连续id、错误码、错误信息,再根据需求写构造方法;
5.说出Spring或者SpringMVC中常用的5个注解
@Component 基本注解,标识一个受Spring管理的组件 @Controller 标识为一个表现层的组件 @Service 标识为一个业务层的组件 @Repository 标识为一个持久层的组件 @Autowired 自动装配 @Qualifier("") 具体指定要装配的组件的id值 @RequestMapping() 完成请求映射 @PathVariable 映射请求URL中占位符到请求处理方法的形参
6.简述SpringMVC中如何返回JSON数据
Step1:在项目中加入json转换的依赖,例如jackson,fastjson,gson等
Step2:在请求处理方法中将返回值改为具体返回的数据的类型, 例如数据的集合类List等
Step3:在请求处理方法上使用@ResponseBody注解
7.Spring循环依赖问题
常见问法
请解释一下spring中的三级缓存
三级缓存分别是什么?三个Map有什么异同?
什么是循环依赖?请你谈谈?看过spring源码吗?
如何检测是否存在循环依赖?实际开发中见过循环依赖的异常吗?
多例的情况下,循环依赖问题为什么无法解决?
什么是循环依赖?
循环依赖就是在创建 A 实例的时候里面包含着 B 属性实例,所以这个时候就需要去创建 B 实例,而创 建 B 实例过程中也包含着 A 实例。 这样 A 实例还在创建的过程当中,所以就导致 A 和 B 实例都创建不出来。
spring通过三级缓存来解决循环依赖:
一级缓存:缓存经过完整的生命周期的Bean
二级缓存 :缓存未经过完整的生命周期的Bean
三级缓存:缓存的是ObjectFactory,其中存储了一个生成代理类的拉姆达表达式
我们在创建 A 的过程中,先将 A 放入三级缓存 ,这时要创建B,B要创建A就直接去三级缓存中查找,并且判断需不需要进行 AOP 处理,如果需要就执行拉姆达表达式得到代理对象,不需要就取出原始对象。然后将取出的对象放入二级缓存中,因为这个时候 A 还未经 过完整的生命周期所以不能放入一级缓存。这个时候其他需要依赖 A 对象的直接从二级缓存中去获取即可。当B创建完成,A 继续执行生命周期,当A完成了属性的注入后,就可以放入一级缓存了
问题1:为什么构造器注入属性无法解决循环依赖问题?
由于spring中的bean的创建过程为先实例化 再初始化(在进行对象实例化的过程中不必赋值)将实例化好的对象暴露出去,供其他对象调用,然而使用构造器注入,必须要使用构造器完成对象的初始化的操作,就会陷入死循环的状态
问题2:一级缓存能不能解决循环依赖问题? 不能
在三个级别的缓存中存储的对象是有区别的 一级缓存为完全实例化且初始化的对象 二级缓存实例化但未初始化对象 如果只有一级缓存,如果是并发操作下,就有可能取到实例化但未初始化的对象,就会出现问题
问题3:二级缓存能不能解决循环依赖问题?
理论上二级缓存可以解决循环依赖问题,但是需要注意,为什么需要在三级缓存中存储匿名内部类(ObjectFactory),原因在于 需要创建代理对象 eg:现有A类,需要生成代理对象 A是否需要进行实例化(需要) 在三级缓存中存放的是生成具体对象的一个匿名内部类,该类可能是代理类也可能是普通的对象,而使用三级缓存可以保证无论是否需要是代理对象,都可以保证使用的是同一个对象,而不会出现,一会儿使用普通bean 一会儿使用代理类