大家好,我是老三啊,面渣逆袭 持续,这节咱们来搞定另一个面试必问知识点——Spring。
有人说,“Java程序员都是Spring程序员”,老三不太赞成这个观念,可是这也能够看出Spring在Java国际里无足轻重的效果。
根底
1.Spring是什么?特性?有哪些模块?
一句话概括:Spring 是一个轻量级、非入侵式的操控回转 (IoC) 和面向切面 (AOP) 的结构。
2003年,一个音乐家Rod Johnson决议发展一个轻量级的Java开发结构,Spring
作为Java战场的龙骑兵逐渐崛起,并淘汰了EJB
这个传统的重装骑兵。
到了现在,企业级开发的标配根本便是 Spring5 + Spring Boot 2 + JDK 8
Spring有哪些特性呢?
Spring有许多长处:
- IOC 和 DI 的支撑
Spring 的中心便是一个大的工厂容器,能够保护一切方针的创立和依靠联系,Spring 工厂用于生成 Bean,而且办理 Bean 的生命周期,完结高内聚低耦合的规划理念。
- AOP 编程的支撑
Spring 供给了面向切面编程,能够便利的完结对程序进行权限阻拦、运转监控等切面功用。
- 声明式业务的支撑
支撑经过装备就来完结对业务的办理,而不需求经过硬编码的办法,曾经重复的一些业务提交、回滚的JDBC代码,都能够不必自己写了。
- 便利测验的支撑
Spring 对 Junit 供给支撑,能够经过注解便利地测验 Spring 程序。
- 快速集成功用
便利集成各种优异结构,Spring 不排挤各种优异的开源结构,其内部供给了对各种优异结构(如:Struts、Hibernate、MyBatis、Quartz 等)的直接支撑。
- 杂乱API模板封装
Spring 对 JavaEE 开发中十分难用的一些 API(JDBC、JavaMail、远程调用等)都供给了模板化的封装,这些封装 API 的供给使得运用难度大大下降。
2.Spring有哪些模块呢?
Spring 结构是分模块存在,除了最中心的Spring Core Container
是必要模块之外,其他模块都是可选
,大约有 20 多个模块。
最首要的七大模块:
- Spring Core:Spring 中心,它是结构最根底的部分,供给 IOC 和依靠注入 DI 特性。
- Spring Context:Spring 上下文容器,它是 BeanFactory 功用加强的一个子接口。
- Spring Web:它供给 Web 运用开发的支撑。
- Spring MVC:它针对 Web 运用中 MVC 思维的完结。
- Spring DAO:供给对 JDBC 笼统层,简化了 JDBC 编码,一起,编码更具有健壮性。
- Spring ORM:它支撑用于盛行的 ORM 结构的整合,比方:Spring + Hibernate、Spring + iBatis、Spring + JDO 的整合等。
- Spring AOP:即面向切面编程,它供给了与 AOP 联盟兼容的编程完结。
3.Spring有哪些常用注解呢?
Spring有许多模块,甚至广义的SpringBoot、SpringCloud也算是Spring的一部分,咱们来分模块,按功用来看一下一些常用的注解:
Web:
- @Controller:组合注解(组合了@Component注解),运用在MVC层(操控层)。
- @RestController:该注解为一个组合注解,相当于@Controller和@ResponseBody的组合,注解在类上,意味着,该Controller的一切办法都默许加上了@ResponseBody。
- @RequestMapping:用于映射Web恳求,包含拜访途径和参数。假设是Restful风格接口,还能够依据恳求类型运用不同的注解:
- @GetMapping
- @PostMapping
- @PutMapping
- @DeleteMapping
- @ResponseBody:支撑将回来值放在response内,而不是一个页面,一般用户回来json数据。
- @RequestBody:允许request的参数在request体中,而不是在直接连接在地址后边。
- @PathVariable:用于接纳途径参数,比方@RequestMapping(“/hello/{name}”)声明的途径,将注解放在参数中前,即可获取该值,一般作为Restful的接口完结办法。
- @RestController:该注解为一个组合注解,相当于@Controller和@ResponseBody的组合,注解在类上,意味着,该Controller的一切办法都默许加上了@ResponseBody。
容器:
- @Component:表示一个带注释的类是一个“组件”,成为Spring办理的Bean。当运用依据注解的装备和类途径扫描时,这些类被视为主动检测的候选方针。一起@Component仍是一个元注解。
- @Service:组合注解(组合了@Component注解),运用在service层(业务逻辑层)。
- @Repository:组合注解(组合了@Component注解),运用在dao层(数据拜访层)。
- @Autowired:Spring供给的东西(由Spring的依靠注入东西(BeanPostProcessor、BeanFactoryPostProcessor)主动注入)。
- @Qualifier:该注解一般跟 @Autowired 一同运用,当想对注入的进程做更多的操控,@Qualifier 可帮助装备,比方两个以上相同类型的 Bean 时 Spring 无法抉择,用到此注解
- @Configuration:声明当时类是一个装备类(相当于一个Spring装备的xml文件)
- @Value:可用在字段,结构器参数跟办法参数,指定一个默许值,支撑 #{} 跟 ${} 两个办法。一般将 SpringbBoot 中的 application.properties 装备的特点值赋值给变量。
- @Bean:注解在办法上,声明当时办法的回来值为一个Bean。回来的Bean对应的类中能够界说init()办法和destroy()办法,然后在@Bean(initMethod=”init”,destroyMethod=”destroy”)界说,在结构之后履行init,在毁掉之前履行destroy。
- @Scope:界说咱们选用什么办法去创立Bean(办法上,得有@Bean) 其设置类型包含:Singleton 、Prototype、Request 、 Session、GlobalSession。
AOP:
- @Aspect:声明一个切面(类上) 运用@After、@Before、@Around界说建言(advice),可直接将阻拦规矩(切点)作为参数。
-
@After
:在办法履行之后履行(办法上)。 -
@Before
: 在办法履行之前履行(办法上)。 -
@Around
: 在办法履行之前与之后履行(办法上)。 -
@PointCut
: 声明切点 在java装备类中运用@EnableAspectJAutoProxy注解敞开Spring对AspectJ署理的支撑(类上)。
-
业务:
- @Transactional:在要敞开业务的办法上运用@Transactional注解,即可声明式敞开业务。
4.Spring 中运用了哪些规划办法呢?
Spring 结构中广泛运用了不同类型的规划办法,下面咱们来看看到底有哪些规划办法?
- 工厂办法 : Spring 容器本质是一个大工厂,运用工厂办法经过 BeanFactory、ApplicationContext 创立 bean 方针。
- 署理办法 : Spring AOP 功用功用便是经过署理办法来完结的,分为动态署理和静态署理。
- 单例办法 : Spring 中的 Bean 默许都是单例的,这样有利于容器对Bean的办理。
- 模板办法 : Spring 中 JdbcTemplate、RestTemplate 等以 Template结尾的对数据库、网络等等进行操作的模板类,就运用到了模板办法。
- 观察者办法: Spring 事件驱动模型便是观察者办法很经典的一个运用。
- 适配器办法 :Spring AOP 的增强或告知 (Advice) 运用到了适配器办法、Spring MVC 中也是用到了适配器办法适配 Controller。
- 战略办法:Spring中有一个Resource接口,它的不同完结类,会依据不同的战略去拜访资源。
IOC
5.说一说什么是IOC?什么是DI?
Java 是面向方针的编程言语,一个个实例方针彼此合作组成了业务逻辑,本来,咱们都是在代码里创立方针和方针的依靠。
所谓的IOC(操控回转):便是由容器来负责操控方针的生命周期和方针间的联系。曾经是咱们想要什么,就自己创立什么,现在是咱们需求什么,容器就给咱们送来什么。
也便是说,操控方针生命周期的不再是引用它的方针,而是容器。对详细方针,曾经是它操控其它方针,现在一切方针都被容器操控,所以这就叫操控回转。
DI(依靠注入):指的是容器在实例化方针的时分把它依靠的类注入给它。有的说法IOC和DI是一回事,有的说法是IOC是思维,DI是IOC的完结。
为什么要运用IOC呢?
最首要的是两个字解耦,硬编码会形成方针间的过度耦合,运用IOC之后,咱们能够不必关怀方针间的依靠,专注开发运用就行。
6.能简略说一下Spring IOC的完结机制吗?
PS:这道题老三在面试中被问到过,问法是“你有自己完结过简略的Spring吗?”
Spring的IOC本质便是一个大工厂,咱们想想一个工厂是怎样运转的呢?
-
出产产品:一个工厂最中心的功用便是出产产品。在Spring里,不必Bean自己来实例化,而是交给Spring,应该怎样完结呢?——答案毫无疑问,反射。
那么这个厂子的出产办理是怎样做的?你应该也知道——工厂办法。
-
库存产品:工厂一般都是有库房的,用来库存产品,究竟出产的产品不能立马就拉走。Spring咱们都知道是一个容器,这个容器里存的便是方针,不能每次来取方针,都得现场来反射创立方针,得把创立出的方针存起来。
-
订单处理:还有最重要的一点,工厂依据什么来供给产品呢?订单。这些订单或许形形色色,有线上签签的、有到工厂签的、还有工厂出售上门签的……最终经过处理,指导工厂的出货。
在Spring里,也有这样的订单,它便是咱们bean的界说和依靠联系,能够是xml办法,也能够是咱们最了解的注解办法。
咱们简略地完结一个mini版的Spring IOC:
Bean界说:
Bean经过一个装备文件界说,把它解析成一个类型。
-
beans.properties
偷懒,这儿直接用了最便利解析的properties,这儿直接用一个<key,value>类型的装备来代表Bean的界说,其中key是beanName,value是class
userDao:cn.fighter3.bean.UserDao
-
BeanDefinition.java
bean界说类,装备文件中bean界说对应的实体
public class BeanDefinition { private String beanName; private Class beanClass; //省掉getter、setter }
-
ResourceLoader.java
资源加载器,用来完结装备文件中装备的加载
public class ResourceLoader { public static Map<String, BeanDefinition> getResource() { Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>(16); Properties properties = new Properties(); try { InputStream inputStream = ResourceLoader.class.getResourceAsStream("/beans.properties"); properties.load(inputStream); Iterator<String> it = properties.stringPropertyNames().iterator(); while (it.hasNext()) { String key = it.next(); String className = properties.getProperty(key); BeanDefinition beanDefinition = new BeanDefinition(); beanDefinition.setBeanName(key); Class clazz = Class.forName(className); beanDefinition.setBeanClass(clazz); beanDefinitionMap.put(key, beanDefinition); } inputStream.close(); } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); } return beanDefinitionMap; } }
-
BeanRegister.java
方针注册器,这儿用于单例bean的缓存,咱们大幅简化,默许一切bean都是单例的。能够看到所谓单例注册,也很简略,不过是往HashMap里存方针。
public class BeanRegister { //单例Bean缓存 private Map<String, Object> singletonMap = new HashMap<>(32); /** * 获取单例Bean * * @param beanName bean称号 * @return */ public Object getSingletonBean(String beanName) { return singletonMap.get(beanName); } /** * 注册单例bean * * @param beanName * @param bean */ public void registerSingletonBean(String beanName, Object bean) { if (singletonMap.containsKey(beanName)) { return; } singletonMap.put(beanName, bean); } }
-
BeanFactory.java
-
方针工厂,咱们最中心的一个类,在它初始化的时分,创立了bean注册器,完结了资源的加载。
-
获取bean的时分,先从单例缓存中取,假设没有取到,就创立并注册一个bean
public class BeanFactory { private Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>(); private BeanRegister beanRegister; public BeanFactory() { //创立bean注册器 beanRegister = new BeanRegister(); //加载资源 this.beanDefinitionMap = new ResourceLoader().getResource(); } /** * 获取bean * * @param beanName bean称号 * @return */ public Object getBean(String beanName) { //从bean缓存中取 Object bean = beanRegister.getSingletonBean(beanName); if (bean != null) { return bean; } //依据bean界说,创立bean return createBean(beanDefinitionMap.get(beanName)); } /** * 创立Bean * * @param beanDefinition bean界说 * @return */ private Object createBean(BeanDefinition beanDefinition) { try { Object bean = beanDefinition.getBeanClass().newInstance(); //缓存bean beanRegister.registerSingletonBean(beanDefinition.getBeanName(), bean); return bean; } catch (InstantiationException | IllegalAccessException e) { e.printStackTrace(); } return null; } }
-
-
测验
-
UserDao.java
咱们的Bean类,很简略
public class UserDao { public void queryUserInfo(){ System.out.println("A good man."); } }
-
单元测验
public class ApiTest { @Test public void test_BeanFactory() { //1.创立bean工厂(一起完结了加载资源、创立注册单例bean注册器的操作) BeanFactory beanFactory = new BeanFactory(); //2.第一次获取bean(经过反射创立bean,缓存bean) UserDao userDao1 = (UserDao) beanFactory.getBean("userDao"); userDao1.queryUserInfo(); //3.第2次获取bean(从缓存中获取bean) UserDao userDao2 = (UserDao) beanFactory.getBean("userDao"); userDao2.queryUserInfo(); } }
-
运转成果
A good man. A good man.
-
至此,咱们一个乞丐+破船版的Spring就完结了,代码也比较完好,有条件的能够跑一下。
PS:因为时刻+篇幅的约束,这个demo比较简陋,没有面向接口、没有解耦、鸿沟检查、反常处理……健壮性、扩展性都有很大的缺乏,感兴趣能够学习参阅[15]。
7.说说BeanFactory和ApplicantContext?
能够这么描述,BeanFactory是Spring的“心脏”,ApplicantContext是完好的“身躯”。
- BeanFactory(Bean工厂)是Spring结构的根底设施,面向Spring自身。
- ApplicantContext(运用上下文)建立在BeanFactoty根底上,面向运用Spring结构的开发者。
BeanFactory 接口
BeanFactory是类的通用工厂,能够创立并办理各品种的方针。
Spring为BeanFactory供给了许多种完结,最常用的是XmlBeanFactory,但在Spring 3.2中已被抛弃,建议运用XmlBeanDefinitionReader、DefaultListableBeanFactory。
BeanFactory接口位于类结构树的顶端,它最首要的办法便是getBean(String var1),这个办法从容器中回来特定称号的Bean。
BeanFactory的功用经过其它的接口得到了不断的扩展,比方AbstractAutowireCapableBeanFactory界说了将容器中的Bean按照某种规矩(比方按姓名匹配、按类型匹配等)进行主动安装的办法。
这儿看一个 XMLBeanFactory(已过期) 获取bean 的例子:
public class HelloWorldApp{
public static void main(String[] args) {
BeanFactory factory = new XmlBeanFactory (new ClassPathResource("beans.xml"));
HelloWorld obj = (HelloWorld) factory.getBean("helloWorld");
obj.getMessage();
}
}
ApplicationContext 接口
ApplicationContext由BeanFactory派生而来,供给了更多面向实践运用的功用。能够这么说,运用BeanFactory便是手动档,运用ApplicationContext便是主动档。
ApplicationContext 承继了HierachicalBeanFactory和ListableBeanFactory接口,在此根底上,还经过其他的接口扩展了BeanFactory的功用,包含:
-
Bean instantiation/wiring
-
Bean 的实例化/串联
-
主动的 BeanPostProcessor 注册
-
主动的 BeanFactoryPostProcessor 注册
-
便利的 MessageSource 拜访(i18n)
-
ApplicationEvent 的发布与 BeanFactory 懒加载的办法不同,它是预加载,所以,每一个 bean 都在 ApplicationContext 发动之后实例化
这是 ApplicationContext 的运用例子:
public class HelloWorldApp{
public static void main(String[] args) {
ApplicationContext context=new ClassPathXmlApplicationContext("beans.xml");
HelloWorld obj = (HelloWorld) context.getBean("helloWorld");
obj.getMessage();
}
}
ApplicationContext 包含 BeanFactory 的一切特性,一般推荐运用前者。
8.你知道Spring容器发动阶段会干什么吗?
PS:这道题老三面试被问到过
Spring的IOC容器作业的进程,其实能够划分为两个阶段:容器发动阶段和Bean实例化阶段。
其中容器发动阶段首要做的作业是加载和解析装备文件,保存到对应的Bean界说中。
容器发动开端,首要会经过某种途径加载Congiguration MetaData,在大部分状况下,容器需求依靠某些东西类(BeanDefinitionReader)对加载的Congiguration MetaData进行解析和剖析,并将剖析后的信息组为相应的BeanDefinition。
最终把这些保存了Bean界说必要信息的BeanDefinition,注册到相应的BeanDefinitionRegistry,这样容器发动就完结了。
9.能说一下Spring Bean生命周期吗?
能够看看:Spring Bean生命周期,如同人的终身。。
在Spring中,根本容器BeanFactory和扩展容器ApplicationContext的实例化机遇不太一样,BeanFactory选用的是推迟初始化的办法,也便是只要在第一次getBean()的时分,才会实例化Bean;ApplicationContext发动之后会实例化一切的Bean界说。
Spring IOC 中Bean的生命周期大致分为四个阶段:实例化(Instantiation)、特点赋值(Populate)、初始化(Initialization)、毁掉(Destruction)。
咱们再来看一个略微详细一些的进程:
- 实例化:第 1 步,实例化一个 Bean 方针
- 特点赋值:第 2 步,为 Bean 设置相关特点和依靠
- 初始化:初始化的阶段的进程比较多,5、6步是真实的初始化,第 3、4 步为在初始化前履行,第 7 步在初始化后履行,初始化完结之后,Bean就能够被运用了
- 毁掉:第 8~10步,第8步其实也能够算到毁掉阶段,但不是真实意义上的毁掉,而是先在运用前注册了毁掉的相关调用接口,为了后边第9、10步真实毁掉 Bean 时再履行相应的办法
简略总结一下,Bean生命周期里初始化的进程相对进程会多一些,比方前置、后置的处理。
最终经过一个实例来看一下详细的细节:
- 界说一个
PersonBean
类,完结DisposableBean
,InitializingBean
,BeanFactoryAware
,BeanNameAware
这4个接口,一起还有自界说的init-method
和destroy-method
。
public class PersonBean implements InitializingBean, BeanFactoryAware, BeanNameAware, DisposableBean {
/**
* 身份证号
*/
private Integer no;
/**
* 姓名
*/
private String name;
public PersonBean() {
System.out.println("1.调用结构办法:我出生了!");
}
public Integer getNo() {
return no;
}
public void setNo(Integer no) {
this.no = no;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
System.out.println("2.设置特点:我的姓名叫"+name);
}
@Override
public void setBeanName(String s) {
System.out.println("3.调用BeanNameAware#setBeanName办法:我要上学了,起了个学名");
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
System.out.println("4.调用BeanFactoryAware#setBeanFactory办法:选好校园了");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("6.InitializingBean#afterPropertiesSet办法:入学挂号");
}
public void init() {
System.out.println("7.自界说init办法:尽力上学ing");
}
@Override
public void destroy() throws Exception {
System.out.println("9.DisposableBean#destroy办法:平淡的终身闭幕了");
}
public void destroyMethod() {
System.out.println("10.自界说destroy办法:睡了,别想叫醒我");
}
public void work(){
System.out.println("Bean运用中:作业,只要对社会没有用的人才放假。。");
}
}
- 界说一个
MyBeanPostProcessor
完结BeanPostProcessor
接口。
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("5.BeanPostProcessor.postProcessBeforeInitialization办法:到校园报名啦");
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("8.BeanPostProcessor#postProcessAfterInitialization办法:总算结业,拿到结业证啦!");
return bean;
}
}
- 装备文件,指定
init-method
和destroy-method
特点
<?xml version="1.0" encoding="UTF-8"?>
<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.xsd">
<bean name="myBeanPostProcessor" class="cn.fighter3.spring.life.MyBeanPostProcessor" />
<bean name="personBean" class="cn.fighter3.spring.life.PersonBean"
init-method="init" destroy-method="destroyMethod">
<property name="idNo" value= "80669865"/>
<property name="name" value="张铁钢" />
</bean>
</beans>
- 测验
public class Main {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
PersonBean personBean = (PersonBean) context.getBean("personBean");
personBean.work();
((ClassPathXmlApplicationContext) context).destroy();
}
}
- 运转成果:
1.调用结构办法:我出生了!
2.设置特点:我的姓名叫张铁钢
3.调用BeanNameAware#setBeanName办法:我要上学了,起了个学名
4.调用BeanFactoryAware#setBeanFactory办法:选好校园了
5.BeanPostProcessor#postProcessBeforeInitialization办法:到校园报名啦
6.InitializingBean#afterPropertiesSet办法:入学挂号
7.自界说init办法:尽力上学ing
8.BeanPostProcessor#postProcessAfterInitialization办法:总算结业,拿到结业证啦!
Bean运用中:作业,只要对社会没有用的人才放假。。
9.DisposableBean#destroy办法:平淡的终身闭幕了
10.自界说destroy办法:睡了,别想叫醒我
关于源码,Bean创立进程能够检查AbstractBeanFactory#doGetBean
办法,在这个办法里能够看到Bean的实例化,赋值、初始化的进程,至于最终的毁掉,能够看看ConfigurableApplicationContext#close()
。
10.Bean界说和依靠界说有哪些办法?
有三种办法:直接编码办法、装备文件办法、注解办法。
- 直接编码办法:咱们一般接触不到直接编码的办法,但其实其它的办法最终都要经过直接编码来完结。
- 装备文件办法:经过xml、propreties类型的装备文件,装备相应的依靠联系,Spring读取装备文件,完结依靠联系的注入。
- 注解办法:注解办法应该是咱们用的最多的一种办法了,在相应的地方运用注解润饰,Spring会扫描注解,完结依靠联系的注入。
11.有哪些依靠注入的办法?
Spring支撑结构办法注入、特点注入、工厂办法注入,其中工厂办法注入,又能够分为静态工厂办法注入和非静态工厂办法注入。
-
结构办法注入
经过调用类的结构办法,将接口完结类经过结构办法变量传入
public CatDaoImpl(String message){ this. message = message; }
<bean id="CatDaoImpl" class="com.CatDaoImpl"> <constructor-arg value=" message "></constructor-arg> </bean>
-
特点注入
经过Setter办法完结调用类所需依靠的注入
public class Id { private int id; public int getId() { return id; } public void setId(int id) { this.id = id; } }
<bean id="id" class="com.id "> <property name="id" value="123"></property> </bean>
-
工厂办法注入
-
静态工厂注入
静态工厂望文生义,便是经过调用静态工厂的办法来获取自己需求的方针,为了让 Spring 办理一切方针,咱们不能直接经过”工程类.静态办法()”来获取方针,而是仍然经过 Spring 注入的办法获取:
public class DaoFactory { //静态工厂 public static final FactoryDao getStaticFactoryDaoImpl(){ return new StaticFacotryDaoImpl(); } } public class SpringAction { //注入方针 private FactoryDao staticFactoryDao; //注入方针的 set 办法 public void setStaticFactoryDao(FactoryDao staticFactoryDao) { this.staticFactoryDao = staticFactoryDao; } }
//factory-method="getStaticFactoryDaoImpl"指定调用哪个工厂办法 <bean name="springAction" class=" SpringAction" > <!--运用静态工厂的办法注入方针,对应下面的装备文件--> <property name="staticFactoryDao" ref="staticFactoryDao"></property> </bean> <!--此处获取方针的办法是从工厂类中获取静态办法--> <bean name="staticFactoryDao" class="DaoFactory" factory-method="getStaticFactoryDaoImpl"></bean>
-
非静态工厂注入
非静态工厂,也叫实例工厂,意思是工厂办法不是静态的,所以咱们需求首要 new 一个工厂实例,再调用普通的实例办法。
//非静态工厂 public class DaoFactory { public FactoryDao getFactoryDaoImpl(){ return new FactoryDaoImpl(); } } public class SpringAction { //注入方针 private FactoryDao factoryDao; public void setFactoryDao(FactoryDao factoryDao) { this.factoryDao = factoryDao; } }
<bean name="springAction" class="SpringAction"> <!--运用非静态工厂的办法注入方针,对应下面的装备文件--> <property name="factoryDao" ref="factoryDao"></property> </bean> <!--此处获取方针的办法是从工厂类中获取实例办法--> <bean name="daoFactory" class="com.DaoFactory"></bean> <bean name="factoryDao" factory-bean="daoFactory" factory-method="getFactoryDaoImpl"></bean>
-
12.Spring有哪些主动安装的办法?
什么是主动安装?
Spring IOC容器知道一切Bean的装备信息,此外,经过Java反射机制还能够获知完结类的结构信息,如结构办法的结构、特点等信息。掌握一切Bean的这些信息后,Spring IOC容器就能够按照某种规矩对容器中的Bean进行主动安装,而无须经过显式的办法进行依靠装备。
Spring供给的这种办法,能够按照某些规矩进行Bean的主动安装,元素供给了一个指定主动安装类型的特点:autowire=”<主动安装类型>”
Spring供给了哪几种主动安装类型?
Spring供给了4种主动安装类型:
- byName:依据称号进行主动匹配,假定Boss又一个名为car的特点,假设容器中刚好有一个名为car的bean,Spring就会主动将其安装给Boss的car特点
- byType:依据类型进行主动匹配,假定Boss有一个Car类型的特点,假设容器中刚好有一个Car类型的Bean,Spring就会主动将其安装给Boss这个特点
- constructor:与 byType类似, 只不过它是针对结构函数注入而言的。假设Boss有一个结构函数,结构函数包含一个Car类型的入参,假设容器中有一个Car类型的Bean,则Spring将主动把这个Bean作为Boss结构函数的入参;假设容器中没有找到和结构函数入参匹配类型的Bean,则Spring将抛出反常。
- autodetect:依据Bean的自省机制决议选用byType仍是constructor进行主动安装,假设Bean供给了默许的结构函数,则选用byType,否则选用constructor。
13.Spring 中的 Bean 的效果域有哪些?
Spring的Bean首要支撑五种效果域:
- singleton : 在Spring容器仅存在一个Bean实例,Bean以单实例的办法存在,是Bean默许的效果域。
- prototype : 每次从容器重调用Bean时,都会回来一个新的实例。
以下三个效果域于只在Web运用中适用:
- request : 每一次HTTP恳求都会发生一个新的Bean,该Bean仅在当时HTTP Request内有效。
- session : 同一个HTTP Session共享一个Bean,不同的HTTP Session运用不同的Bean。
- globalSession:同一个大局Session共享一个Bean,只用于依据Protlet的Web运用,Spring5中现已不存在了。
14.Spring 中的单例 Bean 会存在线程安全问题吗?
首要定论在这:Spring中的单例Bean不是线程安全的。
因为单例Bean,是大局只要一个Bean,一切线程共享。假设说单例Bean,是一个无状况的,也便是线程中的操作不会对Bean中的成员变量履行查询以外的操作,那么这个单例Bean是线程安全的。比方Spring mvc 的 Controller、Service、Dao等,这些Bean大多是无状况的,只重视于办法自身。
假设这个Bean是有状况的,也便是会对Bean中的成员变量进行写操作,那么或许就存在线程安全的问题。
单例Bean线程安全问题怎样处理呢?
常见的有这么些处理办法:
-
将Bean界说为多例
这样每一个线程恳求过来都会创立一个新的Bean,可是这样容器就不好办理Bean,不能这么办。
-
在Bean方针中尽量防止界说可变的成员变量
削足适履了属所以,也不能这么干。
-
将Bean中的成员变量保存在ThreadLocal中⭐
咱们知道ThredLoca能保证多线程下变量的阻隔,能够在类中界说一个ThreadLocal成员变量,将需求的可变成员变量保存在ThreadLocal里,这是推荐的一种办法。
15.说说循环依靠?
什么是循环依靠?
Spring 循环依靠:简略说便是自己依靠自己,或许和别的Bean彼此依靠。
只要单例的Bean才存在循环依靠的状况,原型(Prototype)状况下,Spring会直接抛出反常。原因很简略,AB循环依靠,A实例化的时分,发现依靠B,创立B实例,创立B的时分发现需求A,创立A1实例……无限套娃,直接把体系干垮。
Spring能够处理哪些状况的循环依靠?
Spring不支撑依据结构器注入的循环依靠,可是假设AB循环依靠,假设一个是结构器注入,一个是setter注入呢?
看看几种景象:
第四种能够而第五种不能够的原因是 Spring 在创立 Bean 时默许会依据自然排序进行创立,所以 A 会先于 B 进行创立。
所以简略总结,当循环依靠的实例都选用setter办法注入的时分,Spring能够支撑,都选用结构器注入的时分,不支撑,结构器注入和setter注入一起存在的时分,看天。
16.那Spring怎样处理循环依靠的呢?
PS:其实正确答案是开发人员做好规划,别让Bean循环依靠,可是没办法,面试官不想听这个。
咱们都知道,单例Bean初始化完结,要经历三步:
注入就发生在第二步,特点赋值,结合这个进程,Spring 经过三级缓存处理了循环依靠:
- 一级缓存 : Map<String,Object> singletonObjects,单例池,用于保存实例化、特点赋值(注入)、初始化完结的 bean 实例
- 二级缓存 : Map<String,Object> earlySingletonObjects,前期曝光方针,用于保存实例化完结的 bean 实例
- 三级缓存 : Map<String,ObjectFactory<?>> singletonFactories,前期曝光方针工厂,用于保存 bean 创立工厂,以便于后边扩展有机会创立署理方针。
咱们来看一下三级缓存处理循环依靠的进程:
当 A、B 两个类发生循环依靠时:
A实例的初始化进程:
-
创立A实例,实例化的时分把A方针⼯⼚放⼊三级缓存,表示A开端实例化了,尽管我这个方针还不完好,可是先曝光出来让大家知道
-
A注⼊特点时,发现依靠B,此刻B还没有被创立出来,所以去实例化B
-
同样,B注⼊特点时发现依靠A,它就会从缓存里找A方针。顺次从⼀级到三级缓存查询A,从三级缓存经过方针⼯⼚拿到A,发现A尽管不太完善,可是存在,把A放⼊⼆级缓存,一起删去三级缓存中的A,此刻,B现已实例化而且初始化完结,把B放入⼀级缓存。
-
接着A持续特点赋值,顺畅从⼀级缓存拿到实例化且初始化完结的B方针,A方针创立也完结,删去⼆级缓存中的A,一起把A放⼊⼀级缓存
-
最终,⼀级缓存中保存着实例化、初始化都完结的A、B方针
所以,咱们就知道为什么Spring能处理setter注入的循环依靠了,因为实例化和特点赋值是分隔的,所以里边有操作的空间。假设都是结构器注入的化,那么都得在实例化这一步完结注入,所以自然是无法支撑了。
17.为什么要三级缓存?⼆级不⾏吗?
不可,首要是为了⽣成署理方针。假设是没有署理的状况下,运用二级缓存处理循环依靠也是OK的。可是假设存在署理,三级没有问题,二级就不可了。
因为三级缓存中放的是⽣成详细方针的匿名内部类,获取Object的时分,它能够⽣成署理方针,也能够回来普通方针。使⽤三级缓存首要是为了保证不论什么时分使⽤的都是⼀个方针。
假定只要⼆级缓存的状况,往⼆级缓存中放的显示⼀个普通的Bean方针,Bean初始化进程中,经过 BeanPostProcessor 去⽣成署理方针之后,覆盖掉⼆级缓存中的普通Bean方针,那么或许就导致取到的Bean方针不一致了。
18.@Autowired的完结原理?
完结@Autowired的关键是:AutowiredAnnotationBeanPostProcessor
在Bean的初始化阶段,会经过Bean后置处理器来进行一些前置和后置的处理。
完结@Autowired的功用,也是经过后置处理器来完结的。这个后置处理器便是AutowiredAnnotationBeanPostProcessor。
-
Spring在创立bean的进程中,最终会调用到doCreateBean()办法,在doCreateBean()办法中会调用populateBean()办法,来为bean进行特点填充,完结主动安装等作业。
-
在populateBean()办法中一共调用了两次后置处理器,第一次是为了判断是否需求特点填充,假设不需求进行特点填充,那么就会直接进行return,假设需求进行特点填充,那么办法就会持续向下履行,后边会进行第2次后置处理器的调用,这个时分,就会调用到AutowiredAnnotationBeanPostProcessor的postProcessPropertyValues()办法,在该办法中就会进行@Autowired注解的解析,然后完结主动安装。
/** * 特点赋值 **/ protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) { //………… if (hasInstAwareBpps) { if (pvs == null) { pvs = mbd.getPropertyValues(); } PropertyValues pvsToUse; for(Iterator var9 = this.getBeanPostProcessorCache().instantiationAware.iterator(); var9.hasNext(); pvs = pvsToUse) { InstantiationAwareBeanPostProcessor bp = (InstantiationAwareBeanPostProcessor)var9.next(); pvsToUse = bp.postProcessProperties((PropertyValues)pvs, bw.getWrappedInstance(), beanName); if (pvsToUse == null) { if (filteredPds == null) { filteredPds = this.filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching); } //履行后处理器,填充特点,完结主动安装 //调用InstantiationAwareBeanPostProcessor的postProcessPropertyValues()办法 pvsToUse = bp.postProcessPropertyValues((PropertyValues)pvs, filteredPds, bw.getWrappedInstance(), beanName); if (pvsToUse == null) { return; } } } } //………… }
-
postProcessorPropertyValues()办法的源码如下,在该办法中,会先调用findAutowiringMetadata()办法解析出bean中带有@Autowired注解、@Inject和@Value注解的特点和办法。然后调用metadata.inject()办法,进行特点填充。
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) { //@Autowired注解、@Inject和@Value注解的特点和办法 InjectionMetadata metadata = this.findAutowiringMetadata(beanName, bean.getClass(), pvs); try { //特点填充 metadata.inject(bean, beanName, pvs); return pvs; } catch (BeanCreationException var6) { throw var6; } catch (Throwable var7) { throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", var7); } }
AOP
19.说说什么是AOP?
AOP:面向切面编程。简略说,便是把一些业务逻辑中的相同的代码抽取到一个独立的模块中,让业务逻辑更加清新。
详细来说,假设我现在要crud写一堆业务,可是怎么业务代码前后前后进行打印日志和参数的校验呢?
咱们能够把日志记载
和数据校验
可重用的功用模块别离出来,然后在程序的履行的适宜的地方动态地植入这些代码并履行。这样就简化了代码的书写。
业务逻辑代码中没有参和通用逻辑的代码,业务模块更简洁,只包含中心业务代码。完结了业务逻辑和通用逻辑的代码别离,便于保护和升级,下降了业务逻辑和通用逻辑的耦合性。
AOP 能够将遍布运用遍地的功用别离出来形成可重用的组件。在编译期间、装载期间或运转期间完结在不修正源代码的状况下给程序动态增加功用。然后完结对业务逻辑的阻隔,提高代码的模块化才能。
AOP 的中心其实便是动态署理,假设是完结了接口的话就会运用 JDK 动态署理,否则运用 CGLIB 署理,首要运用于处理一些具有横切性质的体系级服务,如日志收集、业务办理、安全检查、缓存、方针池办理等。
AOP有哪些中心概念?
-
切面(Aspect):类是对物体特征的笼统,切面便是对横切重视点的笼统
-
连接点(Joinpoint):被阻拦到的点,因为 Spring 只支撑办法类型的连接点,所以在 Spring中连接点指的便是被阻拦到的办法,实践上连接点还能够是字段或许结构器
-
切点(Pointcut):对连接点进行阻拦的定位
-
告知(Advice):所谓告知指的便是指阻拦到连接点之后要履行的代码,也能够称作增强
-
方针方针 (Target):署理的方针方针
-
织入(Weabing):织入是将增强增加到方针类的详细连接点上的进程。
-
编译期织入:切面在方针类编译时被织入
-
类加载期织入:切面在方针类加载到JVM时被织入。需求特别的类加载器,它能够在方针类被引进运用之前增强该方针类的字节码。
-
运转期织入:切面在运用运转的某个时刻被织入。一般状况下,在织入切面时,AOP容器会为方针方针动态地创立一个署理方针。SpringAOP便是以这种办法织入切面。
Spring选用运转期织入,而AspectJ选用编译期织入和类加载器织入。
-
-
引介(introduction):引介是一种特别的增强,能够动态地为类增加一些特点和办法
AOP有哪些盘绕办法?
AOP 一般有 5 种盘绕办法:
- 前置告知 (@Before)
- 回来告知 (@AfterReturning)
- 反常告知 (@AfterThrowing)
- 后置告知 (@After)
- 盘绕告知 (@Around)
多个切面的状况下,能够经过 @Order 指定先后顺序,数字越小,优先级越高。
20.说说你平常有用到AOP吗?
PS:这道题老三的同事面试提名人的时分问到了,提名人说了一堆AOP原理,同事就势来一句,你能现场写一下AOP的运用吗?成果——场面一度很尴尬。尽管我对面试写这种百度就能出来的东西持保留意见,可是仍是加上了这一问,究竟招人最终都是要撸代码的。
这儿给出一个小例子,SpringBoot项目中,利用AOP打印接口的入参和出参日志,以及履行时刻,仍是比较便利的。
-
引进依靠:引进AOP依靠
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
-
自界说注解:自界说一个注解作为切点
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) @Documented public @interface WebLog { }
-
装备AOP切面:
-
@Aspect:标识切面
-
@Pointcut:设置切点,这儿以自界说注解为切点,界说切点有许多其它种办法,自界说注解是比较常用的一种。
-
@Before:在切点之前织入,打印了一些入参信息
-
@Around:盘绕切点,打印回来参数和接口履行时刻
@Aspect @Component public class WebLogAspect { private final static Logger logger = LoggerFactory.getLogger(WebLogAspect.class); /** * 以自界说 @WebLog 注解为切点 **/ @Pointcut("@annotation(cn.fighter3.spring.aop_demo.WebLog)") public void webLog() {} /** * 在切点之前织入 */ @Before("webLog()") public void doBefore(JoinPoint joinPoint) throws Throwable { // 开端打印恳求日志 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); // 打印恳求相关参数 logger.info("========================================== Start =========================================="); // 打印恳求 url logger.info("URL : {}", request.getRequestURL().toString()); // 打印 Http method logger.info("HTTP Method : {}", request.getMethod()); // 打印调用 controller 的全途径以及履行办法 logger.info("Class Method : {}.{}", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName()); // 打印恳求的 IP logger.info("IP : {}", request.getRemoteAddr()); // 打印恳求入参 logger.info("Request Args : {}",new ObjectMapper().writeValueAsString(joinPoint.getArgs())); } /** * 在切点之后织入 * @throws Throwable */ @After("webLog()") public void doAfter() throws Throwable { // 结束后打个分隔线,便利检查 logger.info("=========================================== End ==========================================="); } /** * 盘绕 */ @Around("webLog()") public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { //开端时刻 long startTime = System.currentTimeMillis(); Object result = proceedingJoinPoint.proceed(); // 打印出参 logger.info("Response Args : {}", new ObjectMapper().writeValueAsString(result)); // 履行耗时 logger.info("Time-Consuming : {} ms", System.currentTimeMillis() - startTime); return result; } }
-
-
运用:只需求在接口上加上自界说注解
@GetMapping("/hello") @WebLog(desc = "这是一个欢迎接口") public String hello(String name){ return "Hello "+name; }
-
履行成果:能够看到日志打印了入参、出参和履行时刻
21.说说JDK 动态署理和 CGLIB 署理 ?
Spring的AOP是经过动态署理来完结的,动态署理首要有两种办法JDK动态署理和Cglib动态署理,这两种动态署理的运用和原理有些不同。
JDK 动态署理
- Interface:关于 JDK 动态署理,方针类需求完结一个Interface。
- InvocationHandler:InvocationHandler是一个接口,能够经过完结这个接口,界说横切逻辑,再经过反射机制(invoke)调用方针类的代码,在次进程,或许包装逻辑,对方针办法进行前置后置处理。
- Proxy:Proxy利用InvocationHandler动态创立一个符合方针类完结的接口的实例,生成方针类的署理方针。
CgLib 动态署理
- 运用JDK创立署理有一大约束,它只能为接口创立署理实例,而CgLib 动态署理就没有这个约束。
- CgLib 动态署理是运用字节码处理结构 ASM,其原理是经过字节码技能为一个类创立子类,并在子类中选用办法阻拦的技能阻拦一切父类办法的调用,顺势织入横切逻辑。
- CgLib 创立的动态署理方针功用比 JDK 创立的动态署理方针的功用高不少,可是 CGLib 在创立署理方针时所花费的时刻却比 JDK 多得多,所以关于单例的方针,因为无需频繁创立方针,用 CGLib 适宜,反之,运用 JDK 办法要更为适宜一些。一起,因为 CGLib 由所以选用动态创立子类的办法,关于 final 办法,无法进行署理。
咱们来看一个常见的小场景,客服中转,处理用户问题:
JDK动态署理完结:
-
接口
public interface ISolver { void solve(); }
-
方针类:需求完结对应接口
public class Solver implements ISolver { @Override public void solve() { System.out.println("张狂掉头发处理问题……"); } }
-
态署理工厂:ProxyFactory,直接用反射办法生成一个方针方针的署理方针,这儿用了一个匿名内部类办法重写InvocationHandler办法,完结接口重写也差不多
public class ProxyFactory { // 保护一个方针方针 private Object target; public ProxyFactory(Object target) { this.target = target; } // 为方针方针生成署理方针 public Object getProxyInstance() { return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("请问有什么能够帮到您?"); // 调用方针方针办法 Object returnValue = method.invoke(target, args); System.out.println("问题现已处理啦!"); return null; } }); } }
-
客户端:Client,生成一个署理方针实例,经过署理方针调用方针方针办法
public class Client { public static void main(String[] args) { //方针方针:程序员 ISolver developer = new Solver(); //署理:客服小姐姐 ISolver csProxy = (ISolver) new ProxyFactory(developer).getProxyInstance(); //方针办法:处理问题 csProxy.solve(); } }
Cglib动态署理完结:
-
方针类:Solver,这儿方针类不必再完结接口。
public class Solver { public void solve() { System.out.println("张狂掉头发处理问题……"); } }
-
动态署理工厂:
public class ProxyFactory implements MethodInterceptor { //保护一个方针方针 private Object target; public ProxyFactory(Object target) { this.target = target; } //为方针方针生成署理方针 public Object getProxyInstance() { //东西类 Enhancer en = new Enhancer(); //设置父类 en.setSuperclass(target.getClass()); //设置回调函数 en.setCallback(this); //创立子类方针署理 return en.create(); } @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("请问有什么能够帮到您?"); // 履行方针方针的办法 Object returnValue = method.invoke(target, args); System.out.println("问题现已处理啦!"); return null; } }
-
客户端:Client
public class Client { public static void main(String[] args) { //方针方针:程序员 Solver developer = new Solver(); //署理:客服小姐姐 Solver csProxy = (Solver) new ProxyFactory(developer).getProxyInstance(); //方针办法:处理问题 csProxy.solve(); } }
22.说说Spring AOP 和 AspectJ AOP 区别?
Spring AOP
Spring AOP 属于运转时增强
,首要具有如下特点:
-
依据动态署理来完结,默许假设运用接口的,用 JDK 供给的动态署理完结,假设是办规律运用 CGLIB 完结
-
Spring AOP 需求依靠 IOC 容器来办理,而且只能效果于 Spring 容器,运用纯 Java 代码完结
-
在功用上,因为 Spring AOP 是依据动态署理来完结的,在容器发动时需求生成署理实例,在办法调用上也会增加栈的深度,使得 Spring AOP 的功用不如 AspectJ 的那么好。
-
Spring AOP 致力于处理企业级开发中最遍及的 AOP(办法织入)。
AspectJ
AspectJ 是一个易用的功用强大的 AOP 结构,属于编译时增强
, 能够独自运用,也能够整合到其它结构中,是 AOP 编程的完全处理方案。AspectJ 需求用到独自的编译器 ajc。
AspectJ 属于静态织入,经过修正代码来完结,在实践运转之前就完结了织入,所以说它生成的类是没有额定运转时开销的,一般有如下几个织入的机遇:
-
编译期织入(Compile-time weaving):如类 A 运用 AspectJ 增加了一个特点,类 B 引用了它,这个场景就需求编译期的时分就进行织入,否则没法编译类 B。
-
编译后织入(Post-compile weaving):也便是现已生成了 .class 文件,或现已打成 jar 包了,这种状况咱们需求增强处理的话,就要用到编译后织入。
-
类加载后织入(Load-time weaving):指的是在加载类的时分进行织入,要完结这个时期的织入,有几种常见的办法
全体比照方下:
业务
Spring 业务的本质其实便是数据库对业务的支撑,没有数据库的业务支撑,Spring 是无法供给业务功用的。Spring 只供给一致业务办理接口,详细完结都是由各数据库自己完结,数据库业务的提交和回滚是经过数据库自己的业务机制完结。
23.Spring 业务的品种?
Spring 支撑编程式业务
办理和声明式
业务办理两种办法:
- 编程式业务
编程式业务办理运用 TransactionTemplate,需求显式履行业务。
-
声明式业务
-
声明式业务办理建立在 AOP 之上的。其本质是经过 AOP 功用,对办法前后进行阻拦,将业务处理的功用编织到阻拦的办法中,也便是在方针办法开端之前发动一个业务,在履行完方针办法之后依据履行状况提交或许回滚业务
-
长处是不需求在业务逻辑代码中掺杂业务办理的代码,只需在装备文件中做相关的业务规矩声明或经过 @Transactional 注解的办法,便能够将业务规矩运用到业务逻辑中,减少业务代码的污染。唯一缺乏地方是,最细粒度只能效果到办法等级,无法做到像编程式业务那样能够效果到代码块等级。
24.Spring 的业务阻隔等级?
Spring的接口TransactionDefinition中界说了表示阻隔等级的常量,当然其实首要仍是对应数据库的业务阻隔等级:
- ISOLATION_DEFAULT:运用后端数据库默许的阻隔界别,MySQL 默许可重复读,Oracle 默许读已提交。
- ISOLATION_READ_UNCOMMITTED:读未提交
- ISOLATION_READ_COMMITTED:读已提交
- ISOLATION_REPEATABLE_READ:可重复读
- ISOLATION_SERIALIZABLE:串行化
25.Spring 的业务传达机制?
Spring 业务的传达机制说的是,当多个业务一起存在的时分——一般指的是多个业务办法彼此调用时,Spring 怎么处理这些业务的行为。
业务传达机制是运用简略的 ThreadLocal 完结的,所以,假设调用的办法是在新线程调用的,业务传达实践上是会失效的。
Spring默许的业务传达行为是PROPAFATION_REQUIRED,它适合绝大多数状况,假设多个ServiceX#methodX()都作业在业务环境下(均被Spring业务增强),且程序中存在调用链Service1#method1()->Service2#method2()->Service3#method3(),那么这3个服务类的三个办法经过Spring的业务传达机制都作业在同一个业务中。
26.声明式业务完结原理了解吗?
便是经过AOP/动态署理。
-
在Bean初始化阶段创立署理方针:Spring容器在初始化每个单例bean的时分,会遍历容器中的一切BeanPostProcessor完结类,并履行其postProcessAfterInitialization办法,在履行AbstractAutoProxyCreator类的postProcessAfterInitialization办法时会遍历容器中一切的切面,查找与当时实例化bean匹配的切面,这儿会获取业务特点切面,查找@Transactional注解及其特点值,然后依据得到的切面创立一个署理方针,默许是运用JDK动态署理创立署理,假设方针类是接口,则运用JDK动态署理,否则运用Cglib。
-
在履行方针办法时进行业务增强操作:当经过署理方针调用Bean办法的时分,会触发对应的AOP增强阻拦器,声明式业务是一种盘绕增强,对应接口为
MethodInterceptor
,业务增强对该接口的完结为TransactionInterceptor
,类图如下:业务阻拦器
TransactionInterceptor
在invoke
办法中,经过调用父类TransactionAspectSupport
的invokeWithinTransaction
办法进行业务处理,包含敞开业务、业务提交、反常回滚。
27.声明式业务在哪些状况下会失效?
1、@Transactional 运用在非 public 润饰的办法上
假设Transactional注解运用在非 public 润饰的办法上,Transactional将会失效。
是因为在Spring AOP 署理时,TransactionInterceptor (业务阻拦器)在方针办法履行前后进行阻拦,DynamicAdvisedInterceptor(CglibAopProxy 的内部类)的intercept办法 或 JdkDynamicAopProxy的invoke办法会直接调用AbstractFallbackTransactionAttributeSource的 computeTransactionAttribute办法,获取Transactional 注解的业务装备信息。
protected TransactionAttribute computeTransactionAttribute(Method method,
Class<?> targetClass) {
// Don't allow no-public methods as required.
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
此办法会检查方针办法的润饰符是否为 public,不是 public则不会获取@Transactional 的特点装备信息。
2、@Transactional 注解特点 propagation 设置过错
- TransactionDefinition.PROPAGATION_SUPPORTS:假设当时存在业务,则加入该业务;假设当时没有业务,则以非业务的办法持续运转。
- TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非业务办法运转,假设当时存在业务,则把当时业务挂起。
- TransactionDefinition.PROPAGATION_NEVER:以非业务办法运转,假设当时存在业务,则抛出反常。
3、@Transactional 注解特点 rollbackFor 设置过错
rollbackFor 能够指定能够触发业务回滚的反常类型。Spring默许抛出了未检查unchecked反常(承继自 RuntimeException的反常)或许 Error才回滚业务,其他反常不会触发回滚业务。
// 希望自界说的反常能够进行回滚
@Transactional(propagation= Propagation.REQUIRED,rollbackFor= MyException.class
若在方针办法中抛出的反常是 rollbackFor 指定的反常的子类,业务同样会回滚。
4、同一个类中办法调用,导致@Transactional失效
开发中防止不了会对同一个类里边的办法调用,比方有一个类Test,它的一个办法A,A再调用本类的办法B(不论办法B是用public仍是private润饰),但办法A没有声明注解业务,而B办法有。则外部调用办法A之后,办法B的业务是不会起效果的。这也是经常犯过错的一个地方。
那为啥会呈现这种状况?其实这仍是因为运用Spring AOP署理形成的,因为只要当业务办法被当时类以外的代码调用时,才会由Spring生成的署理方针来办理。
//@Transactional
@GetMapping("/test")
private Integer A() throws Exception {
CityInfoDict cityInfoDict = new CityInfoDict();
cityInfoDict.setCityName("2");
/**
* B 刺进字段为 3的数据
*/
this.insertB();
/**
* A 刺进字段为 2的数据
*/
int insert = cityInfoDictMapper.insert(cityInfoDict);
return insert;
}
@Transactional()
public Integer insertB() throws Exception {
CityInfoDict cityInfoDict = new CityInfoDict();
cityInfoDict.setCityName("3");
cityInfoDict.setParentCityId(3);
return cityInfoDictMapper.insert(cityInfoDict);
}
这种状况是最常见的一种@Transactional注解失效场景
@Transactional
private Integer A() throws Exception {
int insert = 0;
try {
CityInfoDict cityInfoDict = new CityInfoDict();
cityInfoDict.setCityName("2");
cityInfoDict.setParentCityId(2);
/**
* A 刺进字段为 2的数据
*/
insert = cityInfoDictMapper.insert(cityInfoDict);
/**
* B 刺进字段为 3的数据
*/
b.insertB();
} catch (Exception e) {
e.printStackTrace();
}
}
假设B办法内部抛了反常,而A办法此刻try catch了B办法的反常,那这个业务就不能正常回滚了,会抛出反常:
org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
MVC
28.Spring MVC 的中心组件?
- DispatcherServlet:前置操控器,是整个流程操控的中心,操控其他组件的履行,进行一致调度,下降组件之间的耦合性,相当于总指挥。
- Handler:处理器,完结详细的业务逻辑,相当于 Servlet 或 Action。
- HandlerMapping:DispatcherServlet 接纳到恳求之后,经过 HandlerMapping 将不同的恳求映射到不同的 Handler。
- HandlerInterceptor:处理器阻拦器,是一个接口,假设需求完结一些阻拦处理,能够完结该接口。
- HandlerExecutionChain:处理器履行链,包含两部分内容:Handler 和 HandlerInterceptor(体系会有一个默许的 HandlerInterceptor,假设需求额定设置阻拦,能够增加阻拦器)。
- HandlerAdapter:处理器适配器,Handler 履行业务办法之前,需求进行一系列的操作,包含表单数据的验证、数据类型的转换、将表单数据封装到 JavaBean 等,这些操作都是由 HandlerApater 来完结,开发者只需将注意力会集业务逻辑的处理上,DispatcherServlet 经过 HandlerAdapter 履行不同的 Handler。
- ModelAndView:装载了模型数据和视图信息,作为 Handler 的处理成果,回来给 DispatcherServlet。
- ViewResolver:视图解析器,DispatcheServlet 经过它将逻辑视图解析为物理视图,最终将烘托成果呼应给客户端。
29.Spring MVC 的作业流程?
- 客户端向服务端发送一次恳求,这个恳求会先到前端操控器DispatcherServlet(也叫中央操控器)。
- DispatcherServlet接纳到恳求后会调用HandlerMapping处理器映射器。由此得知,该恳求该由哪个Controller来处理(并未调用Controller,仅仅得知)
- DispatcherServlet调用HandlerAdapter处理器适配器,告知处理器适配器应该要去履行哪个Controller
- HandlerAdapter处理器适配器去履行Controller并得到ModelAndView(数据和视图),并层层回来给DispatcherServlet
- DispatcherServlet将ModelAndView交给ViewReslover视图解析器解析,然后回来真实的视图。
- DispatcherServlet将模型数据填充到视图中
- DispatcherServlet将成果呼应给客户端
Spring MVC 尽管全体流程杂乱,可是实践开发中很简略,大部分的组件不需求开发人员创立和办理,只需求经过装备文件的办法完结装备即可,真实需求开发人员进行处理的只要 Handler(Controller) 、View 、Model。
当然咱们现在大部分的开发都是前后端别离,Restful风格接口,后端只需求回来Json数据就行了。
30.SpringMVC Restful风格的接口的流程是什么样的呢?
PS:这是一道全新的陈腔滥调,究竟ModelAndView这种办法应该没人用了吧?现在都是前后端别离接口,陈腔滥调也该更新换代了。
咱们都知道Restful接口,呼应格局是json,这就用到了一个常用注解:@ResponseBody
@GetMapping("/user")
@ResponseBody
public User user(){
return new User(1,"张三");
}
加入了这个注解后,全体的流程上和运用ModelAndView大体上相同,可是细节上有一些不同:
-
客户端向服务端发送一次恳求,这个恳求会先到前端操控器DispatcherServlet
-
DispatcherServlet接纳到恳求后会调用HandlerMapping处理器映射器。由此得知,该恳求该由哪个Controller来处理
-
DispatcherServlet调用HandlerAdapter处理器适配器,告知处理器适配器应该要去履行哪个Controller
-
Controller被封装成了ServletInvocableHandlerMethod,HandlerAdapter处理器适配器去履行invokeAndHandle办法,完结对Controller的恳求处理
-
HandlerAdapter履行完对Controller的恳求,会调用HandlerMethodReturnValueHandler去处理回来值,首要的进程:
5.1. 调用RequestResponseBodyMethodProcessor,创立ServletServerHttpResponse(Spring对原生ServerHttpResponse的封装)实例
5.2.运用HttpMessageConverter的write办法,将回来值写入ServletServerHttpResponse的OutputStream输出流中
5.3.在写入的进程中,会运用JsonGenerator(默许运用Jackson结构)对回来值进行Json序列化
-
履行完恳求后,回来的ModealAndView为null,ServletServerHttpResponse里也现已写入了呼应,所以不必关怀View的处理
Spring Boot
31.介绍一下SpringBoot,有哪些长处?
Spring Boot 依据 Spring 开发,Spirng Boot 自身并不供给 Spring 结构的中心特性以及扩展功用,仅仅用于快速、敏捷地开发新一代依据 Spring 结构的运用程序。它并不是用来代替 Spring 的处理方案,而是和 Spring 结构紧密结合用于提升 Spring 开发者体会的东西。
Spring Boot 以约定大于装备
中心思维开展作业,相比Spring具有如下优势:
- Spring Boot 能够快速创立独立的Spring运用程序。
- Spring Boot 内嵌了如Tomcat,Jetty和Undertow这样的容器,也便是说能够直接跑起来,用不着再做部署作业了。
- Spring Boot 无需再像Spring一样运用一堆繁琐的xml文件装备。
- Spring Boot 能够主动装备(中心)Spring。SpringBoot将原有的XML装备改为Java装备,将bean注入改为运用注解注入的办法(@Autowire),并将多个xml、properties装备浓缩在一个appliaction.yml装备文件中。
- Spring Boot 供给了一些现有的功用,如量度东西,表单数据验证以及一些外部装备这样的一些第三方功用。
- Spring Boot 能够快速整合常用依靠(开发库,例如spring-webmvc、jackson-json、validation-api和tomcat等),供给的POM能够简化Maven的装备。当咱们引进中心依靠时,SpringBoot会自引进其他依靠。
32.SpringBoot主动装备原理了解吗?
SpringBoot敞开主动装备的注解是@EnableAutoConfiguration
,发动类上的注解@SpringBootApplication
是一个复合注解,包含了@EnableAutoConfiguration:
-
EnableAutoConfiguration
仅仅一个简略的注解,主动安装中心功用的完结实践是经过AutoConfigurationImportSelector
类@AutoConfigurationPackage //将main同级的包下的一切组件注册到容器中 @Import({AutoConfigurationImportSelector.class}) //加载主动安装类 xxxAutoconfiguration public @interface EnableAutoConfiguration { String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration"; Class<?>[] exclude() default {}; String[] excludeName() default {}; }
-
AutoConfigurationImportSelector
完结了ImportSelector
接口,这个接口的效果便是收集需求导入的装备类,合作@Import()
就能够将相应的类导入到Spring容器中 -
获取注入类的办法是selectImports(),它实践调用的是
getAutoConfigurationEntry
,这个办法是获取主动安装类的关键,首要流程能够分为这么几步:- 获取注解的特点,用于后边的扫除
- 获取一切需求主动安装的装备类的途径:这一步是最关键的,从META-INF/spring.factories获取主动装备类的途径
- 去掉重复的装备类和需求扫除的重复类,把需求主动加载的装备类的途径存储起来
protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
//1.获取到注解的特点
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
//2.获取需求主动安装的一切装备类,读取META-INF/spring.factories,获取主动装备类途径
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
//3.1.移除重复的装备
configurations = this.removeDuplicates(configurations);
//3.2.处理需求扫除的装备
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = this.getConfigurationClassFilter().filter(configurations);
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
}
}
33.怎么自界说一个SpringBoot Srarter?
知道了主动装备原理,创立一个自界说SpringBoot Starter也很简略。
- 创立一个项目,命名为demo-spring-boot-starter,引进SpringBoot相关依靠
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
-
编写装备文件
这儿界说了特点装备的前缀
@ConfigurationProperties(prefix = "hello") public class HelloProperties { private String name; //省掉getter、setter }
-
主动安装
创立主动装备类HelloPropertiesConfigure
@Configuration @EnableConfigurationProperties(HelloProperties.class) public class HelloPropertiesConfigure { }
-
装备主动类
在
/resources/META-INF/spring.factories
文件中增加主动装备类途径org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ cn.fighter3.demo.starter.configure.HelloPropertiesConfigure
-
测验
-
创立一个工程,引进自界说starter依靠
<dependency> <groupId>cn.fighter3</groupId> <artifactId>demo-spring-boot-starter</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency>
-
在装备文件里增加装备
hello.name=张三
-
测验类
@RunWith(SpringRunner.class) @SpringBootTest public class HelloTest { @Autowired HelloProperties helloProperties; @Test public void hello(){ System.out.println("你好,"+helloProperties.getName()); } }
-
运转成果
至此,随手写的一个自界说SpringBoot-Starter就完结了,尽管比较简略,可是完结了首要的主动安装的才能。
-
34.Springboot 发动原理?
SpringApplication 这个类首要做了以下四件工作:
- 揣度运用的类型是普通的项目仍是 Web 项目
- 查找并加载一切可用初始化器 , 设置到 initializers 特点中
- 找出一切的运用程序监听器,设置到 listeners 特点中
- 揣度并设置 main 办法的界说类,找到运转的主类
SpringBoot 发动大致流程如下 :
Spring Cloud
35.对SpringCloud了解多少?
SpringCloud是Spring官方推出的微服务治理结构。
什么是微服务?
- 2014 年 Martin Fowler 提出的一种新的架构办法。微服务架构是一种架构办法,提倡将单一运用程序划分红一组小的服务,服务之间彼此和谐,互相合作,为用户供给最终价值。每个服务运转在其独立的进程中,服务与服务之间选用轻量级的通讯机制(如HTTP或Dubbo)互相协作,每个服务都围绕着详细的业务进行构建,而且能够被独立的部署到出产环境中,别的,应尽量防止一致的,会集式的服务办理机制,对详细的一个服务而言,应依据业务上下文,挑选适宜的言语、东西(如Maven)对其进行构建。
- 微服务化的中心便是将传统的一站式运用,依据业务拆分红一个一个的服务,完全地去耦合,每一个微服务供给单个业务功用的服务,一个服务做一件工作,从技能角度看便是一种小而独立的处理进程,类似进程的概念,能够自行独自发动或毁掉,具有自己独立的数据库。
微服务架构首要要处理哪些问题?
- 服务许多,客户端怎样拜访,怎么供给对外网关?
- 这么多服务,服务之间怎么通讯? HTTP仍是RPC?
- 这么多服务,怎么治理? 服务的注册和发现。
- 服务挂了怎样办?熔断机制。
有哪些主流微服务结构?
- Spring Cloud Netflix
- Spring Cloud Alibaba
- SpringBoot + Dubbo + ZooKeeper
SpringCloud有哪些中心组件?
PS:微服务后边有机会再扩展,其实面试一般都是结合项目去问。
参阅:
[1]. 《Spring揭秘》
[2]. 面试官:关于Spring就问这13个
[3]. 15个经典的Spring面试常见问题
[4].面试还不知道BeanFactory和ApplicationContext的区别?
[5]. Java面试中常问的Spring方面问题(包括七大方向共55道题,含答案)
[6] .Spring Bean 生命周期 (实例结合源码完全讲透)
[7]. @Autowired注解的完结原理
[8].万字长文,带你从源码认识Spring业务原理,让Spring业务不再是面试噩梦
[9].【技能干货】Spring业务原理一探
[10]. Spring的声明式业务@Transactional注解的6种失效场景
[11].Spring官网
[12].Spring运用了哪些规划办法?
[13].《通晓Spring4.X企业运用开发实战》
[14].Spring 中的bean 是线程安全的吗?
[15].小傅哥 《手撸Spring》
[16].手撸架构,Spring 面试63问
[17]. @Autowired注解的完结原理
[18].怎么高雅地在 Spring Boot 中运用自界说注解
[19].Spring MVC源码(三) —– @RequestBody和@ResponseBody原理解析
面渣逆袭系列文章首发公众号:三分恶,欢迎重视!