(面试必备)超详细Spring IOC、AOP、事务解析及其案例
在具体介绍IOC和AOP之前,我们先简要说明下Spring常用注解
@Controller/@RestController/@RequestMapping介绍
声明这是一个Springboot的应用
@RequestMapping与@GetMapping和@PostMapping
@PathVariable注解使用
@RequestParam注解使用
Spring @Configuration 注解介绍
@Configuration标注在类上,相当于把该类作为spring的xml配置文件中的
@RequestBody的使用
在 SpringBoot 的核心配置文件中,除了使用内置的配置项之外,我们还可以在自定义配
置,然后采用如下注解去读取配置的属性值
spring的@Value注解使用
@ConfigurationProperties
Spring注解@Component、@Repository、@Service、@Controller区别
@Repository的作用?
@Repository的作用
@component
@component
@Autowired 的作用是什么?
对@Reference 、@Resource和@Autowired的简单理解
@Slf4j
spring注解@Service注解的使用
1、@Controller:用于标注控制器层组件
2、@Service:用于标注业务层组件
3、@Component : 用于标注这是一个受 Spring 管理的组件,组件引用名称是类名,第一个字母小写。可以使用@Component(“beanID”) 指定组件的名称
4、@Repository:用于标注数据访问组件,即DAO组件
5、@Bean:方法级别的注解,主要用在@Configuration和@Component注解的类里,@Bean注解的方法会产生一个Bean对象,该对象由Spring管理并放到IoC容器中。引用名称是方法名,也可以用@Bean(name = “beanID”)指定组件名
6、@Scope(“prototype”):将组件的范围设置为原型的(即多例)。保证每一个请求有一个单独的action来处理,避免action的线程问题。
由于Spring默认是单例的,只会创建一个action对象,每次访问都是同一个对象,容易产生并发问题,数据不安全。
7、@Autowired:默认按类型进行自动装配。在容器查找匹配的Bean,当有且仅有一个匹配的Bean时,Spring将其注入@Autowired标注的变量中。
8、@Resource:默认按名称进行自动装配,当找不到与名称匹配的Bean时会按类型装配。
简单点说,就是,能够明确该类是一个控制器类组件的,就用@Controller;能够明确是一个服务类组件的,就用@Service;能够明确该类是一个数据访问组件的,就用@Repository;不知道他是啥或者不好区分他是啥,但是就是想让他动态装配的就用@Component。
@Controller、@Service、@Component、@Repository都是类级别的注解,如果一个方法也想动态装配,就用@Bean。
当我们想按类型进行自动装配时,就用@Autowired;当我们想按名称(beanID)进行自动装配时,就用@Resource;当我们需要根据比如配置信息等来动态装配不同的组件时,可以用getBean(“beanID”)。
到这里,如果对这些注解,或是自动装配不太理解,可以继续往下,看完 控制反转(IoC) 内容后再回来理解这里的内容。
IOC(Inverse of Control):控制反转,也可以称为依赖倒置。
所谓依赖,从程序的角度看,就是比如A要调用B的方法,那么A就依赖于B,反正A要用到B,则A依赖于B。所谓倒置,你必须理解如果不倒置,会怎么着,因为A必须要有B,才可以调用B,如果不倒置,意思就是A主动获取B的实例:B b = new B(),这就是最简单的获取B实例的方法(当然还有各种设计模式可以帮助你去获得B的实例,比如工厂、Locator等等),然后你就可以调用b对象了。所以,不倒置,意味着A要主动获取B,才能使用B;到了这里,就应该明白了倒置的意思了。倒置就是A要调用B的话,A并不需要主动获取B,而是由其它人自动将B送上门来。
IoC (Inversion of control )控制反转/反转控制。它是一种思想不是一个技术实现。描述的是:Java 开发领域对象的创建以及管理的问题。
例如:现有类 A 依赖于类 B
传统的开发方式 :往往是在类 A 中手动通过 new 关键字来 new 一个 B 的对象出来
使用 IoC 思想的开发方式 :不通过 new 关键字来创建对象,
而是通过 IoC 容器(Spring 框架) 来帮助我们实例化对象。我们需要哪个对象,直接从 IoC 容器里面过去即可。
从以上两种开发方式的对比来看:我们 “丧失了一个权力” (创建、管理对象的权力),从而也得到了一个好处(不用再考虑对象的创建、管理等一系列的事情)
控制 :指的是对象创建(实例化、管理)的权力
反转 :控制权交给外部环境(Spring 框架、IoC 容器)
控制反转,简单点说,就是创建对象的控制权,被反转到了Spring框架上。
通常,我们实例化一个对象时,都是使用类的构造方法来new一个对象,
这个过程是由我们自己来控制的,而控制反转就把new对象的工交给了Spring容器。
形象的举例就是:
通常情况下,假如你有一天在家里口渴了,要喝水,那么你可以到你小区的小卖部去,告诉他们,你需要一瓶水,然后小卖部给你一瓶水!这本来没有太大问题,关键是如果小卖部很远,那么你必须知道:从你家如何到小卖部;小卖部里是否有你需要的水;你还要考虑是否开着车去;等等等等,也许有太多的问题要考虑了。也就是说,为了一瓶水,你还可能需要依赖于车等等这些交通工具或别的工具,问题是不是变得复杂了?那么如何解决这个问题呢?
解决这个问题的方法很简单:小卖部提供送货上门服务,凡是小卖部的会员,你只要告知小卖部你需要什么,小卖部将主动把货物给你送上门来!这样一来,你只需要做两件事情,你就可以活得更加轻松自在:
第一:向小卖部注册为会员。
第二:告诉小卖部你需要什么。
这和Spring的做法很类似!Spring就是小卖部,你就是A对象,水就是B对象
第一:在Spring中声明一个类:A
第二:告诉Spring,A需要B
IOC的主要实现方式有依赖注入。
依赖注入,组件不做定位查询,只提供标准的Java方法让容器去决定依赖关系。容器全权负责组件的装配,把符合依赖关系的对象通过Java Bean属性或构造方法传递给需要的对象。
IoC容器:具有依赖注入功能的容器,可以创建对象的容器。IoC容器负责实例化、定位、配置应用程序中的对象并建立这些对象之间的依赖。
DI,英文全称,Dependency Injection,意为依赖注入。
依赖注入:由IoC容器动态地将某个对象所需要的外部资源(包括对象、资源、常量数据)注入到组件(Controller, Service等)之中。简单点说,就是IoC容器会把当前对象所需要的外部资源动态的注入给我们。
Spring依赖注入的方式主要有四个,基于注解注入方式、set注入方式、构造器注入方式、静态工厂注入方式。推荐使用基于注解注入方式,配置较少,比较方便。
服务层代码
@Service
public class AdminService {
//code
}
控制层代码
@Controller
@Scope("prototype")
public class AdminController {
@Autowired
private AdminService adminService;
//code
}
@Autowired与@Resource都可以用来装配Bean,都可以写在字段、setter方法上。他们的区别是:
@Autowired默认按类型进行自动装配(该注解属于Spring),默认情况下要求依赖对象必须存在,如果要允许为null,需设置required属性为false,例:@Autowired(required=false)。如果要使用名称进行装配,可以与@Qualifier注解一起使用。
@Autowired
@Qualifier("adminService")
private AdminService adminService;
@Resource默认按照名称进行装配(该注解属于J2EE),名称可以通过name属性来指定。如果没有指定name属性,当注解写在字段上时,默认取字段名进行装配;如果注解写在setter方法上,默认取属性名进行装配。当找不到与名称相匹配的Bean时,会按照类型进行装配。但是,name属性一旦指定,就只会按照名称进行装配。
@Resource(name = "adminService")
private AdminService adminService;
除此之外,对于一些复杂的装载Bean的时机,比如我们需要根据配置装载不同的Bean,以完成不同的操作,可以使用getBean(“beanID”)的方式来加载Bean。
通过BeanID加载Bean方法如下:
@Component
public class BeanUtils implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
if (BeanUtils.applicationContext == null) {
BeanUtils.applicationContext = applicationContext;
}
}
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
public static Object getBean(String id) throws Exception {
try {
return applicationContext.containsBean(id) ? applicationContext.getBean(id) : null;
} catch (BeansException e) {
e.printStackTrace();
throw new Exception("not found bean id: " + id);
}
}
}
我们在需要装载Bean的地方调用该方法即可
public class BaseController {
protected IService loadService(String id) throws Exception {
IService iService = (IService) BeanUtils.getBean(id);
if (iService != null) {
return iService;
} else {
throw new Exception("加载Bean错误");
}
}
}
//创建类A
public class A {
public A() {
System.out.println("正在初始化类A,调用无参构造器A。。。");
}
public void print() {
System.out.println("调用了类A的print方法。。。");
}
}
//创建类B
public class B {
private A a;
public void setA(A a) {
this.a = a;
}
public void print() {
a.print();
}
}
//配置Beans.xml
<bean id="a" class="com.ssm.ioc.A"/>
<bean id="b" class="com.ssm.ioc.B">
<property name="a" ref="a"/>
</bean>
//测试
@Test
public void test() {
B b = (B) context.getBean("b");
b.print();
}
构造构造器注入
//配置Beans.xml
<bean id="a" class="com.ssm.ioc.A"/>
<bean id="b" class="com.ssm.ioc.B">
<constructor-arg name="a" ref="a"/>
</bean>
//测试
@Test
public void test() {
B b = (B) context.getBean("b");
b.print();
}
//创建工厂类
public class MyBeanFactory {
public static A createCoffer() {
return new A();
}
}
//配置Beans.xml
<bean id="a" class="com.ssm.ioc.MyBeanFactory" factory-method="createA"/>
//测试
@Test
public void test() {
A a = (A) context.getBean("a");
}
//创建工厂类
public class MyBeanFactory {
public MyBeanFactory() {
System.out.println("A工厂实例化中。。。");
}
public A createBean() {
return new A();
}
}
//配置Beans.xml
<bean id="myBeanFactory" class="com.ssm.ioc.MyBeanFactory"/>
<bean id="a" factory-bean="myBeanFactory" factory-method="createBean"/>
//测试
@Test
public void test() {
A a = (A) context.getBean("a");
}
IoC 的思想就是两方之间不互相依赖,由第三方容器来管理相关资源。这样有什么好处呢?
1、对象之间的耦合度或者说依赖程度降低;
2、资源变的容易管理;比如你用 Spring 容器提供的话很容易就可以实现一个单例。
例如:现有一个针对 User 的操作,利用 Service 和 Dao 两层结构进行开发
在没有使用 IoC 思想的情况下,Service 层想要使用 Dao 层的具体实现的话,需要通过 new 关键字在UserServiceImpl 中手动 new 出 IUserDao 的具体实现类 UserDaoImpl(不能直接 new 接口类)。
开发过程中突然接到一个新的需求,针对对IUserDao 接口开发出另一个具体实现类。因为 Server 层依赖了IUserDao的具体实现,所以我们需要修改UserServiceImpl中 new 的对象。如果只有一个类引用了IUserDao的具体实现,可能觉得还好,修改起来也不是很费力气,但是如果有许许多多的地方都引用了IUserDao的具体实现的话,一旦需要更换IUserDao 的实现方式,那修改起来将会非常的头疼。
使用 IoC 的思想,我们将对象的控制权(创建、管理)交有 IoC 容器去管理,我们在使用的时候直接向 IoC 容器 “要” 就可以了
IOC是spring的两大核心概念之一,IOC给我们提供了一个IOCbean容器,这个容器会帮我们自动去创建对象,不需要我们手动创建,IOC实现创建的通过DI(Dependency Injection 依赖注入),我们可以通过写Java注解代码或者是XML配置方式,把我们想要注入对象所依赖的一些其他的bean,自动的注入进去,他是通过byName或byType类型的方式来帮助我们注入。正是因为有了依赖注入,使得IOC有这非常强大的好处,解耦。
可以举个例子,JdbcTemplate 或者 SqlSessionFactory 这种bean,如果我们要把他注入到容器里面,他是需要依赖一个数据源的,如果我们把JdbcTemplate 或者 Druid 的数据源强耦合在一起,会导致一个问题,当我们想要使用jdbctemplate必须要使用Druid数据源,那么依赖注入能够帮助我们在Jdbc注入的时候,只需要让他依赖一个DataSource接口,不需要去依赖具体的实现,这样的好处就是,将来我们给容器里面注入一个Druid数据源,他就会自动注入到JdbcTemplate如果我们注入一个其他的也是一样的。比如说c3p0也是一样的,这样的话,JdbcTemplate和数据源完全的解耦了,不强依赖与任何一个数据源,在spring启动的时候,就会把所有的bean全部创建好,这样的话,程序在运行的时候就不需要创建bean了,运行速度会更快,还有IOC管理bean的时候默认是单例的,可以节省时间,提高性能。
AOP:即面向切面编程
面向切面编程(AOP)就是纵向的编程。比如业务A和业务B现在需要一个相同的操作,传统方法我们可能需要在A、B中都加入相关操作代码,而应用AOP就可以只写一遍代码,A、B共用这段代码。并且,当A、B需要增加新的操作时,可以在不改动原代码的情况下,灵活添加新的业务逻辑实现。
能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
Spring AOP是基于动态代理:如果代理类是实现了接口的类型,那么Spring将会用jdk的Proxy,如果代理类没有实现接口,那么将会使用CJlib的动态代理生成代理类的子类,其底层是使用了字节码操作(ASM)技术。
在实际开发中,比如商品查询、促销查询等业务,都需要记录日志、异常处理等操作,AOP把所有共用代码都剥离出来,单独放置到某个类中进行集中管理,在具体运行时,由容器进行动态织入这些公共代码。
AOP主要一般应用于签名验签、参数校验、日志记录、事务控制、权限控制、性能统计、异常处理等。
切面(Aspect):共有功能的实现。如日志切面、权限切面、验签切面等。在实际开发中通常是一个存放共有功能实现的标准Java类。当Java类使用了@Aspect注解修饰时,就能被AOP容器识别为切面。
通知(Advice):切面的具体实现。就是要给目标对象织入的事情。以目标方法为参照点,根据放置的地方不同,可分为前置通知(Before)、后置通知(AfterReturning)、异常通知(AfterThrowing)、最终通知(After)与环绕通知(Around)5种。在实际开发中通常是切面类中的一个方法,具体属于哪类通知,通过方法上的注解区分。
连接点(JoinPoint):程序在运行过程中能够插入切面的地点。例如,方法调用、异常抛出等。Spring只支持方法级的连接点。一个类的所有方法前、后、抛出异常时等都是连接点。
切入点(Pointcut):用于定义通知应该切入到哪些连接点上。不同的通知通常需要切入到不同的连接点上,这种精准的匹配是由切入点的正则表达式来定义的。
比如,在上面所说的连接点的基础上,来定义切入点。我们有一个类,类里有10个方法,那就产生了几十个连接点。但是我们并不想在所有方法上都织入通知,我们只想让其中的几个方法,在调用之前检验下入参是否合法,那么就用切点来定义这几个方法,让切点来筛选连接点,选中我们想要的方法。切入点就是来定义哪些类里面的哪些方法会得到通知。
目标对象(Target):那些即将切入切面的对象,也就是那些被通知的对象。这些对象专注业务本身的逻辑,所有的共有功能等待AOP容器的切入。
代理对象(Proxy):将通知应用到目标对象之后被动态创建的对象。可以简单地理解为,代理对象的功能等于目标对象本身业务逻辑加上共有功能。代理对象对于使用者而言是透明的,是程序运行过程中的产物。目标对象被织入共有功能后产生的对象。
织入(Weaving):将切面应用到目标对象从而创建一个新的代理对象的过程。这个过程可以发生在编译时、类加载时、运行时。Spring是在运行时完成织入,运行时织入通过Java语言的反射机制与动态代理机制来动态实现。
通过上面的分析可以发现,AOP 主要用来解决:在不改变原有业务逻辑的情况下,增强横切逻辑代码,根本上解耦合,避免横切逻辑代码重复。
切 :指的是横切逻辑,原有业务逻辑代码不动,只能操作横切逻辑代码,所以面向横切逻辑
面 :横切逻辑代码往往要影响的是很多个方法,每个方法如同一个点,多个点构成一个面。这里有一个面的概念
Spring AOP 属于运行时增强,而 AspectJ 是编译时增强。 Spring AOP 基于代理(Proxying),而 AspectJ 基于字节码操作(Bytecode Manipulation)。
Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。AspectJ 相比于 Spring AOP 功能更加强大,但是 Spring AOP 相对来说更简单,
如果我们的切面比较少,那么两者性能差异不大。但是,当切面太多的话,最好选择 AspectJ ,它比Spring AOP 快很多。
MVC是一种设计模式,M(模型),V(视图),C(控制)分层开发。在SpringMVC中我们一般将项目分为四层,业务处理层(Service),数据库操作层(Dao),实体层(Entity),控制层(Controller)。
SpringMVC的工作原理:
在SpringMVC中有一个重要的中央控制器dispatcherServlet,或者叫做中央分发器,他负责接收前端请求,返回结果映射。其步骤如下:
1、浏览器发送请求,tomcat接收到请求之后传给了dispatcherServlet。
2、dispatcher传给了HandlerMapping处理器映射器将将会根据url解析出映射的handler。
3、HandlerAdapter处理器适配器将会调用映射的handler返回一个ModelAndView对象,包括一个Model和View对象。Model是返回的数据,View是逻辑上的View。
4、ViewResover视图解析器将会解析根据逻辑的VIew解析出实际的View。
5、dispatcherServlet将Model传给View。
6、dispatcherServlet返回View给浏览器。
工厂设计模式 : Spring使用工厂模式通过 BeanFactory、ApplicationContext 创建 bean 对象。
代理设计模式 : Spring AOP 功能的实现。
单例设计模式 : Spring 中的 Bean 默认都是单例的。
模板方法模式 : Spring 中 jdbcTemplate、hibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。
包装器设计模式 : 我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。
观察者模式: Spring 事件驱动模型就是观察者模式很经典的一个应用。
适配器模式 :Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式适配Controller。
DAO层,Service层,Controller层、View层