10.剑指JavaOffer-Spring

IOC:控制反转

依赖注入DI:

image

一层层依赖,底盘根据轮子的大小来设计,如果轮子更改,变大了,都要改,灾难

举例说明:
1.一开始呢,构造方法是无参的


初始

2.后来需求中想要,更改轮子的size,用上可变的构造参数,那么所有的函数都需要改


image

什么是依赖注入?
image

3.把轮子作为构造函数的参数注入到底盘中,一步步注入,则如果改轮子大小只需要更改轮子就行了,其他的类不需要更改。


image

以上是构造函数的方式实现依赖注入

除此之外还有如下注入方式:
setter注入

public class Bottom{
private Tire tire;
public void setTire(Tire tire) {
 this.tire = tire;
 } }

接口注入
注解注入(@Autowire)
构造器注入(上面的例子)

IOC容器的优势:

避免在各处使用new来创建类并且可以统一维护(IOC Container)

创建实例的时候不需要知晓其中细节(下图中蓝色部分)

image
springIOC容器

1.读取bean的配置信息
2.生成一份bean定义的注册表
3.根据注册表去实例化bean
4.装配好bean的依赖关系放入缓存池
5.依据java的反射和依赖关系使用bean

image.png

最核心的就是依赖注入和自动装配

先了解一下几个核心接口和类:


image.png

会把xml中定义的类或者注解里的类转化为BeanDefinition

image.png

如图中源码


源码

两个核心接口BeanFactory和ApplicationContext(升级了的BeanFactory)

BeanFactory

image

比较核心的方法getbean

ApplicationContext

ApplicationContext的功能(继承了多个接口)

BeanFactory:能够管理、装配bean

ResourcePatternResolver:能够加载资源文件

MessageSource:能够实现国际化等功能

ApplicationEventPublisher:能够注册监听器,实现监听机制

超级全的:


image.png

实战

在config里面这样定义一个bean:

package com.imooc.framework.ioc.config;

import com.imooc.framework.ioc.entity.Person;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;
// Configuration标签会把类注入到容器里面 Bean标签是把initPerson这个方法返回的实体类装配到IOc容器当中

@Configuration

public class ApplicationConfig{

    @Bean(name="person")

    public Person initPerson(){

        Person user = new Person();

        user.setId(1L);

        user.setName("Jack");

        return user;

    }

}
image.png

上面的方法不推荐,springboot是以下方式装配bean的,就能被扫描到,不过扫描的是当前package和子package

@Component("person")

public class Person{

@Value("1")

private Long id;

@Value("Jack")

private String name;
//get set 方法省略

如果person类实现pet接口类的方法,将pet注入到person类(驯兽师)中

person类增加这几行,就可以调用pet的方法类

@Autowired

private Pet pet;

public void call(){

        pet.move();

    }

//pet接口省略,只有一个方法move

@Component

@Primary(当有多个实现类的时候需要加入这个标签)

public class Dog implements Pet{

@Override

public void move(){

System.out.println("running");

    }

}
Spring容器的refresh()
image

Spring Bean的作用域

  • singleton
    Spring的默认作用域,容器里拥有唯一的Bean实例(适合无状态的bean)
    在Spring的IoC容器中只存在一个实例,所有对该对象的引用将共享这个实例。该实例从容器启动,并因为第一次被请求而初始化之后,将一直存活到容器退出,
    也就是说,它与IoC容器“几乎”拥有相同的“寿命”。

  • prototype
    针对每个getBean请求,容器都会创建一个Bean实例(适合有状态的bean)
    对声明为拥有prototype scope的bean定义,容器在接到该类型对象的请求的时候,会每次都重新生成一个新的对象实例给请求方。虽然这种类型的对象的实例化以及属性设置等工作都是由容器负责的,但是只要准备完毕,并且对象实例返回给请求方之后,容器就不再拥有当前返回对象的引用,请求方需要自己负责当前返回对象的后继生命周期的管理工作,包括该对象的销毁。也就是说,容器每
    次返回给请求方一个新的对象实例之后,就任由这个对象实例“自生自灭”了。

web容器支持一下三种作用域:

  • request
    会为每个http请求创建一个bean实例,该bean仅在当前HTTP request内有效,当请求结束后,该对象的生命周期即告结束

  • session
    会为每个session创建一个bean实例,该bean仅在当前HTTP session内有效

