java基础体系整理(三)Spring框架相关

4 篇文章 0 订阅

前言

本文spring框架相关知识包括Spring、Spring MVC,Spring Boot 以及Mybatis,Hibernate等常见与Spring集成相关的框架。

常见问题

  1. 对Spring IOC、AOP的理解
  2. AOP的实现方式都有哪些?
  3. AOP中切面、切点是指什么?
  4. Spring MVC请求流程,拦截器在哪里执行的? 监听器是指什么?
  5. Mybatis中怎样在一次插入中返回该行主键(如果是自增主键的话)?
  6. Mybatis中 #{}和${}有什么区别
  7. Mybatis中怎样一次插入多行数据
  8. 怎样获得Spring的全局上下文类?
  9. Mybatis和Hibernate有什么区别?
  10. 10.Spring Boot 启动流程
  11. spring 用到了哪些设计模式?分别是在哪用的
  12. spring 两个注解区别,@Autowired 那个如果有多个类会怎样

Spring IOC和AOP

spring有3大核心组件,beans(bean的定义和管理,beanFactory相关),context(bean的上下文相关,运行环境)和core(定义了资源的访问形式)

  • beans包,创建和管理bean,beanFactory创建,对bean的包装等。使用BeanFactory表示Bean容器,是Bean容器的最底层表示。
  • context包,bean的运行时环境,保存各个对象状态。使用ApplicationContext表示应用环境的基本信息。是context包的最顶端接口。比BeanFactory功能更多一些。
  • core包,spring核心工具类包。定义了访问资源的统一方式,将资源抽象成接口的形式。Resoource是资源表示的接口,可以是各种文件形式的资源。

spring可分为两类容器,一类是BeanFactory(以及直接实现它的类),是最简单的容器,是对Ioc的简单封装。最常使用的是XmlBeanFactory。对一个xml文件进行解析并创建bean容器。(目前已废弃)

ApplicationContext 容器包括 BeanFactory 容器的所有功能,所以通常建议超过 BeanFactory。其下子类有FileSysytemXmlApplicationContext,ClassPathXmlApplicationContext,WebXmlApplication等。分别表示从文件系统,classpath或是web环境中加载配置文件用以完成spring环境配置。

spring 元数据

有3种方式可以配置bean。

  1. 基于xml配置文件
  2. 基于注解
  3. 基于java配置

基于xml文件形式的,对bean的定义有以下属性

  1. lazy-init,是否延时加载,即初次访问时加载,默认随容器启动加载。(lazy-init=false)
  2. init-method方法用于该bean初始化完成后调用的方法。(指定的是该bean的方法)
  3. destroy-method 方法用于该bean销毁前执行的方法。

  <!--延时加载,并指定了初始和销毁时的回调方法-->
    <bean id="helloService" class="com.wthfeng.learn.spring.HelloService" lazy-init="true" init-method="init"
          destroy-method="destroy">
        <!--构造器传参,指定bean的依赖关系-->
        <constructor-arg name="helloDao" ref="helloDao"/>
        <!--属性传参,指定依赖关系-->
        <property name="message" value="wthfeng"/>
    </bean>
    <bean name="helloDao" class="com.wthfeng.learn.spring.HelloDao"/>

spring 作用域

主要常用的有singleton(单例,只有一个单例实例,默认)和prototype(原型模式,每次访问创建一个实例)。

注意的是,如果将bean设为prototype模式,那么每次访问该bean时都会创建新实例,也意味着如果有init-method和destroy-method方法,则会执行多次。

生命周期

可以在bean初始化完成以及销毁前做一些工作,对于xml文件,可使用init-methoddestroy-method指定该bean下特定方法。也可以在beans中指定默认方法。如下

<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"
    default-init-method="init" 
    default-destroy-method="destroy">

或者使用硬编码,实现InitializingBeanDisposableBean接口实现回调。

依赖注入

依赖注入主要有2种方式。一个是构造器注入,一个是setter方法注入。

如果你想要向一个对象传递一个引用,你需要使用 标签的 ref 属性,如果你想要直接传递值,那么你应该使用如上所示的 value 属性。

  <bean id="helloService" class="com.wthfeng.learn.spring.HelloService" lazy-init="false">
        <!--构造器传参,指定bean的依赖关系-->
        <constructor-arg name="helloDao" ref="helloDao"/>
        <constructor-arg name="worldDao" ref="worldDao"/>
        <!--属性传参,指定依赖关系-->
        <property name="message" value="wthfeng"/>
    </bean>

自动装配

自动装配分为byTypebyNameconstructor,一个是按bean的类型,一个是按名称,以及按构造器参数。注意当使用byType时,当查找到多余一个类型时会报错。按构造器参数其实是按byType的一种特例,它会按构造器中参数类型进行查找。

如今spring推荐使用构造器注入的方式(针对注解方式而言),原因:

  1. 保证依赖不为空,spring注入时会对依赖进行检查,确保存在
  2. 保证依赖不可变
  3. 返回时是完全初始化的(不需要再调用set方法设值)
  4. 避免了循环依赖(有循环依赖立即报错)

