Spring概述
是什么
它是一个分层的,轻量级的,一站式的 Java开发框架。为Java应用开发提供基础框架的支持。可以让开发者更专注与业务开发。最根本的使命是解决企业级应用开发的复杂性,即简化Java开发。
Spring****框架的核心
IoC容器和AOP模块。通过IoC容器管理POJO对象以及他们之间的耦合关系;通过AOP以动态非侵入的方式增强服务。
IoC让相互协作的组件保持松散的耦合,而AOP编程允许你把遍布于应用各层的功能分离出来形成可重用的功能组件。
优点
1. 简化开发,组件及其依赖关系由容器管理,方便解耦
2. 支持面向切面编程,可以将通用任务(事务,权限,日期等)抽离成重要组件,集中管理。
3. 对于主流的应用框架提供了集成支持
4. 简化javaee api使用,提供了对jdbc, mail 等api的封装
缺点
-
依赖反射,影响性能
-
使用门槛高
装配bean
Spring配置的可选方案
-
在xml中显式配置
-
Java中进行显式配置
优点:类型安全,强大,重构友好
使用@Configuration标注类,使用@Bean标注方法,@Bean注解的方法可以采用任何必要的java功能产生bean
- 隐式的bean发现机制和bean装配机制
自动化装配bean
Spring从两个角度实现自动化装配
组件扫描
spring会自动发现应用上下文中创建的bean。
-
使用注解@Component,@Service,@Bean等标注类
-
使用@ComponentScan开启组件扫描,不指定包名时,默认以配置所在包为基础包进行扫描。可指定包名,指定类名(类名所在包名,可使用空接口标注类所在包)。
@ComponentScan可以指定过滤器数组,决定扫描那些类过滤掉哪些类,过滤器可指定多种类型,甚至自定义过滤类。非常灵活。
@ComponentScans可以包含多个@ComponentScan注解。
自动装配
--spring会自动满足bean之间的依赖关系。
使用Autowired,该注解可用在属性上,方法上。
作用在方法上时,spring会尝试满足方法参数上声明的依赖,
如果没有匹配的bean被装配,会抛出异常,可以使用该注解的required为false避免抛出异常,但此时对于未装配的bean可能出现空指针异常。
存在多个bean满足依赖关系,也会抛出异常。
@Import(类名或类名数组)会自动导入类到IOC容器中,组件名就是类的全类名。
ImportSelector接口实现类可指定导入的具体类有哪些,将实现类使用在
@Import注解中,可将实现类中指定要导入的类导入到IOC容器。
高级装配
环境与profile
- 指定环境profile
JavaConfig中可以使用@Profile注解表明在什么环境下装配。可作用域类级别,方法级别(与@Bean同时同)。
Xml中可通过beans的profile属性指定装配环境。
- 激活profile
Spring.profiles.active表示要激活的环境,Spring.profiles.default表示默认激活的环境,可同时设置多个,用逗号分开。都不设置时,使用该注解标注的bean都不会被激活。
可通过以下几种方式设置参数
-
Springboot配置文件,通过jar包启动时可以在启动命令中添加Spring.profiles.active=xxx
-
前端控制器参数
-
Web应用上下文参数
-
作为JNDI条目
-
作为环境变量
-
作为jvm系统属性
-
在集成测试类上使用@ActiveProfiles注解设置
条件化装配
使用@Conditional用于类上或方法上,传入条件类数组。注解中传入自定义的条件类,该类实现了Condition接口即可。此接口方法中可通过参数ConditionContext获取很多信息。如下:
BeanDefinitionRegistry getRegistry();// bean定义注册类 ConfigurableListableBeanFactory getBeanFactory();// ioc 容器bean工厂 Environment getEnvironment();// 环境变量 ResourceLoader getResourceLoader(); //资源加载器 ClassLoader getClassLoader(); //类加载器
其中常用getEnvironment()获取环境信息,配置文件等,用于条件判断。
有时,配置文件加载顺序问题,可能导致条件判断失败,可使用@ PropertySource注解作用在需要条件化装配的bean上,提前加载配置文件到环境中。
Profile基于@Conditional实现。
处理自动装配歧义性
@Primary
如果一个接口存在多个实现。使用@Autowired注入时无法确定注入哪个。下需要在实现类中使用@Primary标记为首选类。由于该注解可能存在多个,仍可能存在歧义。
@Qualifier
-
@Qualifier(“xxx”)与@Component或@Bean一起使用,给bean指定限定名为xxx,Qualifier不传参数时限定名默认使用类名首字母小写。
-
使用@Qualifier(“xxx”)与@Autowired一同使用指定装配哪个类。此时可以使用多个@Qualifier注解进行唯一限定。
-
自定义注解上使用@Qualifier,此时自定义注解与@Component或@Bean一起使用时,相当于为Qualifier指定了限定名,限定名即为自定义注解的类名首字母小写。此时在与@Autowired一起使用自定义限定注解,即可,且便于重构。
Bean作用域
-
单例singleton,整个应用只创建一个,ioc容器创建时创建,使用@Lasy可以懒加载,即在bean第一次被获取时创建
-
原型prototype,每次注入或通过spring应用上下文获取时,会创建一个新的bean实例
-
会话session,在web应用中,为每个会话创建一个bean实例
-
请求request,在web应用中,为每个请求创建一个实例
默认单例,可通过@Scope与@Component或@Bean一起使用,指定作用域。
使用会话与请求作用域
这两种作用域bean在应用初始化时并不存在,注入到单例bean中存在问题,如何让解决?
通过生成代理类的方式,当代理类方法被调用时,代理类会对其进行懒解析并委托给作用域内真正的bean实例处理。
代理类如何让找到作用域内的对象的呢?
通过线程和应用上下文
@Scope的属性proxyMode,有四个值, DEFAULT,NO, INTERFACES,TARGET_CLASS;
会话作用域的bean是接口时,使用proxyMode=INTERFACES,是类时,必须使用cglib生成基于类的代理,此时使用proxyMode= TARGET_CLASS。请求作用域同会话作用域的bean处理方式。
Aop-面向切面编程
术语
通知
--何时执行什么内容
前置通知 目标方法执行前调用通知
后置通知 目标方法执行后调用通知,此时不关心方法输出的什么。
返回通知 目标方法执行完成之后调用通知
异常通知 目标方法执行抛出异常后调用通知
环绕通知 目标方法执行前和后调用通知
后置与返回的区别?
连接点
--通知的执行位置
应用在执行过程中能够插入切面的一个点,可以是调用方法时,异常抛出时,修改字段时等等,切面代码可以利用这些带点插入应用的正常流程中并添加新的行为。
修改字段时如何感知?
通知的类型决定连接点?
切点
连接点的集合 --何处执行的集合
切点的定义会匹配通知所要织入的一个或多个连接点。通常使用明确的类或方法名称,或利用正则匹配类名和方法名来确定这些切点。有些Aop框架允许动态的创建切点,比如通过方法传参决定是否应用通知。
切面
切点和通知的结合。--何处何时执行什么
引入
--允许我们向现有类添加新的属性和方法。
织入
--把切面运用到目标对象并生成代理对象的过程。切面在指定的连接点织入到目标对象中。
目标对象的生命周期中,有多个点可以织入:
-
编译期,需要特殊的编译器,Aspectj 就是这种方式织入。
-
类加载期,需要特殊的类加载器,在目标类被引入应用之前,增强目标类的字节码,Aspectj5加载时织入就支持这种方式。
-
运行期,切面在应用运行的某个时刻织入,一般情况下,织入切面时,Aop容器会动态的为目标对象生成代理对象。Spring Aop就是采用这种方式。
如果使用切面的需求超过了简单的方法调用(如构造方法拦截,属性拦截等)需要考虑使用Aspctj 来实现切面。
定义切面
-
使用@EnableAspectJAutoProxy开启切面
-
使用@Aspect标记切面类
-
使用@Pointcut定义切点集合,在@Pointcut的值中定义切点表达式,
-
在@Before,@After中使用@Pointcut标注的方法即可。
参数传递
[图片上传失败...(image-96cc09-1592105529525)]
Todo 如何获取目标方法的参数并处理然后赋值给目标方法并执行目标方法?
Spring AOP中的动态代理
Spring-Aop主要是通过生成代理类的方式,对目标方法进行增强,生成代理类的两种方式。
Jdk动态代理
实现原理:https://blog.csdn.net/yhl_jxy/article/details/80586785
利用拦截器(拦截器必须实现InvocationHanlder)加上反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
invoke方法根据传入的代理对象,方法和参数来决定调用代理的哪个方法。
方法签名如下:
invoke(Object Proxy,Method method,Object[] args)
spring默认使用jdk动态代理,但是要求目标类必须实现了某个接口。
Cglib
是一个代码生成包, 底层基于asm(生成字节码用的)框架来转换字节码生成新的类。
相比 jdk动态代理,生成代理类速度较慢,因为需要操作字节码。但执行速度较快。
代理类实现 MethodInterceptor 接口,最终通过该接口拦截目标方法,在其中进行增强逻辑处理,并通过 代理类对象调用invokeSuper执行目标方法。
Cglib提供了方法过滤器(CallbackFilter),可以指定具体拦截哪些方法:
自定义过滤器
package com.tech.cglibx;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.CallbackFilter;
public class MyProxyFilter implements CallbackFilter {
@Override
public int accept(Method arg0) {
if(!"query".equalsIgnoreCase(arg0.getName()))
return 0;
return 1;
}
}
通过以下方法获取代理类
- public static BookServiceBean getAuthInstanceByFilter(MyCglibProxy myProxy){
- Enhancer en = new Enhancer();
- en.setSuperclass(BookServiceBean.class);
- en.setCallbacks(new Callback[]{myProxy,NoOp.INSTANCE});
- en.setCallbackFilter(new MyProxyFilter());
- return (BookServiceBean)en.create();
- }
JDK动态代理和CGLIB字节码生成的区别?
-
JDK动态代理只能对实现了接口的类生成代理,而不能针对类
-
CGLIB是针对类实现代理,主要是对指定的类生成一个子类,对目标方法进行拦截,并在目标方法执行前后进行增强。 因为是继承,所以该类或方法不要声明成final
-
Spring默认使用jdk动态代理,如果类为实现接口,会转换为cglib代理,也可使用配置强制使用cglib代理。
网友评论