【Java面试题】spring+springMVC+mybatis原理及实现机制(持续更新)

               

本文将持续更新,主要讲解SSM框架的底层原理和实现机制等




1.什么是IOC?

IOC即Inverse of Control,它包括两个内容:控制与反转

那到底什么东西的“控制”被“反转”了呢?对于软件而言,即是某一个接口具体实现类的选择控制权从调用类中移除,转交给第三方决定。

因为IOC确实不够开门见山,因此业界曾经进行了广泛的讨论,最终软件界的泰斗级人物Martin Fowler提出了DI(依赖注入:Dependency Injection)的概念用以代替IOC

即让调用类对某一个接口的依赖关系由第三方(容器或者协作类)注入,以移除调用类对某一个接口实现类的依赖。


2.什么是AOP?

AOP是Aspect Oritened Programming的简称,普遍翻译成 面向切面编程。

按照软件重构思想的理念,如果多个类中出现相同的代码,应该考虑定义一个共同的抽象类,将这些相同的代码提取到抽象类中。

比如,Horse、Pig、Camel这些对象都有run、eat方法,通过引入一个包含这两个方法抽象的Animal父类,Horse、Pig、Camel就可以通过继承Animal复用run和eat方法。

通过引入父类消除多个类中重复代码的方式在大多情况下是可行的,但世界并非永远这么简单。

举个事务管理的栗子,看下面代码

Connection conn ;try{conn = DriverManager.getConnection();①获取数据连接conn.setAutoCommit(false); ②关闭自动提交的机制conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE); ③设置事务隔离级别Statement stmt = conn.createStatement();int rows = stmt.executeUpdate( "INSERT INTO t_topic VALUES(1,’tom’) " );rows = stmt.executeUpdate( "UPDATE t_user set topic_nums = topic_nums +1 "+"WHERE user_id = 1");conn.commit();④提交事务}catch(Exception e){…conn.rollback();⑤提交事务}finally{…}


在上面的代码中,真正的业务逻辑就只有

int rows = stmt.executeUpdate( "INSERT INTO t_topic VALUES(1,’tom’) " );rows = stmt.executeUpdate( "UPDATE t_user set topic_nums = topic_nums +1 "+"WHERE user_id = 1");

其他的逻辑都可以称之为横切逻辑,这些横切逻辑依附在业务方法的流程中,我们无法将之转移到其他地方去。

AOP独辟蹊径通过横向抽取机制为这类无法通过纵向继承体系进行抽象的重复性代码提供了解决方案。

Spring AOP使用动态代理技术在运行期织入增强的代码。


3.Spring AOP使用了两种代理机制:一种是基于JDK的动态代理;另一种是基于CGLib的动态代理,请简述?

之所以需要两种代理机制,很大程度上市因为JDK本身只提供接口的代理,而不支持类的代理。

JDK动态代理:

JDK动态代理主要涉及到java.lang.reflect包中两个类:Proxy和InvocationHandler。
其中InvocationHandler是一个接口,可以通过实现该接口定义横切逻辑,并通过 反射机制调用目标类的代码,动态的将横切逻辑和业务逻辑编织在一起。所以我们可以将InvacationHandler看成是一个编织器

而Proxy利用InvacationHandler动态创建一个符合某一接口的实例,生成目标类的代理对象。


Object invoke(Object proxy, Method method, Object[] args) throws Throwable

具体的流程为:

  1. 我们实现InvacationHandler接口,该接口定义了一个invoke方法,proxy是最终生成的代理实例,一般不会用到。
  2. method是被代理目标实例的某个具体方法,通过它可以发起目标实例方法的反射调用
  3. args是传给被代理实例的某个方法的入参数组,在方法反射调用时使用。

此外,我们还需要Proxy的newProxyInstance方法创建代理类对象

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentExceptionloader:  一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载interfaces:  一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了h:  一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上


使用JDK动态代理有一个限制,即它只能为接口创建代理实例,这一点我们可以从newProxyInstance方法的签名中就看的很清楚:

第二个入参interfaces就是需要代理实例实现的接口列表。

虽然面向接口编程的思想被很多大师级人物推崇,但在实际开发中,许多开发者也对此深感疑惑:难道对一个简单的业务表的操作也要老老实实的创建5个类(领域对象类、Dao接口、Dao实现类、Service接口和Service实现类)吗?难道不能直接通过实现类构造程序吗?对于这个问题,我们很难给出一个熟好熟劣的准确判断,但我们确实发现有很多不适用接口的项目也取得了很好的效果。