使用注解 @AutoWire 表示按类型装配。若发现多余一个类型bean时会抛出异常,NoUniqueBeanDefinitionException,可以使用@Qualifier使用名称指定唯一的bean。

 // 使用构造器注入的方法
 @Autowired
 public UserService(@Qualifier(value = "userDao") UserDAO userDAO) {
        this.userDAO = userDAO;
 }

    // setter注入
    @Autowired
    @Qualifier(value = "userDao")
    private UserDAO userDAO;

@Resource注解是JSR-250的注解,spring也支持。默认@Resource按名称进行匹配,若没有找到则按类型匹配。有name属性,按名称匹配,和type属性,按类型匹配。

@PostConstruct 加在方法上,是当bean创建完成后执行的初始化方法,是xml形式的init-method的注解版本。@PreDestroy表示销毁前方法回调,是destroy-method方法的注解版本。

    // 等于在xml格式定义的bean上添加 init-method='init'
   @PostConstruct
    public void init() {
        System.out.println("init dao hello");

    }
    
    // 等于在xml格式定义的bean上添加 destroy-method='destroy'
    @PreDestroy
    public void destroy() throws Exception {
        System.out.println("destroy dao ");
    }

Spring AOP

几个概念
aop指面向切面编程。将若干横切的东西(方法或属性等)提取出来单独处理。比如需要在项目中所有添加方法中添加日志表示处理完了,不需要再每个方法都添加一行重复的日志。只需定义切面,由AOP完成。

切点(PointCut):定义需要在哪里织入一些增强或其他共性的处理。(比如需要在所有save()方法里加一行日志,那切点就是用来表示所有方法的)

通知(Advice):定义什么时增加处理和增加什么处理。(接上个例子,就是在save()方法哪里加日志呢,是在方法执行完返回前还是还没执行的时候,或是抛异常的情况;加什么日志内容呢(或者不加日志做一些其他操作))等等,都是通知定义的。

切面(Aspect):上述两个概念理解了,切面就是切点和通知的结合。一个切面就是需要在哪里做增加,以及什么时增强,增强什么的完整定义。

xml格式的aop定义, Spring只支持方法连接点。

    <!--aop配置元素,aop配置都需在其内部-->
    <aop:config>

        <!--定义切点-->
        <aop:pointcut id="allService" expression="execution(* com.wthfeng.learn.spring.aop.*.printLog(..))"/>
        
        <!--切面定义-->
        <aop:aspect id="logAspect" ref="logBean">

            <!--通知定义了干什么和什么时干-->
            <!--通知,包括前置、后置、返回、抛出异常,环绕通知等-->
            <aop:before method="before" pointcut-ref="allService"/>
            <aop:after method="after" pointcut-ref="allService"/>
            <aop:around method="around" pointcut-ref="allService"/>
        </aop:aspect>
    </aop:config>

也可以使用注解定义

// 切面定义
@Aspect
@Component
public class AspectModel {

     // 定义切点 
    @Pointcut(value = "execution(* com.wthfeng.learn.spring.aop.*Service.save(..))")
    public void selectSave() {

    }
    // 通知(前置)
    @Before(value = "selectSave()")
    public void before() {

        System.out.println("before method...");

    }
    // 通知(后置)
    @After(value = "selectSave()")
    public void after() {
        System.out.println("after method...");
    }
}

Spring AOP可由JDK动态代理和CGLib实现。

JDK动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。JDK动态代理的核心是InvocationHandler接口和Proxy类。

  1. 实现InvocationHandler接口
  2. 使用Proxy.newProxyInstance(classloader, interfaces, invocationHandler)创建代理类。

CGLIB会让生成的代理类继承当前对象,并在代理类中对代理方法进行强化处理(前置处理、后置处理等)。在CGLIB底层底层实现是通过ASM字节码处理框架来转换字节码并生成新的代理类

注意如果一个类是final类,则不能使用CGLib代理。

spring aop实现

spring aop的实现主要依赖于BeanPostProcessor,在回调的初始化方法中,判断bean是否符合表达式规范,符合则生成代理,返回代理bean。这样就为实现aop提供依据。


public class PostServiceBean implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {

        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {

        // 做bean做一些修改
        return bean;
    }
}

Spring MVC

首先需要了解serlvet及其工作机制spring MVC说到底就是为解决serlvet过多导致的地址映射杂乱,以及如何统一处理返回资源的问题。也就是说,如果不用MVC,直接用serlvet也不是不行,只是太麻烦了。

spring MVC解决的问题:

  1. 管理映射,当请求过来时找到匹配的方法进行处理。感觉这应该是它的核心功能。
  2. 处理返回资源,将资源统一格式处理,如返回jsp,json等。

spring mvc 涉及的要点

DispatcherServlet 核心servlet,负责处理所有请求,并返回响应。spring mvc最核心的类。由它(及子类)负责初始化spring mvc环境,可以在它的配置文件中指定一些其他设置等。具体的,该servlet初始化时会执行initWebApplicationContext方法,该方法最终会调用AbstractApplicationContext.refresh()方法创建spring IOC容器用来管理bean。

    <servlet>
        <servlet-name>dispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath*:mvc/DispatcherServlet.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>

