目录
概述
什么是spring
侵入式的概念
spring的核心
spring的优势
注意
IOC控制反转
概述
核心
容器
DI,dependency injection依赖注入
概念
注入方式
循环依赖
spring如何解决循环依赖
spring生成Bean的方式
Bean属性注入(Bean属性赋值)
springBean生命周期
实例化Bean
属性赋值(依赖注入)
Aware接口回调
BeanPostProcessor前置处理器
InitializingBean(初始化)
BeanPostProcessor后置处理器
destroy-method销毁
springBean的作用域
springIOC注解
类注解
@Component
@Configuration
@Scope
@Scope具有以下几种作用域
@Scope注意点
属性注解
@Value
@AutoWire
编辑
@Resource
编辑
Resource总结
@Autowire和@Resource区别
方法注解
@Bean
@Scope
@Lazy
@Primary
@Profile
AOP面向切面
底层实现
代理
静态代理
动态代理
JDK 动态代理
Cglib 动态代理
动态代理注意点
AOP名词
Joinpoint(连接点)
Pointcut(切入点)
Advice(通知/增强)
Introduction(引介)
Aspect(切面)
Weaving(织入)
Proxy(代理)
简单的springAOP实现
@Aspect
@Pointcut("xxx")
@Before等
其他通知注解
切点表达式
依赖
实现
一站式轻量级分层框架,非侵入式
spring是非侵入式的框架
侵入式:实现特定接口,继承特定类,改变原有类的结构,即为侵入式,如struts2
非侵入式:对类原本结构没有影响,仍然增强了JavaBean的功能
核心是IOC控制反转和AOP面向切面
IOC,解耦开发
AOP,方便权限拦截,运行监控
声明式事务管理
方便集成其它框架
降低日常开发难度
因为当下项目已经全面普及springboot,前后端分离,因此我们不再关注以前ssm项目中常见的xml配置文件,比如applicationContext.xml,只关注spring本身
在没有spring的情况下,如果需要使用一个类的对象,需要手动的new Xxx(),那么当前类就和Xxx类形成了强耦合
于是有人想到用BeanFactory模式,通过BeanFactory获取对象,这样当前类就和Xxx解耦,比如
private XxDao xxDao = DaoFactory.getInstance().createDao("...dao.impl.XxDaoImpl", XxDao.class);
spring提供了更好的解决方案,即IOC控制反转,将对象的管理权反转给了spring,开发者不再需要手动创建Bean,而是直接从spring容器获取
IOC的核心是spring容器,spring创建对象并负责管理它们完整的生命周期
IOC需要依赖注入(DI)的支持:创建A对象,A会用到其他的类,这时就需要依赖注入支持
ApplicationContext
即时加载,加载applicationContext.xml(容器启动)时候就会创建对象
ClassPathXmlApplicationContext:加载类路径下 Spring 的配置文件.
FileSystemXmlApplicationContext:加载本地磁盘下 Spring 的配置文件
依赖:指一个类使用到了另一个类,A类依赖B类,即A类使用到了B类
依赖注入:将类和类的依赖关系告诉spring容器
构造方法:通过构造方法将另一个类的对象传入当前类
set方法:通过set方法将另一个类的对象传入当前类
注解注入:通过注解获取另一个类的对象
注解方式只能注入spring容器管理的类,因此要配置扫描包,springboot默认会扫描启动类所在包及其子包下的Bean,可以自定义配置
@ComponentScan(value = {"com.xxx.app.service.*", "com.xxx.app.controller.*"})
public class UserServiceImpl implements UserService {
//注解注入
@Autowired
private UserDao userDao;
//使用构造方法实现依赖注入
public UserServiceImpl(UserDao userDao){
this.userDao = userDao;
}
//使用set方法实现依赖注入
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}
定义:A依赖B,B依赖A
构造器的循环依赖:无法处理,直接抛出BeanCurrentlylnCreationException异常
单例模式下的setter循环依赖:通过“三级缓存”处理循环依赖
非单例循环依赖:无法处理
通过无参构造创建Bean
使用BeanFactory:静态和非静态工厂
构造方法:通过构造方法给属性赋值
set方法:通过set方法给属性赋值
spel
对于BeanFactory容器,当客户向容器请求一个尚未初始化的bean时,或初始化bean的时候需要注入另一个尚未初始化的依赖时,容器就会调用createBean进行实例化。
对于ApplicationContext容器,当容器启动结束后,通过获取BeanDefinition对象中的信息,实例化所有的bean
实例化后的对象被封装在BeanWrapper对象中,紧接着,Spring根据BeanDefinition中的信息 以及 通过BeanWrapper提供的设置属性的接口完成属性设置与依赖注入
Spring会检测该对象是否实现了xxxAware接口,如果Bean实现了Aware接口,容器会回调相应的方法,将容器相关的信息注入到Bean中
如果这个Bean实现了BeanNameAware接口,会调用它实现的setBeanName(String beanId)方法,传入Bean的名字
如果这个Bean实现了BeanClassLoaderAware接口,调用setBeanClassLoader()方法,传入ClassLoader对象的实例
如果这个Bean实现了BeanFactoryAware接口,会调用它实现的setBeanFactory()方法,传递的是Spring工厂自身
如果这个Bean实现了ApplicationContextAware接口,会调用setApplicationContext(ApplicationContext)方法,传入Spring上下文
容器会调用所有实现了BeanPostProcessor接口的类的postProcessBeforeInitialization方法,对Bean进行前置处理
如果Bean实现了InitializingBean接口,执行afeterPropertiesSet()方法
或在Bean定义中(配置)指定init-method方法
如果这个Bean实现了BeanPostProcessor接口,将会调用postProcessAfterInitialization(Object obj, String s)方法
由于这个方法是在Bean初始化结束时调用的,所以可以被应用于内存或缓存技术
在这一步后,Bean就被创建完成,可以开始使用这个Bean了
当容器关闭时,会调用Bean的销毁方法
如果Bean实现了DisposableBean这个接口,会调用其实现的destroy()方法
或在Bean定义中指定destroy-method方法
singleton:默认作用域,单例bean,每个容器中只有一个bean的实例
prototype:为每一个bean请求创建一个实例
request:为每一个request请求创建一个实例,在请求完成以后,bean会失效并被垃圾回收器回收
session:与request范围类似,同一个session会话共享一个实例,不同会话使用不同的实例
global-session:全局作用域,所有会话共享一个实例。如果想要声明让所有会话共享的存储变量的话,那么这全局变量需要存储在global-session中
刚刚我们说,IOC是将对象的控制从开发者反转给spring,我们通过spring提供的注解来通知spring管理哪些类,将哪些类以哪种作用域向容器中注入Bean
向spring容器注入当前Bean
@Component("user"),相当于xml配置了
Spring 中提供@Component 的三个衍生注解,功能和@Component一样
@Controller 控制层
@Service 业务层
加在接口的实现类上,不加在接口上
对于有多个实现类的接口,要指定name,自动装配要使用@Resource(name="xxx")
@Repository 持久层
说明:为了区别于类型,通常配置name属性为开头小写
实现类通常配置name=接口名,存在多个实现类则根据区别配置不同name
标记这是一个配置类,一般会配合@Bean,@Scope,@Lazy使用,等价于原本的xml配置文件注入Bean
@Configuration
public class AppConfig {
@Bean
public TransferService transferService() {
return new TransferServiceImpl();
}
}
相当于xml
@Scope作用在类上,表示给定容器中Bean的作用域
@Scope(scopeName="prototype");
singleton 单实例的(单例)(默认),全局有且仅有一个实例
prototype 多实例的(多例),每次获取Bean的时候会有一个新的实例
reqeust 同一次请求,每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP request内有效
session 同一个会话级别,每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP session内有效
1.尽量不要直接使用字符串配置,使用spring提供的参数
ConfigurableBeanFactory.SCOPE_PROTOTYPE
ConfigurableBeanFactory.SCOPE_SINGLETON
WebApplicationContext.SCOPE_REQUEST
WebApplicationContext.SCOPE_SESSION
2.每一个Bean实例,都会有一个initMethod() 和 destroyMethod() 方法,我们可以在Bean 类中自行定义,也可以使用 Spring 默认提供的这两个方法
也可以通过@Bean的属性指定@Bean(initMethod = "initUser", destroyMethod = "destroyUser")
3.思考这么一个场景:当Controller中定义了非静态成员变量,如果不设置成多例,那么多次调用操作同一个变量,会不断的改变变量的值,与我们调用方法的预期不符
用于注入普通类型
@Value(value="zs"),value=可以省略
作用在指定成员的成员变量上或者对应的set()上,区别在于
作用成员变量上,通过反射Field注入
作用在set()上,通过set()注入
可以获取配置文件的数据进行注入,比如.properties
@Autowire默认按照类型(by-type)装配
默认情况下要求依赖对象必须存在,如果找到多个,再按照名称装配
@Autowire
private StudentService studentService;
如果允许依赖对象为null,需设置required属性为false,即
@Autowire(required=false)
private StudentService studentService;
如果使用按照名称(by-name)装配,需结合@Qualifier注解使用,即
@Autowire
@Qualifier("studentService") //这里的参数是bean的name,studentService
private StudentService studentService;
默认按照名称(by-name)装配,名称可以通过name属性指定
当按照名称(by-name)装配未匹配时,按照类型(by-type)装配,此时就是用成员的类型去容器中bean的类型匹配
@Resource(name="studentService") //用这里的参数studentService去容器和bean的name匹配
当显式指定name属性后,只能按照名称(by-name)装配
如果没有指定name
当@Resource注解在成员变量上时,默认取name=成员变量名称装配,这里是字段名称不是字段类型,如下就是用studentService去和容器中bean的name匹配
当@Resource注解在setter方法上时,默认取name=属性名称装配
@Resource
private StudentService studentService;
@Resource
public void setXXX() {...}
如果同时指定name和type属性,则找到唯一匹配的bean装配,未找到则抛异常
如果指定name属性,则按照名称(by-name)装配,未找到则抛异常
如果指定type属性,则按照类型(by-type)装配,未找到或者找到多个则抛异常(一个type可能有多个实现类,所以可能找到多个结果)
既未指定name属性,又未指定type属性,则按照名称(by-name)装配,如果未找到,则按照类型(by-type)装配
1.来源不同:@Autowire是spring提供的注解,@Resource是jdk提供的注解
2.默认的依赖查找顺序不同:
@Autowire默认根据类型查找,如果找到多个,再根据名称查找
@Resource默认根据名称查找,找不到再根据类型查找
3.支持的参数不同
@Autowire仅支持require参数
@Resource支持多个参数,比如name,type
4.支持的注入方式不同
@Autowire支持属性注入,setter注入,构造注入
@Resource不支持构造注入,即这个标签不能打在构造方法上实现注入
方法级别注解,通常使用在@Configuration的配置类中使用
向容器注入一个Bean,id为方法名,类型为方法返回值
@Configuration
public class AppConfig {
@Bean
public TransferService transferService() {
return new TransferServiceImpl();
}
}
相当于xml
@Scope作用在方法上,表示当前Bean作用域
@Lazy作用在方法上,表示当前Bean在注入容器时是懒加载模式
当某个类型的Bean有多个候选者可以注入时,应当优先加载@Primary标记的Bean
当spring.profile.active为对应的值时,才向容器注入当前Bean
切面编程并不是spring开创的,来源是java拦截器思想,但spring给切面编程提供了更好的解决方式
抽取程序执行过程中多个部分功能一致的代码,比如:权限校验,日志记录,性能监控,事务控制
通过代理机制,springAOP会用到两种代理机制,只能选一种不能混用
什么是代理:将原本的调用,比如person.run();封装一层代理,在代理对象中调用person.run();
代码运行前,我们就编写好代理类,编译后生成class文件
缺点十分明显:每一个被代理对象都需要建一个代理类去代理,代码冗杂
代理类是在运行过程中产生的,不需要给每一个被代理类编写单独的代理类
针对接口的实现类产生代理
生成接口的实现类,在实现类中通过method.invoke(...)调用被代理对象的方法
针对没有实现接口的类产生代理
对目标进行继承代理,应用的是字节码增强生成当前类的子类对象
Enhancer类的create()生成代理对象
实现MethodInterceptor接口,重写intercept(...)
methodProxy.invoke(target,objects)调用代理对象方法
如果没有Spring,那么需要我们手动的Proxy.newProxyInstance(xx.xx.xx);
简单来说,就是在目标方法执行时,会通过代理的方式生成一个子类,重写目标方法,在方法体前后执行AOP代码
如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP
如果目标对象实现了接口,可以强制使用CGLIB实现AOP
如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换
Spring AOP的原理是 JDK 动态代理和CGLIB字节码增强技术,前者需要被代理类实现相应接口,也只有接口中的方法可以被JDK动态代理技术所处理;后者实际上是生成一个子类,来覆盖被代理类,那么父类的final方法就不能代理,因为父类的final方法不能被子类所覆盖。一般而言Spring默认优先使用JDK动态代理技术,只有在被代理类没有实现接口时,才会选择使用CGLIB技术来实现AOP,这样就可能造成代理混用的问题,spring提供了一个配置强制使用cglib代理来解决混用问题:
目标对象中所有可以增强的方法都是连接点
所谓连接点是指那些可以被拦截到的点,因为spring只支持方法类型的连接点
目标对象中已经被增强的方法
所谓切入点是指我们要对哪些连接点Joinpoint进行拦截的定义
负责增强的代码
所谓通知是指拦截到Joinpoint之后所要做的事情
通知分为前置通知,后置返回通知,后置通知(最终通知),环绕通知(切面要完成的功能),异常通知
后置返回通知:目标方法出现异常不会执行
后置通知(最终通知):目标出现异常也会执行
引介是一种特殊的通知,在不修改类代码的前提下,Introduction可以在运行期为类动态地添加一些方法或 Field
是切入点和通知(引介)的结合
Target(目标对象)
被代理的目标对象
将通知应用到切点的过程就是织入
是指把增强应用到目标对象来创建新的代理对象的过程,织入完成生成代理对象
通知织入到目标对象以后,形成代理对象
一个类被AOP织入增强后,就产生一个结果代理类
定义切面,表示该类是切面类
这个注解只是声明这是个切面类,并没有放进Spring容器,通常使用@Component将该类交给spring容器
切面类中可以定义切点,通知
定义切点,两种方式
execution 指定方法,@Pointcut("execution(* com.coolway.*(..))")
@annotation 指定注解,@Pointcut("@annotation(com.coolway.annotation.RequiresLogin)")
定义通知,即指定切点处要执行的AOP代码
要给通知注解传入切点
要么使用上面@Pointcut("xxx")定义好的切点
要么重新传入切点,比如@Around("@annotation(com.coolway.annotation.RequiresLogin)")
@AfterReturning
@Around
@AfterThrowing
@After
完整的表达式:execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
modifiers-pattern:方法的可见性修饰符,必须是public,通常省略,void com.coolway.service.impl.StudentServiceImpl.getStudent()
ret-type-pattern:方法的返回值类型,如 int,void 等;通常不对返回值做要求,用*表示任意类型返回值,* com.coolway.service.impl.StudentServiceImpl.getStudent()
declaring-type-pattern:方法所在类的全路径名
类名可以定义为部分匹配,比如以ServiceImpl结尾,* com.coolway.service.impl.*ServiceImpl.*(..)
可以配置成包括子包下符合的,使用..符号,如service下任意子包下的impl包的ServiceImpl类的任意方法(任意参数),* com.coolway.service..impl.*ServiceImpl.*(..)
name-pattern:方法名,如 getOrderDetail();可以用*表示该类的任意方法,* com.coolway.service.impl.StudentServiceImpl.*()
param-pattern:方法的参数类型,如 java.lang.String;..表示任意参数,* com.coolway.service.impl.StudentServiceImpl.*(..)
throws-pattern:方法抛出的异常类型,如 java.lang.Exception;
示例:execution(public void com.coolway.service.impl.StudentServiceImpl.getStudent())
org.springframework.boot
spring-boot-starter-aop
@Aspect
public class AspectDemo {
//定义切点,指定方法
@Pointcut("execution(* com.coolway.*(..))")
public void loginAspectPointcut() {}
//使用定义好的切点
@Before("loginAspectPointcut()")
public void beforeLogin() {
System.out.println("开始访问,查看是否登录");
}
//指定切点为注解
@After("@annotation(com.coolway.annotation.RequiresLogin)")
public void afterLogin() {
System.out.println("结束访问");
}
}