对于没有通过接口定义业务方法的类,如何动态创建代理实例呢?JDK的代理技术显然已经黔驴技穷,CGLib作为一个替代者,填补了这个空缺。


CGLib动态代理:

CHLib采用非常底层的字节码技术,可以为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,并顺势织入横切逻辑。

看如下代码:

package go.jacob.day1105;import java.lang.reflect.Method;import net.sf.cglib.proxy.Enhancer;import net.sf.cglib.proxy.MethodInterceptor;import net.sf.cglib.proxy.MethodProxy;public class MyCglicProxy implements MethodInterceptorprivate Enhancer enhancer=new Enhancer(); public Object getProxy(Class clazz){  enhancer.setSuperclass(clazz); //设置所需创建子类的类;即需要代理的 类  enhancer.setCallback(this);  return enhancer.create();//通过字节码技术动态创建子类实例 }  @Override //拦截父类中所有方法 public Object intercept(Object obj, Method method1, Object[] args, MethodProxy proxy) throws Throwable {  //..... 方法前横切逻辑  Object result=proxy.invokeSuper(obj, args); //通过代理类调用父类中的方法  //..... 方法后横切逻辑  return result; }}

在上面的代码中,用户可以通过getProxy为一个类创建动态代理对象,该代理对象通过扩展clazz创建代理对象。在这个代理对象中,我们织入相应的横切逻辑。

intercept是CDLib定义的Interceptor接口的方法,它拦截所有目标类方法的调用,obj表示目标类的实例;method为目标类方法的反射对象;args为方法的动态入参;而proxy为代理类的实例。


下面创建测试类:

package go.jacob.day1105;public class Test public static void main(String[] args) {  MyCglicProxy proxy = new MyCglicProxy();  Cat c=new Cat();  Cat catProxy = (Cat)proxy.getProxy(c.getClass());  catProxy.eat();   }}class Catpublic void eat(){  System.out.println("我是猫,我喜欢吃鱼"); }}


运行结果



观察以上的输出,发现代理类的名字是 ,这个特殊的类就是CGLib为Cat创建的实例。


4.springMVC工作原理



Spring MVC框架围绕着DispatcherServlet这个核心展开,DispatcherServlet是Spring MVC的总导演、总策划,它负责截获请求并将其分派给相应的处理器处理。

Spring MVC是基于Model 2实现的框架,所以它底层的机制也是MVC,我们通过上图来描述Spring MVC的整体架构。

从接受请求到返回响应,Spring MVC框架的众多组件通力合作、各司其职,有条不紊地完成分内的工作。在整个框架中,DispatcherServlet处于核心的位置,它负责协调和组织不同组件以完成请求处理并返回响应的工作。和大多数Web MVC框架一样,Spring MVC通过一个前端Servlet接受所有的请求,并将具体工作委托给其他组件进行处理,DispatcherServlet就是SpringMVC的前端Servlet。下面我们对Spring MVC处理请求的整体过程做一下高空俯瞰。

  1. 整个过程始于客户端发出一个Http请求,Web应用服务器接受到这个请求,如果匹配DispatcherServlet的请求映射路径(在web.xml中指定),web容器将该请求转发给DispatcherServlet处理。
  2. DispatcherServlet接收到这个请求后,将根据请求的信息(包括URL、HTTP方法、请求报文头、请求参数、Cookie等)即HandlerMapping的配置找到处理请求的处理器(Handler)。可将HandlerMapping看成是路由器,将Handler看成是目标主机。值得注意的是:Spring MVC中并没有定义一个handler接口,实际上任何一个Object都可以成为请求处理器。
  3. 当DispatcherServlet根据HandlerMapping得到对应当前请求的Handler后,通过HandlerAdapter对Handler进行封装,再以统一的适配器接口调用Handler。HandlerAdapter是Spring MVC的框架级接口,顾名思义,HandlerAdapter是一个是适配器,它用统一的接口对各种Handler方法进行调用。
  4. 处理器完成业务逻辑的处理后将返回一个ModelAndView给DispatcherServlet,ModelAndView包含了逻辑视图名和模型数据信息。
  5. ModelAndView中包含的是“逻辑视图名”而并非真正的视图对象,DispatcherServlet借由ViewResolver完成逻辑是视图名到真实视图对象的解析工作。
  6. 当得到真实的视图对象View后,DispatcherServlet就使用这个View对象对ModelAndView中的模型数据进行视图渲染。
  7. 最终客户端得到的相应信息,可能是一个普通的HTML页面,也可能是一个XML或者JSON串,甚至可能是一张图片或者一个PDF文档等不同的媒体形式。


5.HttpMessageConverter<T>详解