listener监听器,serlvet可以设置监听器,ServletContextListener监听器用于监听servle上下文启动和销毁的事件,实现此接口即可。spring的ContextLoaderListener就实现了此接口。用于完成spring环境的创建工作。这里说的spring环境不是上文spring mvc的上下文环境,实际上,先执行监听器,创建了spring上下文(像dao、service、jdbc等bean环境),然后执行DispatcherServlet的初始化方法创建mvc环境。mvc环境会判断当前是否有spring上下文,有的话则设置为父上下文。至此完成spring和spring mvc两个环境的创建(spring mvc主要针对controller的bean以及映射管理等)。
spring MVC执行流程,在doDispatch()方法中

  1. 根据getHandler()找到处理器执行链(包括handler(即封装的controller方法),以及前后拦截器链)。
  2. 根据handler得到适配器(HandlerAdapter)
  3. 执行前置拦截器
  4. 执行handler,根据有无@ResponseBody注解及返回结果类型判断处理结果,并返回ModelAndView(若为json格式,则返回null)
  5. 执行后置拦截器
  6. 处理返回结果,渲染视图
// doDispatch()方法,mvc核心处理流程
// 去掉了其他无关代码和异常处理,只保留了主要流程
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		
		      // 根据请求获取执行器链(handler处理器和拦截器链)
		      HandlerExecutionChain mappedHandler = null;
				mappedHandler = getHandler(processedRequest);
				
				// 根据处理器找到相应适配器
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

		       
             // 执行前置拦截器
				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}

				//执行hanler方法,返回ModelAndView
				//注意返回的ModelAndView可能为null(如返回json格式等)
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

			   // 执行后置拦截器
				mappedHandler.applyPostHandle(processedRequest, response, mv);
			
			  // 处理结果,渲染等
			  processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
		
				
	}

spring mvc有适配器模式。用于处理不同类型的控制器,比如使用注解的控制器(@Controller),或是直接实现Controller接口的控制器,或是实现HttpRequestHandler接口的控制器。各种控制器的需要不同的调用方式,否则不利于解耦。这里就使用了适配器模式。

有以下适配器

  1. SimpleControllerHandlerAdapter,简单适配器,适配直接实现Controller接口的控制器。返回ModelAndView。调用执行时直接调用原方法
  2. HttpRequestHandlerAdapter,Http请求处理适配器,需要实现HttpRequestHandler接口。没有返回值,类似于servlet的处理方式。
  3. RequestMappingHandlerAdapter,最常用的适配器,可处理注解类型的控制器。可以使用<mvc:annotation-driven />开启。最常使用@Controller@RequestMapping注解。

对于前2种适配器,需要进行url映射处理,如下

   <bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="mappings">
            <props>
                <prop key="/simple1">simple1</prop>
                <prop key="/simple2">simple2</prop>
            </props>
        </property>
    </bean>

    <bean id="simple1" class="com.wthfeng.learn.spring.mvc.controller.SimpleController"/>
    <bean id="simple2" class="com.wthfeng.learn.spring.mvc.controller.HandlerController"/>

SimpleController实现了Controller接口,如下

public class SimpleController implements Controller {

    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        ModelAndView mv = new ModelAndView();
        mv.setViewName("index");
        mv.addObject("message", "simple controller");
        return mv;
    }
}

而HandlerController实现了HttpRequestHandler接口,如下

public class HandlerController implements HttpRequestHandler {

    @Override
    public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        PrintWriter printWriter = response.getWriter();
        response.setContentType("text/html;charset=UTF-8");
        printWriter.write("<html><head><head><body><h1>");
        printWriter.write("Hello message!");
        printWriter.write("</h1></body></html>");
        printWriter.close();

    }
}

无论是SimpleController还是HandlerController,一个类只能有一个servlet。

ApplicationContextAware

实现该接口可得到Spring上下文ApplicationContext。该接口只有一个方法setApplicationContext(ApplicationContext applicationContext),可使用静态变量接受这个上下文。

Spring会检查所有的bean,如果有bean实现了ApplicationContextAware接口,会在创建该bean后,自动调用该bean的setApplicationContext传入容器本身上下文。由此可得到Spring上下文。

拦截器和过滤器

filter是serlvet的过滤器,只能过滤serlvet方法,而拦截器是mvc容器实现的,

filter定义的web.xml中,或使用注解

intercept由spring mvc定义,在mvc的配置文件定义

   <!--Spring MVC拦截器-->
    <mvc:interceptors>

        <mvc:interceptor>
            <mvc:mapping path="/"/>
            <bean class="com.wthfeng.learn.spring.mvc.interceptor.SecondInterceptor"/>
        </mvc:interceptor>

        <mvc:interceptor>
            <mvc:mapping path="/hello"/>
            <bean class="com.wthfeng.learn.spring.mvc.interceptor.MyInterceptor"/>
        </mvc:interceptor>

    </mvc:interceptors>

后记

  1. github - tiny-spring项目简要实现了spring框架,可参考
  2. Spring 技术内幕
  3. java代理模式与JDK代理
  4. Servlet工作机制解析
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值