AOP与IOC

AOP 是面向切面编程的思想,解决了程序中横向的代码重复问题。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。主要的功能是:日志记录,性能统计,安全控制,事务处理,异常处理等等,解决代码复用。
DI/IOC 依赖注入、控制反转,是Spring的两大特性之一 它不是什么技术,而是一种解耦的设计思想 。
以前对象使我们手动实例化,比如:Service层调用Dao层,需要Dao d = new Dao;但是这样会导致两个层之间的耦合性大大增强。而spring的IOC,反转控制,会在我们需要实例对象的时候,由spring容器为我们提供,并通过DI依赖注入来实现目标对象的获得,完成解耦操作。可以通过setter方法注入、构造注入、注解注入。
依赖注入是Spring的思想,在使用Spring进行开发时,可以将对象交给spring进行管理,在初始化时spring创建一批对象,当你需要用的时候只要从spring的容器中获取对象,而不用自己去new,当然在对象创建的时候可以注入另一个对象。比如A,B两个对象都由spring管理,A中持有对B的引用,那么spring在生成A对象的时候就已经把B对象的一个实例给到A了,当你在A中用到B的时候直接使用就可以了。

AOP三种配置方式及原理

Spring支持三种aop的配置方式,分别是手动配置,自动命名空间配置,自动注解配置
三种aop生成原理其实都是一样的,在spring中使用实现的代码也是一套的。原理都是通过添加一个AbstractAutoProxyCreator的BeanPostProcessor, 然后我们知道BeanPostProcessor会被提前实例化,而且在一个普通对象被创建后(依赖注入完成,开始初始化),会回调BeanPostProcessor接口方法。而代理的生成就是在回调BeanPostProcessor的applyBeanPostProcessorsAfterInitialization方法时生成的,AbstractAutoProxyCreator重写了这个方法,去直接实现生成代理对象,然后返回添加到容器中。
过程:
初始化之后会进行切点的判断,如果匹配上切点,会生成一个新的类(如果是接口则进行实现),来继承原来的对象,并重写里面的方法,在方法执行时会调用切点里的方法,然后调用原来对象里面的方法。(原来注入的对象在代理对象中没有值是null,只会在target对象中显示,是因为在代理对象中还会回去调用原来对象的方法,所以没有必要进行属性填充)
image.png
image.png

使用AOP捕获日志

使用 @ControllerAdvice是 controller 的一个辅助类,最常用的就是作为全局异常处理的切面类。
使用@Pointcut("execution(public com.example.springboot_jsp.controller..*(..))")注解,用来捕获置顶切面的日志。

静态代理与动态代理

静态代理中代理类在编译期就已经确定,而动态代理则是JVM运行时动态生成,静态代理的效率相对动态代理来说相对高一些,但是静态代理代码冗余大,一单需要修改接口,代理类和委托类都需要修改。

JDK动态代理与CGLIB代理

JDK动态代理只能对实现了接口的类生成代理,而不能针对类。
CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法。因为是继承, 所以该类或方法最好不要声明成final。

Spring AOP

  1. 默认使用 JDK 动态代理,这样便可以代理所有的接口类型(interface)
  2. Spring AOP也支持CGLIB的代理方式。如果我们被代理对象没有实现任何接口,则默认是CGLIB
  3. 我们可以强制使用CGLIB,指定proxy-target-class = "true" 或者 基于注解 @EnableAspectJAutoProxy(proxyTargetClass = true)

    Spring中Bean的单例、多例

    Spring哪里用到了单例?

  • Springboot 采用的是单例模式
  • @Component 注解默认实例化的对象是单例,如果想声明成多例对象可以使用@Scope(“prototype”)
  • @Repository默认单例
  • @Service默认单例
  • @Controller默认单例

    Spring单例Bean与单例模式的区别?

    Spring单例Bean与单例模式的区别在于它们关联的环境不一样,单例模式是指在一个JVM进程中仅有一个实例,而Spring单例是指一个Spring Bean容器(ApplicationContext)中仅有一个实例。
    注意(重要):

  • 单例bean的效果使用的单例池,相当于一个map,key是name,value是对象。

  • @Automired在Spring容器中寻找,是先bytype,然后byname,bean单例说的是name只能有一个。
  • @Automired在自动注入时,会经过层层筛选,找到相应的bean
  • @Resource是先byname,然后bytype,不是Spring的注解,建议使用
  • @Resource有name属性,可以直接根据name去找bean

    Spring 单例实现方式

    Spring中DAO和Service都是以单实例的bean形式存在,Spring通过ThreadLocal类将有状态的变量(例如数据库连接Connection)本地线程化,从而做到多线程状况下的安全。在一次请求响应的处理线程中, 该线程贯通展示、服务、数据持久化三层,通过ThreadLocal使得所有关联的对象引用到的都是同一个变量。
    优先使用单例Bean,可以节省内存开销并减少维护Bean的生成与销毁所带来的额外开销。

    Spring 上下文

    我们常见的ApplicationContext本质上说就是一个维护Bean定义以及对象之间协作关系的高级接口 ,你只要将你需要IOC容器替你管理的对象基于xml也罢,java注解也好,总之你要将需要管理的对象(Spring中我们都称之问bean)、bean之间的协作关系配置好,然后利用应用上下文对象加载进我们的Spring容器,容器就能为你的程序提供你想要的对象管理服务了。
    spring的启动过程其实就是其IoC容器的启动过程,对于web程序,IoC容器启动过程即是建立上下文的过程。
  1. 首先,对于一个web应用,其部署在web容器中,web容器提供其一个全局的上下文环境,这个上下文就是ServletContext,其为后面的spring IoC容器提供宿主环境;
  2. 其次,在web.xml中会提供有contextLoaderListener。在web容器启动时,会触发容器初始化事件,此时contextLoaderListener会监听到这个事件,其contextInitialized方法会被调用,在这个方法中,spring会初始化一个启动上下文,这个上下文被称为根上下文,即WebApplicationContext,这是一个接口类,确切的说,其实际的实现类是XmlWebApplicationContext。这个就是spring的IoC容器,其对应的Bean定义的配置由web.xml中的context-param标签指定。在这个IoC容器初始化完毕后,spring以WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE为属性Key,将其存储到ServletContext中,便于获取;
  3. 再次,contextLoaderListener监听器初始化完毕后,开始初始化web.xml中配置的Servlet,这个servlet可以配置多个,以最常见的DispatcherServlet为例,这个servlet实际上是一个标准的前端控制器,用以转发、匹配、处理每个servlet请求。DispatcherServlet上下文在初始化的时候会建立自己的IoC上下文,用以持有spring mvc相关的bean。在建立DispatcherServlet自己的IoC上下文时,会利用WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE先从ServletContext中获取之前的根上下文(即WebApplicationContext)作为自己上下文的parent上下文。有了这个parent上下文之后,再初始化自己持有的上下文。这个DispatcherServlet初始化自己上下文的工作在其initStrategies方法中可以看到,大概的工作就是初始化处理器映射、视图解析等。这个servlet自己持有的上下文默认实现类也是mlWebApplicationContext。初始化完毕后,spring以与servlet的名字相关(此处不是简单的以servlet名为Key,而是通过一些转换,具体可自行查看源码)的属性为属性Key,也将其存到ServletContext中,以便后续使用。这样每个servlet就持有自己的上下文,即拥有自己独立的bean空间,同时各个servlet共享相同的bean,即根上下文(第2步中初始化的上下文)定义的那些bean。

参考资料