  • globalSession
    会为每个全局的http session创建一个bean实例,该作用域仅对Portlet有效


Spring Bean 的生命周期

image
  • 实例化bean并设置bean属性「如前面例子中Person类的@Component("Person")+ 属性name上面的@Value("Jack") 」

  • 检查Aware 接口(对SpringIOC容器的感知)并设置相关依赖


    image.png
  • BeanPostProcessor前置处理,在bean完成实例化之和完成一些自定义的处理逻辑(比如替换当前对象实例或者字节码增强当前对象实例等。Spring的AOP则更多地使用BeanPostProcessor来为对象生成相应的代理对象)。

  • 检查是否实现接口InitiallizingBean以决定是否调用afterPropertiesSet,做一些属性被设置之后一些自定义的事情(比如,在有些情况下,某个业务对象实例化完成后,还不能处于可以使用状态。这个时候就可以让该业务对象实现该接口,并在方法afterPropertiesSet()中完成对该业务对象的后续处理。)

  • 检查是否有自定义的init-method,做一些初始化的工作

  • BeanPostProcessor后置处理,bean初始化之后的自定义的工作
    销毁的过程


    image.png
image
总流程

Spring AOP

关注点分离:不同问题交给不同的部分去解决,

  • 面向切面编程AOP是这种技术的体现

  • 通用化功能代码的实现,对应的就是所谓的切面(Aspect)

  • 业务功能代码和切面代码分开后,架构将变得高内聚低耦合

  • 确保功能的完整性:切面最终需要被合并到业务中(织入)

实现AOP的技术,主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码。

AspectJ是idea可以new的

控制层:

@RestController

public class HelloController{

@RequestMapping(value="/hello", method = RequestMethod.GET)

@ResponseBody

public String hello(){

String sentence ="Hello World";

        System.out.println(sentence);

return sentence;

    }

@RequestMapping(value="/hi", method = RequestMethod.GET)

@ResponseBody

public String hi(){

String sentence ="Hi World";

        System.out.println(sentence);

return sentence;

    }

}

可能会有许多的controller和不同的方法,每个都写记录日志的逻辑,成本很高
日志提醒切面

@Aspect

@Component

public class RequestLogAspect{

private static final Logger logger = LoggerFactory.getLogger(RequestLogAspect.class);

@Pointcut("execution(public * com.imooc.framework.web..*.*(..))")//这个下面的任何参数任意方法都是切入点,都打日志。

public void webLog(){}

@Before("webLog()")

public void doBefore(JoinPoint joinPoint){

// 接收到请求,记录请求内容

        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();

        HttpServletRequest request = attributes.getRequest();

// 记录下请求内容

logger.info("URL : "+ request.getRequestURL().toString());

logger.info("IP : "+ request.getRemoteAddr());

    }

@AfterReturning(returning ="ret", pointcut ="webLog()")

public void doAfterReturning(Object ret){

// 处理完请求,返回内容

logger.info("RESPONSE : "+ ret);

    }

}

Aspect:RequestLogAspect,

Target:HelloController

Join Point :hi方法和hello方法

Pointcut @Pointcut("execution(public * com.imooc.framework.web...(..))")

Advice :Before、AfterReturning 异常通知等

image

AOP的实现

image

JDKProxy:通过java的内部反射机制实现的,利用拦截器(拦截器必须实现InvocationHanlder)加上反射机制

Cglib:借助ASM(能够操作java字节码)实现的,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。实现CGLIB动态代理必须实现MethodInterceptor(方法拦截器)接口
总的来说反射机制生成类的过程中比较高效,ASM在生成类之后的执行过程比较高效

何时使用JDK还是CGLIB?
1)如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP。

2)如果目标对象实现了接口,可以强制使用CGLIB实现AOP。

3)如果目标对象没有实现了接口,必须采用CGLIB库,Spring会自动在JDK动态代理和CGLIB之间转换。

代理模式:接口+真实实现类+代理类(里面有真实实现类)

例子:Payment 接口类,方法付钱,AliPay代理类,RealPayment 真实实现类

Payment接口

public interface Payment{
    void pay();
}
public class RealPayment implements Payment{

@Override

public void pay(){

System.out.println("作为用户,我只关心支付功能");

    }

}

代理类

