本文曾经收录到Github仓库,该仓库蕴含计算机根底、Java根底、多线程、JVM、数据库、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分布式、微服务、设计模式、架构、校招社招分享等外围知识点,欢送star~
Github地址:https://github.com/Tyson0314/...
Spring的长处
- 通过管制反转和依赖注入实现松耦合。
- 反对面向切面的编程,并且把利用业务逻辑和零碎服务离开。
- 通过切面和模板缩小样板式代码。
- 申明式事务的反对。能够从枯燥繁冗的事务管理代码中解脱进去,通过申明式形式灵便地进行事务的治理,进步开发效率和品质。
- 不便集成各种优良框架。外部提供了对各种优良框架的间接反对(如:Hessian、Quartz、MyBatis等)。
- 不便程序的测试。Spring反对Junit4,增加注解便能够测试Spring程序。
Spring 用到了哪些设计模式?
1、简略工厂模式:BeanFactory
就是简略工厂模式的体现,依据传入一个惟一标识来取得 Bean 对象。
@Overridepublic Object getBean(String name) throws BeansException { assertBeanFactoryActive(); return getBeanFactory().getBean(name);}
2、工厂办法模式:FactoryBean
就是典型的工厂办法模式。spring在应用getBean()
调用取得该bean时,会主动调用该bean的getObject()
办法。每个 Bean 都会对应一个 FactoryBean
,如 SqlSessionFactory
对应 SqlSessionFactoryBean
。
3、单例模式:一个类仅有一个实例,提供一个拜访它的全局拜访点。Spring 创立 Bean 实例默认是单例的。
4、适配器模式:SpringMVC中的适配器HandlerAdatper
。因为利用会有多个Controller实现,如果须要间接调用Controller办法,那么须要先判断是由哪一个Controller解决申请,而后调用相应的办法。当减少新的 Controller,须要批改原来的逻辑,违反了开闭准则(对批改敞开,对扩大凋谢)。
为此,Spring提供了一个适配器接口,每一种 Controller 对应一种 HandlerAdapter
实现类,当申请过去,SpringMVC会调用getHandler()
获取相应的Controller,而后获取该Controller对应的 HandlerAdapter
,最初调用HandlerAdapter
的handle()
办法解决申请,实际上调用的是Controller的handleRequest()
。每次增加新的 Controller 时,只须要减少一个适配器类就能够,无需批改原有的逻辑。
罕用的处理器适配器:SimpleControllerHandlerAdapter
,HttpRequestHandlerAdapter
,AnnotationMethodHandlerAdapter
。
// Determine handler for the current request.mappedHandler = getHandler(processedRequest);HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());// Actually invoke the handler.mv = ha.handle(processedRequest, response, mappedHandler.getHandler());public class HttpRequestHandlerAdapter implements HandlerAdapter { @Override public boolean supports(Object handler) {//handler是被适配的对象,这里应用的是对象的适配器模式 return (handler instanceof HttpRequestHandler); } @Override @Nullable public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { ((HttpRequestHandler) handler).handleRequest(request, response); return null; }}
5、代理模式:spring 的 aop 应用了动静代理,有两种形式JdkDynamicAopProxy
和Cglib2AopProxy
。
6、观察者模式:spring 中 observer 模式罕用的中央是 listener 的实现,如ApplicationListener
。
7、模板模式: Spring 中 jdbcTemplate
、hibernateTemplate
等,就应用到了模板模式。
什么是AOP?
面向切面编程,作为面向对象的一种补充,将公共逻辑(事务管理、日志、缓存等)封装成切面,跟业务代码进行拆散,能够缩小零碎的反复代码和升高模块之间的耦合度。切面就是那些与业务无关,但所有业务模块都会调用的公共逻辑。
AOP有哪些实现形式?
AOP有两种实现形式:动态代理和动静代理。
动态代理
动态代理:代理类在编译阶段生成,在编译阶段将告诉织入Java字节码中,也称编译时加强。AspectJ应用的是动态代理。
毛病:代理对象须要与指标对象实现一样的接口,并且实现接口的办法,会有冗余代码。同时,一旦接口减少办法,指标对象与代理对象都要保护。
动静代理
动静代理:代理类在程序运行时创立,AOP框架不会去批改字节码,而是在内存中长期生成一个代理对象,在运行期间对业务办法进行加强,不会生成新类。
Spring AOP的实现原理
Spring
的AOP
实现原理其实很简略,就是通过动静代理实现的。如果咱们为Spring
的某个bean
配置了切面,那么Spring
在创立这个bean
的时候,实际上创立的是这个bean
的一个代理对象,咱们后续对bean
中办法的调用,实际上调用的是代理类重写的代理办法。而Spring
的AOP
应用了两种动静代理,别离是JDK的动静代理,以及CGLib的动静代理。
JDK动静代理和CGLIB动静代理的区别?
Spring AOP中的动静代理次要有两种形式:JDK动静代理和CGLIB动静代理。
JDK动静代理
如果指标类实现了接口,Spring AOP会抉择应用JDK动静代理指标类。代理类依据指标类实现的接口动静生成,不须要本人编写,生成的动静代理类和指标类都实现雷同的接口。JDK动静代理的外围是InvocationHandler
接口和Proxy
类。
毛病:指标类必须有实现的接口。如果某个类没有实现接口,那么这个类就不能用JDK动静代理。
CGLIB动静代理
通过继承实现。如果指标类没有实现接口,那么Spring AOP会抉择应用CGLIB来动静代理指标类。CGLIB(Code Generation Library)能够在运行时动静生成类的字节码,动态创建指标类的子类对象,在子类对象中加强指标类。
CGLIB是通过继承的形式做的动静代理,因而如果某个类被标记为final
,那么它是无奈应用CGLIB做动静代理的。
长处:指标类不须要实现特定的接口,更加灵便。
什么时候采纳哪种动静代理?
- 如果指标对象实现了接口,默认状况下会采纳JDK的动静代理实现AOP
- 如果指标对象实现了接口,能够强制应用CGLIB实现AOP
- 如果指标对象没有实现了接口,必须采纳CGLIB库
两者的区别:
- jdk动静代理应用jdk中的类Proxy来创立代理对象,它应用反射技术来实现,不须要导入其余依赖。cglib须要引入相干依赖:
asm.jar
,它应用字节码加强技术来实现。 - 当指标类实现了接口的时候Spring Aop默认应用jdk动静代理形式来加强办法,没有实现接口的时候应用cglib动静代理形式加强办法。
Spring AOP相干术语
(1)切面(Aspect):切面是告诉和切点的联合。告诉和切点独特定义了切面的全部内容。
(2)连接点(Join point):指办法,在Spring AOP中,一个连接点总是代表一个办法的执行。连接点是在利用执行过程中可能插入切面的一个点。这个点能够是调用办法时、抛出异样时、甚至批改一个字段时。切面代码能够利用这些点插入到利用的失常流程之中,并增加新的行为。
(3)告诉(Advice):在AOP术语中,切面的工作被称为告诉。
(4)切入点(Pointcut):切点的定义会匹配告诉所要织入的一个或多个连接点。咱们通常应用明确的类和办法名称,或是利用正则表达式定义所匹配的类和办法名称来指定这些切点。
(5)引入(Introduction):引入容许咱们向现有类增加新办法或属性。
(6)指标对象(Target Object): 被一个或者多个切面(aspect)所告诉(advise)的对象。它通常是一个代理对象。
(7)织入(Weaving):织入是把切面利用到指标对象并创立新的代理对象的过程。在指标对象的生命周期里有以下工夫点能够进行织入:
- 编译期:切面在指标类编译时被织入。AspectJ的织入编译器是以这种形式织入切面的。
- 类加载期:切面在指标类加载到JVM时被织入。须要非凡的类加载器,它能够在指标类被引入利用之前加强该指标类的字节码。AspectJ5的加载时织入就反对以这种形式织入切面。
- 运行期:切面在利用运行的某个时刻被织入。个别状况下,在织入切面时,AOP容器会为指标对象动静地创立一个代理对象。SpringAOP就是以这种形式织入切面。
Spring告诉有哪些类型?
在AOP术语中,切面的工作被称为告诉。告诉实际上是程序运行时要通过Spring AOP框架来触发的代码段。
Spring切面能够利用5种类型的告诉:
- 前置告诉(Before):在指标办法被调用之前调用告诉性能;
- 后置告诉(After):在指标办法实现之后调用告诉,此时不会关怀办法的输入是什么;
- 返回告诉(After-returning ):在指标办法胜利执行之后调用告诉;
- 异样告诉(After-throwing):在指标办法抛出异样后调用告诉;
- 盘绕告诉(Around):告诉包裹了被告诉的办法,在被告诉的办法调用之前和调用之后执行自定义的逻辑。
什么是IOC?
IOC:管制反转,由Spring容器治理bean的整个生命周期。通过反射实现对其余对象的管制,包含初始化、创立、销毁等,解放手动创建对象的过程,同时升高类之间的耦合度。
IOC的益处?
ioc的思维最外围的中央在于,资源不禁应用资源者治理,而由不应用资源的第三方治理,这能够带来很多益处。第一,资源集中管理,实现资源的可配置和易治理。第二,升高了应用资源单方的依赖水平,也就是咱们说的耦合度。
也就是说,甲方要达成某种目标不须要间接依赖乙方,它只须要达到的目标通知第三方机构就能够了,比方甲方须要一双袜子,而乙方它卖一双袜子,它要把袜子卖出去,并不需要本人去间接找到一个卖家来实现袜子的卖出。它也只须要找第三方,通知他人我要卖一双袜子。这下好了,甲乙双方进行交易流动,都不须要本人间接去找卖家,相当于程序外部凋谢接口,卖家由第三方作为参数传入。甲乙相互不依赖,而且只有在进行交易流动的时候,甲才和乙产生分割。反之亦然。这样做什么益处么呢,甲乙能够在对方不实在存在的状况下独立存在,而且保障不交易时候无分割,想交易的时候能够很容易的产生分割。甲乙交易流动不须要单方见面,防止了单方的互不信赖造成交易失败的问题。因为交易由第三方来负责分割,而且甲乙都认为第三方牢靠。那么交易就能很牢靠很灵便的产生和进行了。
这就是ioc的核心思想。生存中这种例子亘古未有,支付宝在整个淘宝体系里就是宏大的ioc容器,交易单方之外的第三方,提供可靠性可依赖可灵便变更交易方的资源管理核心。另外人事代理也是,雇佣机构和集体之外的第三方。
参考链接:https://www.zhihu.com/questio...
什么是依赖注入?
在Spring创建对象的过程中,把对象依赖的属性注入到对象中。依赖注入次要有两种形式:结构器注入和属性注入。
IOC容器初始化过程?
- 从XML中读取配置文件。
- 将bean标签解析成 BeanDefinition,如解析 property 元素, 并注入到 BeanDefinition 实例中。
- 将 BeanDefinition 注册到容器 BeanDefinitionMap 中。
- BeanFactory 依据 BeanDefinition 的定义信息创立实例化和初始化 bean。
单例bean的初始化以及依赖注入个别都在容器初始化阶段进行,只有懒加载(lazy-init为true)的单例bean是在利用第一次调用getBean()时进行初始化和依赖注入。
// AbstractApplicationContext// Instantiate all remaining (non-lazy-init) singletons.finishBeanFactoryInitialization(beanFactory);
多例bean 在容器启动时不实例化,即便设置 lazy-init 为 false 也没用,只有调用了getBean()才进行实例化。
loadBeanDefinitions
采纳了模板模式,具体加载 BeanDefinition
的逻辑由各个子类实现。
Bean的生命周期
1.调用bean的构造方法创立Bean
2.通过反射调用setter办法进行属性的依赖注入
3.如果Bean实现了BeanNameAware
接口,Spring将调用setBeanName
(),设置 Bean
的name(xml文件中bean标签的id)
4.如果Bean实现了BeanFactoryAware
接口,Spring将调用setBeanFactory()
把bean factory设置给Bean
5.如果存在BeanPostProcessor
,Spring将调用它们的postProcessBeforeInitialization
(预初始化)办法,在Bean初始化前对其进行解决
6.如果Bean实现了InitializingBean
接口,Spring将调用它的afterPropertiesSet
办法,而后调用xml定义的 init-method 办法,两个办法作用相似,都是在初始化 bean 的时候执行
7.如果存在BeanPostProcessor
,Spring将调用它们的postProcessAfterInitialization
(后初始化)办法,在Bean初始化后对其进行解决
8.Bean初始化实现,供给用应用,这里分两种状况:
8.1 如果Bean为单例的话,那么容器会返回Bean给用户,并存入缓存池。如果Bean实现了DisposableBean
接口,Spring将调用它的destory
办法,而后调用在xml中定义的 destory-method
办法,这两个办法作用相似,都是在Bean实例销毁前执行。
8.2 如果Bean是多例的话,容器将Bean返回给用户,剩下的生命周期由用户管制。
public interface BeanPostProcessor { @Nullable default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; } @Nullable default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { return bean; }}public interface InitializingBean { void afterPropertiesSet() throws Exception;}
BeanFactory和FactoryBean的区别?
BeanFactory:治理Bean的容器,Spring中生成的Bean都是由这个接口的实现来治理的。
FactoryBean:通常是用来创立比较复杂的bean,个别的bean 间接用xml配置即可,但如果一个bean的创立过程中波及到很多其余的bean 和简单的逻辑,间接用xml配置比拟麻烦,这时能够思考用FactoryBean,能够暗藏实例化简单Bean的细节。
当配置文件中bean标签的class属性配置的实现类是FactoryBean时,通过 getBean()办法返回的不是FactoryBean自身,而是调用FactoryBean#getObject()办法所返回的对象,相当于FactoryBean#getObject()代理了getBean()办法。如果想得到FactoryBean必须应用 '&' + beanName 的形式获取。
Mybatis 提供了 SqlSessionFactoryBean
,能够简化 SqlSessionFactory
的配置:
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> { @Override public void afterPropertiesSet() throws Exception { notNull(dataSource, "Property 'dataSource' is required"); notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required"); state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null), "Property 'configuration' and 'configLocation' can not specified with together"); this.sqlSessionFactory = buildSqlSessionFactory(); } protected SqlSessionFactory buildSqlSessionFactory() throws IOException { //简单逻辑 } @Override public SqlSessionFactory getObject() throws Exception { if (this.sqlSessionFactory == null) { afterPropertiesSet(); } return this.sqlSessionFactory; }}
在 xml 配置 SqlSessionFactoryBean:
<bean id="tradeSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="trade" /> <property name="mapperLocations" value="classpath*:mapper/trade/*Mapper.xml" /> <property name="configLocation" value="classpath:mybatis-config.xml" /> <property name="typeAliasesPackage" value="com.bytebeats.mybatis3.domain.trade" /></bean>
Spring 将会在利用启动时创立 SqlSessionFactory
,并应用 sqlSessionFactory
这个名字存储起来。
BeanFactory和ApplicationContext有什么区别?
BeanFactory和ApplicationContext是Spring的两大外围接口,都能够当做Spring的容器。其中ApplicationContext是BeanFactory的子接口。
两者区别如下:
1、性能上的区别。BeanFactory是Spring外面最底层的接口,蕴含了各种Bean的定义,读取bean配置文档,治理bean的加载、实例化,管制bean的生命周期,保护bean之间的依赖关系。
ApplicationContext接口作为BeanFactory的派生,除了提供BeanFactory所具备的性能外,还提供了更残缺的框架性能,如继承MessageSource、反对国际化、对立的资源文件拜访形式、同时加载多个配置文件等性能。
2、加载形式的区别。BeanFactroy采纳的是提早加载模式来注入Bean的,即只有在应用到某个Bean时(调用getBean()),才对该Bean进行加载实例化。这样,咱们就不能发现一些存在的Spring的配置问题。如果Bean的某一个属性没有注入,BeanFacotry加载后,直至第一次应用调用getBean办法才会抛出异样。
而ApplicationContext是在容器启动时,一次性创立了所有的Bean。这样,在容器启动时,咱们就能够发现Spring中存在的配置谬误,这样有利于查看所依赖属性是否注入。 ApplicationContext启动后预载入所有的单例Bean,那么在须要的时候,不须要期待创立bean,因为它们曾经创立好了。
绝对于根本的BeanFactory,ApplicationContext 惟一的有余是占用内存空间。当应用程序配置Bean较多时,程序启动较慢。
3、创立形式的区别。BeanFactory通常以编程的形式被创立,ApplicationContext还能以申明的形式创立,如应用ContextLoader。
4、注册形式的区别。BeanFactory和ApplicationContext都反对BeanPostProcessor、BeanFactoryPostProcessor的应用,但两者之间的区别是:BeanFactory须要手动注册,而ApplicationContext则是主动注册。
Bean注入容器有哪些形式?
1、@Configuration + @Bean
@Configuration用来申明一个配置类,而后应用 @Bean 注解,用于申明一个bean,将其退出到Spring容器中。
@Configurationpublic class MyConfiguration { @Bean public Person person() { Person person = new Person(); person.setName("大彬"); return person; }}
2、通过包扫描特定注解的形式
@ComponentScan搁置在咱们的配置类上,而后能够指定一个门路,进行扫描带有特定注解的bean,而后加至容器中。
特定注解包含@Controller、@Service、@Repository、@Component
@Componentpublic class Person { //...} @ComponentScan(basePackages = "com.dabin.test.*")public class Demo1 { public static void main(String[] args) { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Demo1.class); Person bean = applicationContext.getBean(Person.class); System.out.println(bean); }}
3、@Import注解导入
@Import注解平时开发用的不多,然而也是十分重要的,在进行Spring扩大时常常会用到,它常常搭配自定义注解进行应用,而后往容器中导入一个配置文件。
@ComponentScan/*把用到的资源导入到以后容器中*/@Import({Person.class})public class App { public static void main(String[] args) throws Exception { ConfigurableApplicationContext context = SpringApplication.run(App.class, args); System.out.println(context.getBean(Person.class)); context.close(); }}
4、实现BeanDefinitionRegistryPostProcessor进行后置解决。
在Spring容器启动的时候会执行 BeanDefinitionRegistryPostProcessor 的 postProcessBeanDefinitionRegistry 办法,就是等beanDefinition加载结束之后,对beanDefinition进行后置解决,能够在此进行调整IOC容器中的beanDefinition,从而烦扰到前面进行初始化bean。
在上面的代码中,咱们手动向beanDefinitionRegistry中注册了person的BeanDefinition。最终胜利将person退出到applicationContext中。
public class Demo1 { public static void main(String[] args) { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(); MyBeanDefinitionRegistryPostProcessor beanDefinitionRegistryPostProcessor = new MyBeanDefinitionRegistryPostProcessor(); applicationContext.addBeanFactoryPostProcessor(beanDefinitionRegistryPostProcessor); applicationContext.refresh(); Person bean = applicationContext.getBean(Person.class); System.out.println(bean); }} class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor { @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(Person.class).getBeanDefinition(); registry.registerBeanDefinition("person", beanDefinition); } @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { }}
5、应用FactoryBean接口
如下图代码,应用@Configuration + @Bean的形式将 PersonFactoryBean 退出到容器中,这里没有向容器中间接注入 Person,而是注入 PersonFactoryBean,而后从容器中拿Person这个类型的bean。
@Configurationpublic class Demo1 { @Bean public PersonFactoryBean personFactoryBean() { return new PersonFactoryBean(); } public static void main(String[] args) { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Demo1.class); Person bean = applicationContext.getBean(Person.class); System.out.println(bean); }} class PersonFactoryBean implements FactoryBean<Person> { @Override public Person getObject() throws Exception { return new Person(); } @Override public Class<?> getObjectType() { return Person.class; }}
Bean的作用域
1、singleton:单例,Spring中的bean默认都是单例的。
2、prototype:每次申请都会创立一个新的bean实例。
3、request:每一次HTTP申请都会产生一个新的bean,该bean仅在以后HTTP request内无效。
4、session:每一次HTTP申请都会产生一个新的bean,该bean仅在以后HTTP session内无效。
5、global-session:全局session作用域。
Spring主动拆卸的形式有哪些?
Spring的主动拆卸有三种模式:byType(依据类型),byName(依据名称)、constructor(依据构造函数)。
byType
找到与依赖类型雷同的bean注入到另外的bean中,这个过程须要借助setter注入来实现,因而必须存在set办法,否则注入失败。
当xml文件中存在多个雷同类型名称不同的实例Bean时,Spring容器依赖注入依然会失败,因为存在多种适宜的选项,Spring容器无奈晓得该注入那种,此时咱们须要为Spring容器提供帮忙,指定注入那个Bean实例。能够通过<bean>
标签的autowire-candidate设置为false来过滤那些不须要注入的实例Bean
<bean id="userDao" class="com.zejian.spring.springIoc.dao.impl.UserDaoImpl" /><!-- autowire-candidate="false" 过滤该类型 --><bean id="userDao2" autowire-candidate="false" class="com.zejian.spring.springIoc.dao.impl.UserDaoImpl" /><!-- byType 依据类型主动拆卸userDao--><bean id="userService" autowire="byType" class="com.zejian.spring.springIoc.service.impl.UserServiceImpl" />
byName
将属性名与bean名称进行匹配,如果找到则注入依赖bean。
<bean id="userDao" class="com.zejian.spring.springIoc.dao.impl.UserDaoImpl" /><bean id="userDao2" class="com.zejian.spring.springIoc.dao.impl.UserDaoImpl" /><!-- byName 依据名称主动拆卸,找到UserServiceImpl名为 userDao属性并注入--><bean id="userService" autowire="byName" class="com.zejian.spring.springIoc.service.impl.UserServiceImpl" />
constructor
存在单个实例则优先按类型进行参数匹配(无论名称是否匹配),当存在多个类型雷同实例时,按名称优先匹配,如果没有找到对应名称,则注入失败。
@Autowired和@Resource的区别?
Autowire是spring的注解。默认状况下@Autowired是按类型匹配的(byType)。如果须要按名称(byName)匹配的话,能够应用@Qualifier注解与@Autowired联合。@Autowired 能够传递一个required=false
的属性,false指明当userDao实例存在就注入不存就疏忽,如果为true,就必须注入,若userDao实例不存在,就抛出异样。
public class UserServiceImpl implements UserService { //标注成员变量 @Autowired @Qualifier("userDao1") private UserDao userDao; }
Resource是j2ee的注解,默认按 byName模式主动注入。@Resource有两个中重要的属性:name和type。name属性指定bean的名字,type属性则指定bean的类型。因而应用name属性,则按byName模式的主动注入策略,如果应用type属性,则按 byType模式主动注入策略。假使既不指定name也不指定type属性,Spring容器将通过反射技术默认按byName模式注入。
@Resource(name="userDao")private UserDao userDao;//用于成员变量//也能够用于set办法标注@Resource(name="userDao")public void setUserDao(UserDao userDao) { this.userDao= userDao;}
上述两种主动拆卸的依赖注入并不适宜简略值类型,如int、boolean、long、String以及Enum等,对于这些类型,Spring容器也提供了@Value注入的形式。
@Value和@Autowired、@Resource相似,也是用来对属性进行注入的,只不过@Value是用来从Properties文件中来获取值的,并且@Value能够解析SpEL(Spring表达式)。
比方,jdbc.properties文件如下:
jdbc.driver=com.mysql.jdbc.Driverjdbc.url=jdbc:mysql://127.0.0.1:3306/test?characterEncoding=UTF-8&allowMultiQueries=truejdbc.username=rootjdbc.password=root
利用注解@Value获取jdbc.url和jdbc.username的值,实现如下:
public class UserServiceImpl implements UserService { //占位符形式 @Value("${jdbc.url}") private String url; //SpEL表达方式,其中代表xml配置文件中的id值configProperties @Value("#{configProperties['jdbc.username']}") private String userName;}
@Qualifier 注解有什么作用
当须要创立多个雷同类型的 bean 并心愿仅应用属性拆卸其中一个 bean 时,能够应用@Qualifier
注解和 @Autowired
通过指定应该拆卸哪个 bean 来打消歧义。
@Bean和@Component有什么区别?
都是应用注解定义 Bean。@Bean 是应用 Java 代码拆卸 Bean,@Component 是主动拆卸 Bean。
@Component 注解用在类上,表明一个类会作为组件类,并告知Spring要为这个类创立bean,每个类对应一个 Bean。
@Bean 注解用在办法上,示意这个办法会返回一个 Bean。@Bean 须要在配置类中应用,即类上须要加上@Configuration注解。
@Componentpublic class Student { private String name = "lkm"; public String getName() { return name; }}@Configurationpublic class WebSocketConfig { @Bean public Student student(){ return new Student(); }}
@Bean 注解更加灵便。当须要将第三方类拆卸到 Spring 容器中,因为没方法源代码上增加@Component注解,只能应用@Bean 注解的形式,当然也能够应用 xml 的形式。
@Component、@Controller、@Repositor和@Service 的区别?
@Component:最一般的组件,能够被注入到spring容器进行治理。
@Controller:将类标记为 Spring Web MVC 控制器。
@Service:将类标记为业务层组件。
@Repository:将类标记为数据拜访组件,即DAO组件。
Spring 事务实现形式有哪些?
事务就是一系列的操作原子执行。Spring事务机制次要包含申明式事务和编程式事务。
- 编程式事务:通过编程的形式治理事务,这种形式带来了很大的灵活性,但很难保护。
- 申明式事务:将事务管理代码从业务办法中分离出来,通过aop进行封装。Spring申明式事务使得咱们无须要去解决取得连贯、敞开连贯、事务提交和回滚等这些操作。应用
@Transactional
注解开启申明式事务。
@Transactional
相干属性如下:
属性 | 类型 | 形容 |
---|---|---|
value | String | 可选的限定描述符,指定应用的事务管理器 |
propagation | enum: Propagation | 可选的事务流传行为设置 |
isolation | enum: Isolation | 可选的事务隔离级别设置 |
readOnly | boolean | 读写或只读事务,默认读写 |
timeout | int (in seconds granularity) | 事务超时工夫设置 |
rollbackFor | Class对象数组,必须继承自Throwable | 导致事务回滚的异样类数组 |
rollbackForClassName | 类名数组,必须继承自Throwable | 导致事务回滚的异样类名字数组 |
noRollbackFor | Class对象数组,必须继承自Throwable | 不会导致事务回滚的异样类数组 |
noRollbackForClassName | 类名数组,必须继承自Throwable | 不会导致事务回滚的异样类名字数组 |
有哪些事务流传行为?
在TransactionDefinition接口中定义了七个事务流传行为:
PROPAGATION_REQUIRED
如果存在一个事务,则反对以后事务。如果没有事务则开启一个新的事务。如果嵌套调用的两个办法都加了事务注解,并且运行在雷同线程中,则这两个办法应用雷同的事务中。如果运行在不同线程中,则会开启新的事务。PROPAGATION_SUPPORTS
如果存在一个事务,反对以后事务。如果没有事务,则非事务的执行。PROPAGATION_MANDATORY
如果曾经存在一个事务,反对以后事务。如果不存在事务,则抛出异样IllegalTransactionStateException
。PROPAGATION_REQUIRES_NEW
总是开启一个新的事务。须要应用JtaTransactionManager作为事务管理器。PROPAGATION_NOT_SUPPORTED
总是非事务地执行,并挂起任何存在的事务。须要应用JtaTransactionManager作为事务管理器。PROPAGATION_NEVER
总是非事务地执行,如果存在一个流动事务,则抛出异样。PROPAGATION_NESTED
如果一个流动的事务存在,则运行在一个嵌套的事务中。如果没有流动事务, 则按PROPAGATION_REQUIRED 属性执行。
PROPAGATION_NESTED 与PROPAGATION_REQUIRES_NEW的区别:
应用PROPAGATION_REQUIRES_NEW
时,内层事务与外层事务是两个独立的事务。一旦内层事务进行了提交后,外层事务不能对其进行回滚。两个事务互不影响。
应用PROPAGATION_NESTED
时,外层事务的回滚能够引起内层事务的回滚。而内层事务的异样并不会导致外层事务的回滚,它是一个真正的嵌套事务。
Spring事务在什么状况下会生效?
1.拜访权限问题
java的拜访权限次要有四种:private、default、protected、public,它们的权限从左到右,顺次变大。
如果事务办法的拜访权限不是定义成public,这样会导致事务生效,因为spring要求被代理办法必须是public
的。
打开源码,能够看到,在AbstractFallbackTransactionAttributeSource
类的computeTransactionAttribute
办法中有个判断,如果指标办法不是public,则返回null,即不反对事务。
protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) { // Don't allow no-public methods as required. if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) { return null; } ...}
2. 办法用final润饰
如果事务办法用final润饰,将会导致事务生效。因为spring事务底层应用了aop,也就是通过jdk动静代理或者cglib,帮咱们生成了代理类,在代理类中实现的事务性能。
但如果某个办法用final润饰了,那么在它的代理类中,就无奈重写该办法,而增加事务性能。
同理,如果某个办法是static的,同样无奈通过动静代理,变成事务办法。
3.对象没有被spring治理
应用spring事务的前提是:对象要被spring治理,须要创立bean实例。如果类没有加@Controller、@Service、@Component、@Repository等注解,即该类没有交给spring去治理,那么它的办法也不会生成事务。
4.表不反对事务
如果MySQL应用的存储引擎是myisam,这样的话是不反对事务的。因为myisam存储引擎不反对事务。
5.办法外部调用
如下代码所示,update办法下面没有加 @Transactional
注解,调用有 @Transactional
注解的 updateOrder 办法,updateOrder 办法上的事务会生效。
因为产生了本身调用,调用该类本人的办法,而没有通过 Spring 的代理类,只有在内部调用事务才会失效。
@Servicepublic class OrderServiceImpl implements OrderService { public void update(Order order) { this.updateOrder(order); } @Transactional public void updateOrder(Order order) { // update order }}
解决办法:
1、再申明一个service,将外部调用改为内部调用
2、应用编程式事务
3、应用AopContext.currentProxy()获取代理对象
@Servciepublic class OrderServiceImpl implements OrderService { public void update(Order order) { ((OrderService)AopContext.currentProxy()).updateOrder(order); } @Transactional public void updateOrder(Order order) { // update order } }
6.未开启事务
如果是spring我的项目,则须要在配置文件中手动配置事务相干参数。如果忘了配置,事务必定是不会失效的。
如果是springboot我的项目,那么不须要手动配置。因为springboot曾经在DataSourceTransactionManagerAutoConfiguration
类中帮咱们开启了事务。
7.吞了异样
有时候事务不会回滚,有可能是在代码中手动catch了异样。因为开发者本人捕捉了异样,又没有手动抛出,把异样吞掉了,这种状况下spring事务不会回滚。
如果想要spring事务可能失常回滚,必须抛出它可能解决的异样。如果没有抛异样,则spring认为程序是失常的。
Spring怎么解决循环依赖的问题?
首先,有两种Bean注入的形式。
结构器注入和属性注入。
对于结构器注入的循环依赖,Spring解决不了,会间接抛出BeanCurrentlylnCreationException
异样。
对于属性注入的循环依赖(单例模式下),是通过三级缓存解决来循环依赖的。
而非单例对象的循环依赖,则无奈解决。
上面剖析单例模式下属性注入的循环依赖是怎么解决的:
首先,Spring单例对象的初始化大略分为三步:
createBeanInstance
:实例化bean,应用构造方法创建对象,为对象分配内存。populateBean
:进行依赖注入。initializeBean
:初始化bean。
Spring为了解决单例的循环依赖问题,应用了三级缓存:
singletonObjects
:实现了初始化的单例对象map,bean name --> bean instance
earlySingletonObjects
:实现实例化未初始化的单例对象map,bean name --> bean instance
singletonFactories
: 单例对象工厂map,bean name --> ObjectFactory,单例对象实例化实现之后会退出singletonFactories。
在调用createBeanInstance进行实例化之后,会调用addSingletonFactory,将单例对象放到singletonFactories中。
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) { Assert.notNull(singletonFactory, "Singleton factory must not be null"); synchronized (this.singletonObjects) { if (!this.singletonObjects.containsKey(beanName)) { this.singletonFactories.put(beanName, singletonFactory); this.earlySingletonObjects.remove(beanName); this.registeredSingletons.add(beanName); } }}
如果A依赖了B的实例对象,同时B也依赖A的实例对象。
- A首先实现了实例化,并且将本人增加到singletonFactories中
- 接着进行依赖注入,发现自己依赖对象B,此时就尝试去get(B)
- 发现B还没有被实例化,对B进行实例化
- 而后B在初始化的时候发现自己依赖了对象A,于是尝试get(A),尝试一级缓存singletonObjects和二级缓存earlySingletonObjects没找到,尝试三级缓存singletonFactories,因为A初始化时将本人增加到了singletonFactories,所以B能够拿到A对象,而后将A从三级缓存中移到二级缓存中
- B拿到A对象后顺利完成了初始化,而后将本人放入到一级缓存singletonObjects中
- 此时返回A中,A此时能拿到B的对象顺利完成本人的初始化
由此看出,属性注入的循环依赖次要是通过将实例化实现的bean增加到singletonFactories来实现的。而应用结构器依赖注入的bean在实例化的时候会进行依赖注入,不会被增加到singletonFactories中。比方A和B都是通过结构器依赖注入,A在调用结构器进行实例化的时候,发现自己依赖B,B没有被实例化,就会对B进行实例化,此时A未实例化实现,不会被增加到singtonFactories。而B依赖于A,B会去三级缓存寻找A对象,发现不存在,于是又会实例化A,A实例化了两次,从而导致抛异样。
总结:1、利用缓存辨认曾经遍历过的节点; 2、利用Java援用,先提前设置对象地址,后欠缺对象。
Spring启动过程
- 读取web.xml文件。
- 创立 ServletContext,为 ioc 容器提供宿主环境。
- 触发容器初始化事件,调用 contextLoaderListener.contextInitialized()办法,在这个办法会初始化一个利用上下文WebApplicationContext,即 Spring 的 ioc 容器。ioc 容器初始化实现之后,会被存储到 ServletContext 中。
- 初始化web.xml中配置的Servlet。如DispatcherServlet,用于匹配、解决每个servlet申请。
Spring 的单例 Bean 是否有并发平安问题?
当多个用户同时申请一个服务时,容器会给每一个申请调配一个线程,这时多个线程会并发执行该申请对应的业务逻辑,如果业务逻辑有对单例状态的批改(体现为此单例的成员属性),则必须思考线程平安问题。
无状态bean和有状态bean
- 有实例变量的bean,能够保留数据,是非线程平安的。
- 没有实例变量的bean,不能保留数据,是线程平安的。
在Spring中无状态的Bean适宜用单例模式,这样能够共享实例进步性能。有状态的Bean在多线程环境下不平安,个别用Prototype
模式或者应用ThreadLocal
解决线程平安问题。
Spring Bean如何保障并发平安?
Spring的Bean默认都是单例的,某些状况下,单例是并发不平安的。
以 Controller
举例,如果咱们在 Controller
中定义了成员变量。当多个申请降临,进入的都是同一个单例的 Controller
对象,并对此成员变量的值进行批改操作,因而会相互影响,会有并发平安的问题。
应该怎么解决呢?
为了让多个HTTP申请之间不相互影响,能够采取以下措施:
1、单例变原型
对 web 我的项目,能够 Controller
类上加注解 @Scope("prototype")
或 @Scope("request")
,对非 web 我的项目,在 Component
类上增加注解 @Scope("prototype")
。
这种形式实现起来非常简单,然而很大水平上增大了 Bean 创立实例化销毁的服务器资源开销。
2、尽量避免应用成员变量
在业务容许的条件下,能够将成员变量替换为办法中的局部变量。这种形式集体认为是最失当的。
3、应用并发平安的类
如果非要在单例Bean中应用成员变量,能够思考应用并发平安的容器,如 ConcurrentHashMap
、ConcurrentHashSet
等等,将咱们的成员变量包装到这些并发平安的容器中进行治理即可。
4、分布式或微服务的并发平安
如果还要进一步思考到微服务或分布式服务的影响,形式3便不适合了。这种状况下能够借助于能够共享某些信息的分布式缓存中间件,如Redis等。这样即可保障同一种服务的不同服务实例都领有同一份共享信息了。
@Async注解的原理
当咱们调用第三方接口或者办法的时候,咱们不须要期待办法返回才去执行其它逻辑,这时如果响应工夫过长,就会极大的影响程序的执行效率。所以这时就须要应用异步办法来并行执行咱们的逻辑。在springboot中能够应用@Async注解实现异步操作。
应用@Async注解实现异步操作的步骤:
1.首先在启动类上增加 @EnableAsync 注解。
@Configuration@EnableAsyncpublic class App { public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(App.class); MyAsync service = ctx.getBean(MyAsync.class); System.out.println(service.getClass()); service.async1(); System.out.println("main thread finish..."); }}
2.在对应的办法上增加@Async注解。
@Componentpublic class MyAsync { @Async public void asyncTest() { try { TimeUnit.SECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("asyncTest..."); }}
运行代码,控制台输入:
main thread finish...asyncTest...
证实asyncTest办法异步执行了。
原理:
咱们在主启动类上贴了一个@EnableAsync注解,能力应用@Async失效。@EnableAsync的作用是通过@import导入了AsyncConfigurationSelector。在AsyncConfigurationSelector的selectImports办法将ProxyAsyncConfiguration定义为Bean注入容器。在ProxyAsyncConfiguration中通过@Bean的形式注入AsyncAnnotationBeanPostProcessor类。
代码如下:
@Import(AsyncConfigurationSelector.class)public @interface EnableAsync {}public class AsyncConfigurationSelector extends AdviceModeImportSelector<EnableAsync> { public String[] selectImports(AdviceMode adviceMode) { switch (adviceMode) { case PROXY: return new String[] { ProxyAsyncConfiguration.class.getName() }; //... } }}public class ProxyAsyncConfiguration extends AbstractAsyncConfiguration { @Bean(name = TaskManagementConfigUtils.ASYNC_ANNOTATION_PROCESSOR_BEAN_NAME) public AsyncAnnotationBeanPostProcessor asyncAdvisor() { //创立postProcessor AsyncAnnotationBeanPostProcessor bpp = new AsyncAnnotationBeanPostProcessor(); //... }}
AsyncAnnotationBeanPostProcessor往往期创立了一个增强器AsyncAnnotationAdvisor。在AsyncAnnotationAdvisor的buildAdvice办法中,创立了AnnotationAsyncExecutionInterceptor。
public class AsyncAnnotationBeanPostProcessor extends AbstractBeanFactoryAwareAdvisingPostProcessor { @Override public void setBeanFactory(BeanFactory beanFactory) { super.setBeanFactory(beanFactory); //创立一个增强器 AsyncAnnotationAdvisor advisor = new AsyncAnnotationAdvisor(this.executor, this.exceptionHandler); //... advisor.setBeanFactory(beanFactory); this.advisor = advisor; }}public class AsyncAnnotationAdvisor extends AbstractPointcutAdvisor implements BeanFactoryAware { public AsyncAnnotationAdvisor( @Nullable Supplier<Executor> executor, @Nullable Supplier<AsyncUncaughtExceptionHandler> exceptionHandler) { //加强办法 this.advice = buildAdvice(executor, exceptionHandler); this.pointcut = buildPointcut(asyncAnnotationTypes); } // 委托给AnnotationAsyncExecutionInterceptor拦截器 protected Advice buildAdvice( @Nullable Supplier<Executor> executor, @Nullable Supplier<AsyncUncaughtExceptionHandler> exceptionHandler) { //拦截器 AnnotationAsyncExecutionInterceptor interceptor = new AnnotationAsyncExecutionInterceptor(null); interceptor.configure(executor, exceptionHandler); return interceptor; }}
AnnotationAsyncExecutionInterceptor继承自AsyncExecutionInterceptor,间接实现了MethodInterceptor。该拦截器的实现的invoke办法把原来办法的调用提交到新的线程池执行,从而实现了办法的异步。
public class AsyncExecutionInterceptor extends AsyncExecutionAspectSupport implements MethodInterceptor, Ordered { public Object invoke(final MethodInvocation invocation) throws Throwable { //... //构建放到AsyncTaskExecutor执行Callable Task Callable<Object> task = () -> { //... }; //提交到新的线程池执行 return doSubmit(task, executor, invocation.getMethod().getReturnType()); }}
由下面剖析能够看到,@Async注解其实是通过代理的形式来实现异步调用的。
那应用@Async有什么要留神的呢?
1.应用@Aysnc的时候最好配置一个线程池Executor以让线程复用节俭资源,或者为SimpleAsyncTaskExecutor设置基于线程池实现的ThreadFactory,在否则会默认应用SimpleAsyncTaskExecutor,该executor会在每次调用时新建一个线程。
2.调用本类的异步办法是不会起作用的。这种形式绕过了代理而间接调用了办法,@Async注解会生效。
最初给大家分享一个Github仓库,下面有大彬整顿的300多本经典的计算机书籍PDF,包含C语言、C++、Java、Python、前端、数据库、操作系统、计算机网络、数据结构和算法、机器学习、编程人生等,能够star一下,下次找书间接在下面搜寻,仓库继续更新中~
Github地址:https://github.com/Tyson0314/...