文章目录
Spring 基础
Ioc
有什么好处
-
使用者不需要关注对象的构建过程,我们只要借助ioc提供的方法,然后使用就可以了
-
如果我们的系统有特别复杂的依赖关系,spring也能够帮助我们自动管理
-
基于接口注入,实现类想换哪个就换哪个,系统的耦合度低
设计
主要基于BeanFactory和ApplicationContext两个接口
其中BeanFactory是Ioc最底层的接口,但是ApplicationContext对它做了很多有用的扩展,所以大部分我们会时候后者
BeanFactory
public interface BeanFactory {
String FACTORY_BEAN_PREFIX = "&";
//下面是几个获取bean的方法,可以通过名称和类反射来获取
Object getBean(String name) throws BeansException;
<T> T getBean(String name, Class<T> requiredType) throws BeansException;
Object getBean(String name, Object... args) throws BeansException;
<T> T getBean(Class<T> requiredType) throws BeansException;
<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;
<T> ObjectProvider<T> getBeanProvider(Class<T> requiredType);
<T> ObjectProvider<T> getBeanProvider(ResolvableType requiredType);
boolean containsBean(String name);
//单例模式
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
//与单例模式相反
boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException;
//按type类型匹配
@Nullable
Class<?> getType(String name) throws NoSuchBeanDefinitionException;
@Nullable
Class<?> getType(String name, boolean allowFactoryBeanInit) throws NoSuchBeanDefinitionException;
//获取别名
String[] getAliases(String name);
}
其实spring官方的源码注释还是相当详细的,如果你看到的是一些奇奇怪怪的变量名,比如var1,var2,你试着下载源码,这个时候看到的才是spring的源码。比Mybatis注释详细的多,我一度怀疑自己没有下载mybatis的源码。太详细了,再夸一句spring牛逼
ApplicationContext
翻译一下:应用的上下文
ClassPathXmlApplicationContext其中的一个实现类,负责读取类路径下的xml配置的bean信息
Spring Ioc 初始化过程
先定义再初始化
定义的步骤:(整个过程是没有进行任何初始化的)
- Resource 资源定位,通过开发者的配置或注释,定位到资源也就是我们定义Bean的地方
BeanDefinition,把定位的Bean信息保存到BeanDefinitionBeanDefinition的注册,把里面的信息发布到Ioc容器中
注意,完成这些步骤,Bean只是在容器中被定义了,但是没有完成初始化,更没有什么依赖注入,也就是没有把配置的信息放到Bean里面
Spring Bean 有一个配置选项lazy-init,是否延迟初始化,如果延迟初始化,那么只有我们在Ioc容器里面获取它的时候,才会进行Bean的初始化,完成依赖注入
关于切面的其他收获
Ioc容器的本质就是为了管理Bean,如果我们的bean在初始化或者销毁的时候,需要一些自定义的过程,那么我们就需要用到Bean的生命周期
Idea帮助我们自动填充的代码,比如我们在@Override一个方法的时候,idea会帮助我们调用上层的方法,代码还是比较臃肿的,我们可以看看上层是怎么实现的,只要和上层做一样的实现,处理自己事情就可以了
还是有点难的,不过我也收获了不少
我为了偷懒,想着用切面查看一下这些方法执行前后的参数,所以定义了一个切面类
@Aspect//切面注解
@Component//让spring管理
@Slf4j//日志
public class MethodAspect {
@Around(value = "@annotation(methodLog)")
//首先要注意的就是这个指定,这个是下面方法参数的参数名,所以是小写的
public Object doAround(ProceedingJoinPoint joinPoint, MethodLog methodLog) throws Throwable {
String name = methodLog.value();//通过注释我们获得的属性
long start = System.currentTimeMillis();
//以下的这些方法,是无法获取你给方法参数起的名字是什么
Object aThis = joinPoint.getThis();//得到执行这个方法的对象本身
Object[] args = joinPoint.getArgs();//得到方法传入的参数值
Signature signature = joinPoint.getSignature();//得到方法的签名,类名 方法名 方法参数类型
//我把上面这个打印出来,才真正的理解了签名是什么意思,就是我们可以通过签名唯一的定位一个方法
Object target = joinPoint.getTarget();//返回的结果
log.info("{} : {}",name,target);
// log.info("{} {} {} {}",args,aThis,joinPoint.getTarget(),name);
try {
return joinPoint.proceed();//让真正的方法执行
} finally {
long t = System.currentTimeMillis() - start;//这个可以统计一个方法执行说需要的时间
}
}
//后来我发现,真不如去看源码,如果你看了函数还猜不出方法是什么意思,可以直接看源码的注释,这是spring最方便的一点,没有它,我们想要学会这个框架,难度还是非常离谱的
}
不过这种方法其实是不行的,因为spring boot对bean的拦截太多了,到处都充满了它自己的类,所以我们还是得按照书上的方法,老老实实来,下面我就直接给出一个尽可能详细的注释,来描述bean的整个生命周期
@Component
public class InnerBean implements BeanPostProcessor {
@Override//对象开始示例化
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Override//对象实例化完成
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
Bean的生命周期
-
初始化
-
依赖注入
-
setBeanName
BeanNameAware接口 -
setBeanFactory
BeanFactoryAware接口- 4和5一般是spring自己用的方法,用来获取bean和bean名称
-
setApplicationContext
ApplicationContextAware接口- 需要容器实现
ApplicationContextAware接口,它才会起作用 - 主要是这里有
ApplicationContext作为接口参数
- 需要容器实现
-
postProcessBeforeInitialization
- 针对所有bean而言
-
afterPropertiesSet
InitializingBean接口 -
@PostConstruct
- 自定义初始化方法
-
postProcessAfterInitialization
- 针对所有bean而言
-
生存期。。。。。。。
-
destroy
DisposableBean接口 -
@PreDestory
- 自定义消耗方法
Bean 装配
构造器注入
利用构造器,反射注入,不推荐,因为参数多的话,可读性不好
setter注入
利用无参数构造器,结合set方法构造,推荐
接口注入
用于注入系统外界的配置
spring 提供的3种配置方法
- xml配置(基本废弃)
- java接口或类(代替xml完成复杂的配置)
- 隐私Bean发现机制和自动装配原则(推荐,优先使用)
xml配置
如果我们需要配置的存在不定形式的嵌套,也可能不嵌套,spring用ref标签实现了这样的设计要求
spring在xml配置里,使用过命名空间,有点类似与vue在html标签里扩充的属性,简化了我们编写,但其实可读性才是最重要的,因为是xml唯一的优势,还是一个优势是标签的互相引用,可以介绍大量重叠配置,而且可以自由嵌套
注解装配
组件扫描
定义资源,让spring扫描特点的包
自动装配
通过注解来定义,使得一些依赖关系可以通过注解来完成
@Component
这个注解有属性value,代表它在spring中的id,如果没有指定就是类名的首字母小写
@ComponentScan
在spring boot的自动配置下,这个注释不是很常用,但可以帮助我们扫描,spring boot默认没有扫码到的包,它默认会扫码这个包下的其他的类,同时也可以指定包扫描的位置
如果多次指明要扫码的包,会导致重复创建代理对象,这个是很麻烦的,不要出现这种情况,除非你真的需要,所以在spring boot中,我们选择了@Component
两种扫码机制,如果你类的位置固定不动,那么用包扫描,可读性好,否则用类扫描,适合于那种类位置,包变动的
AnnotationConfigApplicationContext
spring 用这类来实现从注释中获取bean
@Autowired
当spring完成某个Bean的定义和生成,它会寻找被@Autowired注释的资源,然后从自己的Bean中找到对应的,把它注入进去
spring推荐我们用接口注入一个bean,这个bean只会出现接口可以调用的方法,如果想让自己在类中定义的方法,在接口中出现,我们可以重写这个方法@Override然后idea会帮我们把这个方法定义在接口中
这个注释可以加在set方法中,如果加载set方法,可以做一些自定义的代码,一般情况是不需要的
构造方法可以使用@Autowired,不过这个注解要写在参数上面
如果一个接口有多个实现类,@Primary标识一个主要的,如果没有具体指定的话,会使用这个类来注入,比如默认数据源。
@Qualifier,在BeanFactory接口中,有按名称注入的接口,但是spring默认采用按类型注入,这个名称就是@Component中定义的名称,没有指定的话,就得是类名首字母的小写
@Bean
第三方包的类怎么处理,有的第三方包把扩充的余地交给了我们,我们可以定义一个类,继承第三方包指定的类或实现第三方包指定的接口,然后用@Component注解,但大部分情况,我们更希望这些配置在yaml中,除非他需要一些逻辑,写在代码中更加简单一些
但,spring还给我们提供了一种注解,让我们在一个类的方法中引入第三方包,它在方法上注解,然后将返回值作为spring的bean,而可以使用Bean的name属性指定名称,而且可以通过注解指定创建和销毁要执行的方法
xml中也可以定义bean,然后通过@ImportResourse引入
@Import
我在spring boot中经常看到这个注解,这个用来引入多个配置类
Profile
环境隔离,比如在开发环境,测试环境,线上环境使用不同的数据库,但是配置之后,这些bean需要我们通过指定环境参数来激活,负责是不会加入到Ioc容器的
- SpringMVC,可以配置web上下文,或者DispatchServlet参数
- 作为JNDI条目
- 配置环境变量
- 配置JVM启动参数
- @ActiveProfiles
@ActiveProfiles
指定环境的名称,然后注入相应的bean
环境变量
spring.profiles.active 配置后spring.profiles.default失效,应该默认环境是开发环境,在后端上线的时候,通过脚本指定好环境,直接启动,覆盖掉其他人员的默认配置
JVM
JAVA_OPTS="-Dspring.profiles.active=test"
条件化装配和属性配置
我影响中spring boot对这个地方做了升级,属性用yaml,条件也提供的新的注解,我们会写在spring boot的专属笔记中
作用域
@Scope注解,可以指定bean的作用域
singleton 单例
默认作用域
prototype 原型
获取一次装配一个新的实例
session 会话
一次会话过程创建一次
request 请求
一次完整的请求对应一个
Sring EL
spring非常强大的表达式解析器,可以完成属性的复杂装配,比如配置文件的值和我们想要的值有一定的区别,但是为了配置文件的可读性,为了配置文件的简易性,我们可以用 Sring EL
面向切面编程
术语概念
Aspect 切面
指定切面的工作环境,比如controller通配符,自定义注解
Advice 通知
before前置通知,在执行原有方法之前执行
after后置通知,执行完方法后,无论方法是否出现异常都会执行
afterReturning返回通知,无异常时执行,比如事务提交
afterThrowing异常通知,出现异常时执行,比如事务回滚
around环绕通知,可以看成前面几个通知的组合写法(推荐)
Introduction 引入
容许我们扩充现有的类,让它有更多的方法
Pointcut 切点
告诉spring在什么时候切入
join point 连接点
具体要拦截的,比如一个方法,还有方法的签名,参数等
Weaving 织入
生成代理对象,并且将切面内容放入到流程中
静态代理:在编译class文件的时候生成逻辑代码
动态代理:ClassLoader类加载的时候生成的逻辑代码,但是在代码运行前生成,CGLIB,spring动态代理,运行期间生成
注意:spring是方法级别的AOP
AOP的使用
@Aspect
注解在类上,表示这个类定义了切面的细节
指示器
指示器可以指定哪些方法被拦截
execution 正则表达式匹配
@annotation 注解
args(xxx,yyy)可以把方法中名为xxx和yyy的参数直接传递给这个切面方法
ProceedingJoinPoint
我们想了解一个类中的属性代表的值是什么含义,最好的方法是,看文档,看博客,或者用debug去实际的方法中对照一些,这个值最好你可以用到的,可以获取的值,否则是没有什么意义的,一般框架肯定会把有用值的get方法提供出来,所以,不要自通过一个一个打印get方法的值,去猜测了
织入
如果我们类实现了一个接口,那么spring会使用jdk的动态代理,否则spring会使用CGLib的代理,所以我们是否定义了接口,和AOP是没有任何关系的
@DeclareParents
让一个类被动的实现一个接口,然后我们可以在切面中调用这个接口提供的方法,通过强制类型转换就可以
xml配置
由于这个没什么人用,所以不做笔记,但是记录一下,因为xml中使用一下符号,比如&&,||,!,<,>会有一定的问题,所以spring在这里做了转义,比如用and,or,not来替代,这个地方可以我们可以学习一下,而且单词的可读性更好
多个切面
@Order
在spring中,一切涉及到顺序的问题,几乎都可以用这个注解来解决
执行顺序
就是责任链的执行顺序,一层套一层
不得不说,书讲的实在太少了,可以作为入门算是不错的书,至少我能看懂
数据库编程
JDBCTemplate
spring会给常用的技术提供模板化的支持,这也是一种设计模式
技术不常用,不做赘述
MyBatis-Spring
将mybatis和spring结合起来
public class SqlSessionFactoryBean
implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
private static final ResourcePatternResolver RESOURCE_PATTERN_RESOLVER = new PathMatchingResourcePatternResolver();
private static final MetadataReaderFactory METADATA_READER_FACTORY = new CachingMetadataReaderFactory();
private Resource configLocation;//MyBatis的配置文件
private Configuration configuration;//mybatis的全局配置对象
private Resource[] mapperLocations;//mapper配置路径
private DataSource dataSource;//数据库
private TransactionFactory transactionFactory;//事物管理器
private Properties configurationProperties;//配置属性
private SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();//四大对象
private SqlSessionFactory sqlSessionFactory;//sqlSession的工厂,四大对象
private String environment = SqlSessionFactoryBean.class.getSimpleName();
private boolean failFast;//是否快速失败
private Interceptor[] plugins;//插件
private TypeHandler<?>[] typeHandlers;//类型转换器
private String typeHandlersPackage;//类型装换器在的包
@SuppressWarnings("rawtypes")
private Class<? extends TypeHandler> defaultEnumTypeHandler;//默认枚举处理器
private Class<?>[] typeAliases;//别名处理器
private String typeAliasesPackage;//别名处理器所在的包
private Class<?> typeAliasesSuperType;
private LanguageDriver[] scriptingLanguageDrivers;//数据库厂商标识
private Class<? extends LanguageDriver> defaultScriptingLanguageDriver;
private DatabaseIdProvider databaseIdProvider;
private Class<? extends VFS> vfs;//unix文件操作
private Cache cache;//缓存
private ObjectFactory objectFactory;//对象工厂
private ObjectWrapperFactory objectWrapperFactory;
}
MapperFactoryBean
怎么把mybatis对接口的动态代理和spring的动态代理结合起来,就是这个类起的作用
书中讲的特别浅,在spring boot的时代下,已经过时了,不过作为一步入门的书,我觉得还是非常好的,至少我非常有兴趣能看下去,而且学到了不少东西
事务管理
这块我一直没有遇到复杂的应用场景,所以打算好好学习一下
PlatformTransactionManager
public interface PlatformTransactionManager extends TransactionManager {
//创建事务
TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;
//提交事务
void commit(TransactionStatus status) throws TransactionException;
//回滚事务
void rollback(TransactionStatus status) throws TransactionException;
}
DataSourceTransactionManager
@Transactional 声明式事务
| 配置项 | 备注 |
|---|---|
| isolation | 隔离级别,非常重要,后面会详细说 |
| value/transactionManager | 一个需要实现PlatformTransactionManager接口的Bean |
| propagation | 传播行为,设计到事务方法之间的相互调用 |
| timeout | 超时时间 |
| readOnly | 只读事务 |
| (no)rollback | (不)会回滚的事务类型 |
| (no)rollbackForClassName | (不)同上,但用的是字符串 |
这个注解让我收获到一点,注解是可以直接配置枚举类的
原子性 (原子不可分割)
整个事务的所有操作,要么全部成功,要是有一个失败,其他所有的都会变回原来未执行事务的状态
一致性
读写一致
隔离性
事务的隔离程度,多个事务
持久性
事务完成后,该事务对数据库所做的更改会被保存
丢失更新
- 两个事务同时开启,其中一个事务发起回滚(mysql基本不会出现这种情况)
- 两个事务同时操作同一个数据和java并发修改同一个数据的影响一样
隔离级别
事务隔离级别,多个事务同时操作一个数据的时候涉及
看别人的博客,写的比书上的好MySQL相关问题
如何选择隔离级别
依据:看性能和数据的一致性哪个对你的这个业务更重要
要求数据库高度精确不能出错而且性能要求还特别高,比如秒杀系统,是不会通过隔离级别来控制数据库的一致性的
此外,在大部分环境中,还选择读/写提交的隔离级别,这种有助于提高并发,而且压制了脏读
如果我们业务的并发特别小,或者是那种对性能要求不是很高,但是要保证数据一致性的,我们可以才用序列化的方法,保证数据库不出错
如果我们不指定@Transactional的隔离级别,他会采用默认的隔离级别,数据库默认的隔离级别,也就是说,对于MySQL是可重复读但是对Oracle这种只支持读/写提交和序列化两种的,支持的是读/写提交
传播行为
方法之间的调用事务策略的问题
| 传播行为 | 含义 |
|---|---|
| REQUIRED | 默认传播行为,如果当前不存在事务,就开新事务,否则,就用之前的事务 |
| REQUIRES_NEW | 无论什么情况都会新开启一个事务 |
| NESTED | 和REQUIRES_NEW类似,但是可以回滚到指定的保存点 |
事务失效
因为@Transactional的底层实现是AOP,所以static和非public方法是无法使用的,static一般我们是不会用的,但是我们有注意,如果我们调用的是私有方法,那么就得小心了,调用私有方法肯定是同层之间调用,注意不要在私有方法上用@Transactional。另外,这也告诉我们,AOP是不支持这些行为的,而且最关键的是自调用的问题,就是一个类的函数互相调用,基于AOP的实现原理,同一个类的方法直接调用,是不会出现代理类的,所以会出现事务失效,所以,AOP只支持,不同类的公共的非静态方法
那怎么解决,其实很好解决,我们有为业务专属的service,一般而言,业务专属service操作好几个数据库,但是单个service只操作和自己精密相关的数据库,和DDD比较类似,但是我这种可以直接操作数据库的修改,而不是通过对象来修改,所以业务层和普通service是一个层级的,我们可以把mapper注入到业务service,BDD的核心就在于,能让对象来操作的,就让对象来操作
我们可以通过查看mybatis的日志,Creating a new SqlSession来看事务的开启情况是什么样的
此外,如果我们想让两个在一个事务中,就不能在controller层写方法了,而且我现在觉得是真的不好,而且没什么用
事务的粒度也是需要我们把握的,因为事务和锁一样,是对影响速度影响比较大的,所以我们应该想办法缩小事务和锁的粒度,你要记住,io和数据库是非常可怕的,你要尽可能的减少对他们的使用,要多用内存,redis,怎么解决,分开方法就行,小问题。而且你要注意的是,分隔的类,只要是不同类之间的互相调用,就可以避免事务出现问题,并不是说,需要你加多少多少层
异常处理,你捕获的异常,是你要真正能处理了的,如果你不行,那么你必须处理后继续抛出,在spring中,如果你自己处理了异常,但是不抛出,就会出现spring不会帮助你回滚事务的情况
5万+

被折叠的 条评论
为什么被折叠?