public class AliPay implements Payment{//代理类

private Payment payment;

public AliPay(Payment payment){

this.payment = payment;

    }

public void beforePay(){

System.out.println("从招行取款");

    }

@Override

public void pay(){

        beforePay();

        payment.pay();

        afterPay();

    }

public void afterPay(){

System.out.println("支付给慕课网");

    }

}
public class ProxyDemo{

public static void main(String[] args){

Payment proxy =newAliPay(newRealPayment());

        proxy.pay();

    }

}

Spring里的代理模式的实现

  • 真实实现类的逻辑包含在getBean方法里
  • getBean方法返回的实际上是Proxy的实例
  • Proxy实例是Spring采用JDK Proxy或CGLIB动态生成的

doGetBean方法中有个initializeBean方法,它调用了BeanPostProcessor,它调用了AbstractProxyCreator下面的wrapIfNecessary方法,里面有createProxy-> createAopProxy->DefaultAopProxyFactory.createAopProxy()

Spring事务管理

Spring并不直接管理事务,而是提供了多种事务管理器,他们将事务管理的职责委托给hibernate或者JTA等持久化机制所提供的相关平台框架的事务来实现。

  • JDBC事务

如果应用程序中直接使用JDBC来进行持久化,DataSourceTransactionManager会为你处理事务边界。为了使用DataSourceTransactionManager,你需要使用如下的XML将其装配到应用程序的上下文定义中:

<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>

实际上,DataSourceTransactionManager是通过调用Java.sql.Connection来管理事务,而后者是通过DataSource获取到的。通过调用连接的commit()方法来提交事务,同样,事务失败则通过调用rollback()方法进行回滚。

  • Hibernate事务

如果应用程序的持久化是通过Hibernate实现的,那么你需要使用HibernateTransactionManager。对于Hibernate3,需要在Spring上下文定义中添加如下的<bean>声明:

<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">  
      <property name="sessionFactory" ref="sessionFactory" />    
</bean>

sessionFactory属性需要装配一个Hibernate的session工厂,HibernateTransactionManager的实现细节是它将事务管理的职责委托给org.hibernate.Transaction对象,而后者是从Hibernate Session中获取到的。当事务成功完成时,HibernateTransactionManager将会调用Transaction对象的commit()方法,反之,将会调用rollback()方法。

  • Java持久化API事务(JPA)

Hibernate多年来一直是事实上的Java持久化标准,但是现在Java持久化API作为真正的Java持久化标准进入大家的视野。如果你计划使用JPA的话,那你需要使用Spring的JpaTransactionManager来处理事务。你需要在Spring中这样配置JpaTransactionManager:

<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">        
    <property name="sessionFactory" ref="sessionFactory" />    
</bean>

JpaTransactionManager只需要装配一个JPA实体管理工厂(javax.persistence.EntityManagerFactory接口的任意实现)。JpaTransactionManager将与由工厂所产生的JPA EntityManager合作来构建事务。

  • Java原生API事务

如果你没有使用以上所述的事务管理,或者是跨越了多个事务管理源(比如两个或者是多个不同的数据源),你就需要使用JtaTransactionManager:

<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">        
    <property name="transactionManagerName" value="java:/TransactionManager" />   
</bean>

JtaTransactionManager将事务管理的责任委托给javax.transaction.UserTransaction和javax.transaction.TransactionManager对象,其中事务成功完成通过UserTransaction.commit()方法提交,事务失败通过UserTransaction.rollback()方法回滚。

Spring 事务中的隔离级别有哪几种?

TransactionDefinition 接口中定义了五个表示隔离级别的常量:

  • TransactionDefinition.ISOLATION_DEFAULT: 使用后端数据库默认的隔离级别,Mysql 默认采用的 REPEATABLE_READ隔离级别 Oracle 默认采用的 READ_COMMITTED隔离级别.
  • TransactionDefinition.ISOLATION_READ_UNCOMMITTED: 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
  • TransactionDefinition.ISOLATION_READ_COMMITTED: 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
  • TransactionDefinition.ISOLATION_REPEATABLE_READ: 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
  • TransactionDefinition.ISOLATION_SERIALIZABLE: 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。

Spring 事务中哪几种事务传播行为?

支持当前事务的情况:

  • TransactionDefinition.PROPAGATION_REQUIRED: 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
  • TransactionDefinition.PROPAGATION_SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
  • TransactionDefinition.PROPAGATION_MANDATORY: 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)