HttpMessageConverter和@responsebody/@requestbody配合使用。

@responsebody表示该方法的返回结果直接写入HTTP response body中 


HttpMessageConverter<T>是spring 3.0新添加的一个重要接口,它负责将请求信息转换成一个对象(类型为T),将对象(类型为T)输出为响应信息

DispatcherServlet默认已经安装了AnnotationMethodHandlerAdapter作为HandlerAdapter的组件实现类,HttpMessageConverter即由AnnotationMethodHandlerAdapter使用,将请求信息转换为对象,或将对象转换为相应信息。

所以,HttpMessageConverter是由适配器HandlerAdapter负责调用



public interface HttpMessageConverter<T> {              //判断数据类型是否可读      boolean canRead(Class<?> clazz, MediaType mediaType);              //判断数据是否可写      boolean canWrite(Class<?> clazz, MediaType mediaType);              //获取支持的数据类型      List<MediaType> getSupportedMediaTypes();              //对参数值进行读,转换为需要的类型      T read(Class<? extends T> clazz, HttpInputMessage inputMessage)              throws IOException, HttpMessageNotReadableException;        //将返回值发送给请求者      void write(T t, MediaType contentType, HttpOutputMessage outputMessage)              throws IOException, HttpMessageNotWritableException;    }  

只要再Spring Web容器中为AnnotationMethodHandlerAdapter装配好相应的处理XML、JSON的HttpMessageConverter,并在交互中通过请求的Accept指定MIME类型,spring MVC就可使服务端的处理方法和客户端透明地通过XML或JSON格式的消息进行通信了,开发者几乎无需关心通信层数据格式的问题,可以将精力集中到业务层的处理上。


在接受到一个HTTP请求时,控制器通过请求消息头的“Content-Type”和“Accept”分别可以知道请求消息的格式以及响应消息的格式。





















-----------------------------------------------------------------

本文内容参考《Spring 3.X企业应用开发实战》、


           

再分享一下我老师大神的人工智能教程吧。零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!https://blog.csdn.net/jiangjunshow

<?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:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd "> <!-- 自动扫描 --> <context:component-scan base-package="com.flong.*" /> <!-- 引入配置文件 --> <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="location" value="classpath:jdbc.properties" /> </bean> <!-- <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" lazy-init="false"> <property name="driverClassName" value="${jdbc.driver}" /> <property name="url" value="${jdbc.url}" /> <property name="username" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> 初始化连接大小 <property name="initialSize" value="${jdbc.initialSize}"></property> 连接池最大数量 <property name="maxActive" value="${jdbc.maxActive}"></property> 连接池最大空闲 <property name="maxIdle" value="${jdbc.maxIdle}"></property> 连接池最小空闲 <property name="minIdle" value="${jdbc.minIdle}"></property> 获取连接最大等待时间 <property name="maxWait" value="${jdbc.maxWait}"></property> </bean> --> <bean id="dbPasswordCallback" class="com.flong.utils.DBPasswordCallback"> <description>数据库连接回调密码解密</description> </bean> <bean id="statFilter" class="com.alibaba.druid.filter.stat.StatFilter" lazy-init="true"> <description>状态过滤器</description> <property name="logSlowSql" value="true" /> <property name="mergeSql" value="true" /> </bean> <!-- 操作数据库删除,修改,添加的数据源 --> <bean id="writeDataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close" init-method="init" lazy-init="true"> <property name="driverClassName" value="${jdbc.driver}" /> <property name="url" value="${jdbc.url}" /> <property name="username" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> <!-- 初始化连接大小 --> <property name="initialSize" value="${jdbc.initialSize}" /> <!-- 连接池最大数量 --> <property name="maxActive" value="${jdbc.maxActive}" /> <!-- 连接池最小空闲 --> <property name="minIdle" value="${jdbc.minIdle}" /> <!-- 获取连接最大等待时间 --> <property name="maxWait" value="${jdbc.maxWait}" /> <!-- --> <property name="defaultReadOnly" value="true" /> <property name="proxyFilters"> <list> <ref bean="statFilter" /> </list> </property> <property name="filters" value="${druid.filters}" /> <property name="connectionProperties" value="password=${password}" /> <property name="passwordCallback" ref="dbPasswordCallback" /> <property name="testWhileIdle" value="true" /> <property name="testOnBorrow" value="false" /> <property name="testOnReturn" value="false" /> <property name="validationQuery" value="SELECT 'x'" /> <property name="timeBetweenLogStatsMillis" value="60000" /> <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 --> <property name="minEvictableIdleTimeMillis" value="${minEvictableIdleTimeMillis}" /> <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 --> <property name="timeBetweenEvictionRunsMillis" value="${timeBetweenEvictionRunsMillis}" /> </bean> <!-- 操作数据库读的数据源 --> <bean id="readDataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close" init-method="init" lazy-init="true"> <property name="driverClassName" value="${jdbc.driver}" /> <property name="url" value="${jdbc.url}" /> <property name="username" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> <!-- 初始化连接大小 --> <property name="initialSize" value="${jdbc.initialSize}" /> <!-- 连接池最大数量 --> <property name="maxActive" value="${jdbc.maxActive}" /> <!-- 连接池最小空闲 --> <property name="minIdle" value="${jdbc.minIdle}" /> <!-- 获取连接最大等待时间 --> <property name="maxWait" value="${jdbc.maxWait}" /> <!-- --> <property name="defaultReadOnly" value="true" /> <property name="proxyFilters"> <list> <ref bean="statFilter" /> </list> </property> <property name="filters" value="${druid.filters}" /> <property name="connectionProperties" value="password=${password}" /> <property name="passwordCallback" ref="dbPasswordCallback" /> <property name="testWhileIdle" value="true" /> <property name="testOnBorrow" value="false" /> <property name="testOnReturn" value="false" /> <property name="validationQuery" value="SELECT 'x'" /> <property name="timeBetweenLogStatsMillis" value="60000" /> <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 --> <property name="minEvictableIdleTimeMillis" value="${minEvictableIdleTimeMillis}" /> <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 --> <property name="timeBetweenEvictionRunsMillis" value="${timeBetweenEvictionRunsMillis}" /> </bean> <!--利用AbstractRoutingDataSource实现动态数据源切换 --> <bean id="dataSource" class="com.flong.utils.persistence.DynamicChooseDataSource" lazy-init="true"> <description>数据源</description> <property name="targetDataSources"> <map key-type="java.lang.String" value-type="javax.sql.DataSource"> <!-- write --> <entry key="write" value-ref="writeDataSource" /> <!-- read --> <entry key="read" value-ref="readDataSource" /> </map> </property> <!-- 从org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource这个类里面可以看到 defaultTargetDataSource这个对象,根据自己需求的情况默认的情况给一个默认的数据源. --> <property name="defaultTargetDataSource" ref="writeDataSource" /> <property name="methodType"> <map key-type="java.lang.String"> <!-- read --> <entry key="read" value=",get,select,count,list,query" /> <!-- write --> <entry key="write" value=",add,insert,create,update,delete,remove," /> </map> </property> </bean> <!-- springMyBatis完美整合,不需要mybatis的配置映射文件 --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <!-- 添加 mybatis-config配置上去。--> <property name="configLocation" value="classpath:mybatis-config.xml" /> <!-- 扫描com.flong.pojo下面的所有类,减少mybatis的mapping所有实体类的路径(parameterType),直接写一个JavaBean即可 --> <property name="typeAliasesPackage" value="com.flong.pojo"/> <!-- 自动扫描mapping.xml文件 --> <property name="mapperLocations" value="classpath:mapping/*.xml"></property> </bean> <!-- DAO接口所在包名,Spring会自动查找其下的类 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.flong.dao" /> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property> </bean> <!-- (事务管理)transaction manager, use JtaTransactionManager for global tx --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <!-- 配置事务通知属性 --> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <!-- 定义事务传播属性 --> <tx:attributes> <tx:method name="insert*" propagation="REQUIRED" /> <tx:method name="update*" propagation="REQUIRED" /> <tx:method name="edit*" propagation="REQUIRED" /> <tx:method name="save*" propagation="REQUIRED" /> <tx:method name="add*" propagation="REQUIRED" /> <tx:method name="new*" propagation="REQUIRED" /> <tx:method name="set*" propagation="REQUIRED" /> <tx:method name="remove*" propagation="REQUIRED" /> <tx:method name="delete*" propagation="REQUIRED" /> <tx:method name="change*" propagation="REQUIRED" /> <tx:method name="get*" propagation="REQUIRED" read-only="true" /> <tx:method name="find*" propagation="REQUIRED" read-only="true" /> <tx:method name="load*" propagation="REQUIRED" read-only="true" /> <tx:method name="*" propagation="REQUIRED" read-only="true" /> </tx:attributes> </tx:advice> <!-- 配置事务切面 --> <aop:config> <aop:pointcut id="serviceOperation" expression="execution(* com.flong.service.*.*(..))" /> <aop:advisor advice-ref="txAdvice" pointcut-ref="serviceOperation" /> </aop:config> </beans>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值