SpringIoC
控制反转是一种通过描述(XML或者注解)让第三方去产生或获取特定对象的方式.
在Spring中实现控制反转的是IoC容器,其实现方式是依赖注入(DI).
Bean对象的初始化
通过Resource进行资源定位(XML和注解),将定位到的信息存入BeanDefinition中,将BeanDefinition的信息放入Spring IoC容器中,至此,就完成了Bean在IoC容器中的定义.定义后根据lazy-init的值来决定是否按需加载初始化(多例不生效),如果为false,则自动初始化Bean,如果为true则在调用IoC容器的getBean方法时,才会进行初始化,完成依赖注入.
BeanFactory是IoC容器的根接口,ApplicationContext是BeanFactory的子接口,ApplicationContext有两个常用的实现类,分别是ClassPathXmlApplicationContext(通过XML文件获取bean对象)和AnnotationConfigApplicationContext(通过注解或者代码获取bean对象)IoC.容器中有两个Map对象,一个是Bean池(存放单例bean对象,多例不放入),一个是定义池(存放bean对象的定义信息).
Bean对象的创建
Bean对象如果没有实现FactoryBean(一个工厂Bean),则直接使用构造方法创建.
Bean对象如果实现了FactoryBean,则调用getObject方法创建对象.
依赖注入的三种方式:
构造器注入(适合参数较少的,底层反射),对应标签<constructor-arg>
Setter注入(主流,灵活,底层反射),对应标签<property>
接口注入.(从外部获取的)
Spring Bean的生命周期
要自定义初始化或销毁方法只需要实现对应的生命周期接口,并覆盖对应方法即可.
装配Spring Bean
配置Bean的方式有三种:
- 在XML中显式配置(包括命名空间定义).
- 在Java的接口和类中实现配置.
- 隐式Bean的发现机制和自动装配.
根据约定大于配置的原则,优先选择第三种隐式发现机制和自动装配.
当没有办法使用自动装配机制时,应当使用第二种方式,避免XML配置文件的泛滥.
开发中会使用以注解为主(简易,方便),以XML为辅(方便管理,第三方)的方式.
Spring提供了两种方式来让IoC容器发现Bean:组件扫描,自动装配(使用注解)
用@Primary(优先使用)和@Qualifier(按名称查找)可以消除自动装配的歧义性(@Autowired默认是按照类型匹配的).
JDK动态代理
JDK动态代理要求实现InvocationHandler接口并重写invoke方法
public Object invoke(Object proxy, Method method, Object[] args) {}
proxy代表代理对象,method代表需要调度的方法,args代表被调度方法的参数
通过Proxy类的newProxyInstance静态方法可以获得生成的代理对象
Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h);
loader代表被代理对象(真实对象)的类加载器
interfaces代表真实对象实现的那些接口
h代表实现了InvocationHandler接口的实现类实例.
该方法首先会做一些安全检查,然后使用getProxyClass0(loader, intfs)方法来生成一个修改过的新类,然后反射调用新类的构造方法来创建代理对象.
getProxyClass0(loader, intfs)方法会返回proxyClassCache.get(loader, interfaces),如果缓存中有代理对象,则直接返回,如果没有则通过Proxy类中的内部类ProxyClassFactory来生成新类.
通过观察,可以发现该类继承了Proxy类并且实现了真实对象的父接口,该类的构造方法传入的参数是InvocationHandler接口的实现类实例(也就是上面所说的h).该类的成员是Method类型,这几个方法类型的成员对应的就是真实对象实现的所有接口中的方法.
在该类中的方法中会调用InvocationHandler接口实例中的invoke(Object proxy, Method method, Object[] args).
传入的第一个参数是this,对应代理对象,第二个参数是对应的方法类型成员,第三个就是对应的参数).
而在InvocationHandler接口中的invoke方法中则可以通过Method类型的invoke方法反射调用真实对象的对应方法.
由此可知,JDK动态代理通过提供InvocationHandler接口的invoke方法,让开发者在invoke方法中写入自己的代理逻辑即可,而底层的动态代理交由JDK来实现,也知道了JDK动态代理为什么必须提供接口,而不能是类(因为代理对象要继承Proxy类中的方法).
AOP
常用术语:切面(类似于拦截器),通知(before,after,afterReturning,afterThrowing,around),引入(允许在现有的类里添加自定义的类和方法),切点(告诉AOP在何处拦截并织入,一般用正则表达式表示,其实就是拦截方法的规则),连接点(具备需要被拦截的东西,也就是被拦截的方法),织入(生成代理对象并将切面内容放入流程中的过程,有接口使用JDK动态代理,没接口使用CGLIB)
AOP的本质其实就是AOP横切思想+拦截器+动态代理+责任链模式,拦截器通过切点规则拦截指定方法,通过动态代理技术生成代理对象,并将切面中定义的通知织入到执行流程中,如果有多个切面,则采用责任链模式层层拦截.
Spring的AOP只能支持到方法级别的拦截,也就是说Spring是方法级别的AOP框架.
实现AOP拦截的方法
- 使用ProxyFactoryBean和对应的接口实现AOP.
- 使用XML配置AOP(辅助)
- 使用@AspectJ注解驱动切面(主流)
- 使用AspectJ注入切面.
Spring中的AOP相关注解
@EnableAspectJAutoProxy启用AspectJ框架的自动代理,Spring才会生成动态代理对象,进而使用AOP功能
@Aspect定义切面
@Pointcut定义切点方法后,通知注解中只需要使用对应的切点签名即可,如@Before("print()")
@Order(1)定义多切面环境下,切面的执行顺序.数字越小,优先级越高. 还可以通过让切面实现Ordered接口的getOrder方法或者XML配置来实现.
@Before前置通知
@Around环绕通知,将覆盖原有方法,允许通过反射调用原有方法(ProceedingJoinPoint的proceed方法)
@After后置通知
@AfterReturning返回通知
@AfterThrowing异常通知
定义切点表达式
execution()用于匹配连接点的执行方法.粒度级别:方法
@annotation()用于匹配指定的注解.粒度级别:方法
within()限制连接点匹配指定的包.粒度级别:类
bean()用于匹配指定bean id的类.粒度级别:类
以上表达式可以使用逻辑运算符进行运算.
给通知传递参数:在切点表达式后加&&args(参数名称1,参数名称2)即可.如果是XML方式,则将$$换为and.
AOP引入
@DeclareParents(value="com.ssm.service.impl.被增强类+" defaultImpl=实现类.class),原理是让代理对象实现对应接口.
Spring事务管理
- @Transactional的配置项:
- value定义事务管理器.
- transactionManager同上.
- isolation隔离级别,默认Isolation.DEFAULT取数据库默认隔离级别.isolation=Isolation.READ_UNCOMMITTED读取未提交.iIsolation.READ_COMMITTED提交已读取.iIsolation.REPEATABLE_READ可重复读.iIsolation.SERIALIZABLE串行化/序列化
- propagation传播行为,默认为REQUIRED
- timeout超时时间,单位为秒.
- readOnly是否开启只读事务,默认为false.
- rollbackFor只有该异常发生才回滚.
- rollbackForClassName同上,只不过是用类名来定义.
- noRollbackFor该异常发生时不回滚事务.
- noRollbackForClassName同上,只不过是用类名来定义.
传播行为
- REQUIRED方法调用时,如果不存在当前事务,那么就创建.如果已存在,就沿用之前的事务.
- SUPPORTS当方法调用时,如果不存在当前事务,那么不启用事务;如果存在当前事务,那么就沿用当前事务.
- MANDATORY方法必须在事务内运行.
- REQUIRES_NEW无论是否存在当前事务,方法都会在新的事务中运行.
- NOT_SUPPORTED不支持事务,如果不存在当前事务,什么也不做,如果存在当前事务,则挂起它,直至方法结束后才恢复.
- NEVER不支持事务,只有在没有事务的环境中才能运行它.
- NESTED嵌套事务,调用方法如果抛出异常只回滚自己内部执行的SQL,而不回滚主方法的SQL.如果数据库不支持保存点,则退回到REQUIRES_NEW级别.
@Transactional对于静态方法和非public方法是失效的(因为底层是动态代理).
@Transactional对于自调用(一个类中方法调用,不会产生代理对象)也会失效,解决方法:使用两个服务类或者从容器中获取bean.
常见错误使用:
控制层中使用service对象调用多个方法.这些方法会产生各自的事务,不在一个事务中,导致数据不一致.
长时间占用事务会导致系统性能的低下,要避免在事务中做业务无关操作和耗时操作.
Spring常用注解
@Component
@Service
@Repository
@Controller
@Autowired
@Qualifier
@Resource
@PropertySource
@Value
@Configuration
@Bean
@ComponentScan
@ComponentScans
@Import
@Scope
@EnableAsync
@Async
@Lazy