不支持当前事务的情况:

  • TransactionDefinition.PROPAGATION_REQUIRES_NEW: 创建一个新的事务,如果当前存在事务,则把当前事务挂起。
  • TransactionDefinition.PROPAGATION_NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。
  • TransactionDefinition.PROPAGATION_NEVER: 以非事务方式运行,如果当前存在事务,则抛出异常。

其他情况:

  • TransactionDefinition.PROPAGATION_NESTED: 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。

各类注解

https://blog.csdn.net/lipinganq/article/details/79155072

SpingMVC 流程

(1)客户端(浏览器)发送请求,直接请求到 DispatcherServlet。
(2)DispatcherServlet 根据请求信息调用 HandlerMapping,解析请求对应的 Handler。
(3)解析到对应的 Handler(也就是我们平常说的 Controller 控制器)后,开始由 HandlerAdapter 适配器处理。
(4)HandlerAdapter 会根据 Handler 来调用真正的处理器开处理请求,并处理相应的业务逻辑。
(5)处理器处理完业务后,会返回一个 ModelAndView 对象,Model 是返回的数据对象,View 是个逻辑上的 View。
(6)ViewResolver 会根据逻辑 View 查找实际的 View。
(7)DispaterServlet 把返回的 Model 传给 View(视图渲染)。
(8)把 View 返回给请求者(浏览器)

SpringMVC 重要组件说明
1、前端控制器DispatcherServlet(不需要工程师开发),由框架提供(重要)

作用:Spring MVC 的入口函数。接收请求,响应结果,相当于转发器,中央处理器。有了 DispatcherServlet 减少了其它组件之间的耦合度。用户请求到达前端控制器,它就相当于mvc模式中的c,DispatcherServlet是整个流程控制的中心,由它调用其它组件处理用户的请求,DispatcherServlet的存在降低了组件之间的耦合性。

2、处理器映射器HandlerMapping(不需要工程师开发),由框架提供

作用:根据请求的url查找Handler。HandlerMapping负责根据用户请求找到Handler即处理器(Controller),SpringMVC提供了不同的映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。

3、处理器适配器HandlerAdapter

作用:按照特定规则(HandlerAdapter要求的规则)去执行Handler 通过HandlerAdapter对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行。

4、处理器Handler(需要工程师开发)

注意:编写Handler时按照HandlerAdapter的要求去做,这样适配器才可以去正确执行Handler,Handler 是继DispatcherServlet前端控制器的后端控制器,在DispatcherServlet的控制下Handler对具体的用户请求进行处理。 由于Handler涉及到具体的用户业务请求,所以一般情况需要工程师根据业务需求开发Handler。

5、视图解析器View resolver(不需要工程师开发),由框架提供

作用:进行视图解析,根据逻辑视图名解析成真正的视图(view) View Resolver负责将处理结果生成View视图,View Resolver首先根据逻辑视图名解析成物理视图名即具体的页面地址,再生成View视图对象,最后对View进行渲染将处理结果通过页面展示给用户。 springmvc框架提供了很多的View视图类型,包括:jstlView、freemarkerView、pdfView等。 一般情况下需要通过页面标签或页面模版技术将模型数据通过页面展示给用户,需要由工程师根据业务需求开发具体的页面。

6、视图View(需要工程师开发)

View是一个接口,实现类支持不同的View类型(jsp、freemarker、pdf...)

注意:处理器Handler(也就是我们平常说的Controller控制器)以及视图层view都是需要我们自己手动开发的。其他的一些组件比如:前端控制器DispatcherServlet、处理器映射器HandlerMapping、处理器适配器HandlerAdapter等等都是框架提供给我们的,不需要自己手动开发。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,636评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,890评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,680评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,766评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,665评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,045评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,515评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,182评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,334评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,274评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,319评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,002评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,599评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,675评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,917评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,309评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,885评论 2 341

推荐阅读更多精彩内容

  • Spring入门使用Spring容器Spring容器使用ApplicationContextApplication...
    渐丶忘阅读 1,390评论 0 4
  • 2.1 我们的理念是:让别人为你服务 IoC是随着近年来轻量级容器(Lightweight Container)的...
    好好学习Sun阅读 2,696评论 0 11
  • JAVA面试题 1、作用域public,private,protected,以及不写时的区别答:区别如下:作用域 ...
    JA尐白阅读 1,140评论 1 0
  • 一. Java基础部分.................................................
    wy_sure阅读 3,780评论 0 11
  • Part III. Core Technologies github 地址 https://github.com/...
    天幕_bc1a阅读 462评论 2 0