Spring是一个轻量级的容器框架。两大核心控制反转(IoC)和面向切面编程(AOP)。
Spring5在2017年9月。
优点:
缺点:
Spring的模块,参考
Spring项目的创建,就是先建立一个maven工程,然后加入springframework
org.springframework
spring-context
5.0.2.RELEASE
程序的耦合和解耦
public static void main(String[] args) throws SQLException, ClassNotFoundException {
//注册驱动的两种方式
// 1. 创建驱动类的实例 ,若没有该jar,则编译器就直接编译不过去,提示的是错误error。
//DriverManager.registerDriver(new com.mysql.jdbc.Driver());
// 2. 通过反射加载驱动类,若没有该jar,则会报运行时异常,
Class.forName("com.mysql.jdbc.Driver"); // 实际开发中此类名从properties文件中读取
}
解耦实例2:controller层,Service层,Dao层的调用
private IMyDao myDao = new MyDaoImpl(); // 业务层要调用持久层的接口和实现类
在业务Service层直接调用了持久层的接口和实现类,若没有该实现类,则直接编译过不去。我们希望编译能过去,只是运行时,即使不存在,也只是报错运行时异常。
按照上面的思路:第一个,反射;第二个,配置文件。
解决耦合的思路: 工厂模式解耦
在实际开发中可以把三层的对象的全类名都使用配置文件保存起来,当启动服务器应用加载的时候,创建这些对象的实例并保存在
容器
中。在获取对象时,不使用new的方式,而是直接从容器
中获取,这就是工厂设计模式。下面代码还存在单例和多例的问题,所以解决多例变单例的思路,可以在static类初始化的时候,先创建对象,然后使用map存起来。之后直接根据类名获取即可。
IOC和DI
我们之前说,解耦的思想是反射和全限类名。那么可以看到xml配置文件中,就有一个id和class指定的全限类名。
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
bean>
<bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">bean>
beans>
那么在获取的时候,就可以从spring上下文,也就是一个容器中获取了。
public class Client {
public static void main(String[] args) {
// 获取核心容器对象(spring上下文)
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
// 根据id获取Bean对象
IAccountService as = (IAccountService)ac.getBean("accountService");
}
}
那么这个容器有三种,其最上面的接口都是beanfactory。
classPathXmlApplicationContext
: 它是从类的根路径下加载配置文件
FileSystemXmlApplicationContext
: 它是从磁盘路径上加载配置文件
AnnotationConfigApplicationContext
: 读取注解创建容器
//类的根路径下
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
RegisterDAO registerDAO = (RegisterDAO)ac.getBean("RegisterDAO");
//按照文件的磁盘路径
ApplicationContext appCt2 = new FileSystemXmlApplicationContext("D:/app.spring.xml");
ApplicationContext appCt2 = new FileSystemXmlApplicationContext("classpath:app.spring.xml");
//配合@Configuration使用
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
@Configuration
public class AppConfig {
@Bean(name="entitlement")
public Entitlement entitlement() {
Entitlement ent = new Entitlement();
ent.setName("Entitlement");
ent.setTime(1);
return ent;
}
}
几乎所有的应用场合我们都直接使用ApplicationContext 而非底层的BeanFactory。
由于若是单例的,所以最好还是容器一创建的时候,就一起创建对象比较好。资源匮乏的环境可以使用bean工厂,它也适合多例对象的情况,用的时候就去创建。
【注意】
ssm项目是打包为war,所以在上下文启动的时候,就加载了bean到容器中。若是main 方法时候的话,就需要new ac进行装载bean。而springboot是jar,里面有自带的tomcat,所以有一个启动类。
配置bean 的三种方式:XML、注解、javaConfig方式(@Configuration,@Bean)
//该注解表示这个类为javaConfig类
@Configuration
public class MyConfig {
//该注解表示:向容器中注册一个叫做myCar的对象;名字为方法名car
@Bean
public Car car() {
return new Car("保时捷","911",300);
}
@Bean("myCar")
public Car car() {
return new Car("保时捷","911",300);
}
}
创建bean的三种方式:构造器,静态工厂,实例工厂
1:调用构造器创建Bean
最常见的方式,
2:调用实例(普通)工厂方法创建Bean
场景:用于某个方法的返回值,放入上下文容器中
被创建的bean指定factory-bean和factory-method
场景:用于某个方法的返回值,放入上下文容器中
被创建的bean指定class(class指定静态工厂实现类)和factory-method,
实例(普通)工厂方法:
factory-bean :该属性指定工厂Bean的id
factory-method:该属性指定实例工厂的工厂方法。
静态工厂方法:
class:指定静态工厂的实现类,告诉Spring该Bean实例应该由哪个静态工厂创建(指定工厂地址)
factory-method:指定由静态工厂的哪个静态方法创建该Bean实例(指定由工厂的哪个车间创建Bean)
三者的区别:
通过默认的无参构造方式创建,本质就是把类交给Spring自带的工厂(BeanFactory)管理
通过实例工厂创建,其本质就是把创建实例的工厂类交由Spring管理
通过静态工厂创建,其本质就是把类交给我们自己的静态工厂管理
其实调用实例工厂创建Bean和调用静态工厂创建Bean的区别就在于,调用实例工厂将工厂单独拿了出来(先实例化工厂)创建一个工厂Bean。
人话就是,工厂类也被配置为一个bean了,交给了spring管理
https://blog.csdn.net/ligeforrent/article/details/80744756
https://blog.csdn.net/weixin_40929150/article/details/81262891
DI依赖注入三种方式:setter属性注入,构造器注入,和其他注入(p空间,c空间,工厂方法等)
DI依赖注入都是完成属性的赋值,xml方式里面有3种,注解方式里面也有3种
setter属性注入
<bean id="user" class="test.spring.bean.User">
<property name="name" value="小明"/>
<property name="password" value="123456"/>
bean>
构造器注入:顺序,类型,参数名
<bean id="accountService" class="org.com.qst.service.impl.AccountServiceImpl">
<constructor-arg name="name" value="mys">constructor-arg>
<constructor-arg name="age" value="21">constructor-arg>
<constructor-arg name="time" ref="now">constructor-arg>
bean>
<bean id="now" class="java.util.Date">bean>
其他(工厂方法,p和c命名空间等)
bean的声明周期
https://www.cnblogs.com/javazhiyin/p/10905294.html
bean的作用域参考
主要是单例和多例。其他三个都是做用于web应用,也就是http请求相关的逻辑。
作用域 | 描述 |
---|---|
singleton单例 | 该作用域将 bean 的定义的限制在每一个 Spring IoC 容器中的一个单一实例(默认)。 |
prototype多例 | 该作用域将单一 bean 的定义限制在任意数量的对象实例。 |
request | 该作用域将 bean 的定义限制为 HTTP 请求。只在 web-aware Spring ApplicationContext 的上下文中有效。 |
session | 该作用域将 bean 的定义限制为 HTTP 会话。 只在web-aware Spring ApplicationContext的上下文中有效。 |
global-session | 该作用域将 bean 的定义限制为全局 HTTP 会话。只在 web-aware Spring ApplicationContext 的上下文中有效。集群中,某台服务器创建的session,全集群知道。 |
<bean id="..." class="..." scope="singleton">
bean>
注解方式的装配bean
//反射创建一个对象,并放入上下文容器中,id为类名的首字母小写,在spring中,还需要在xml配置文件中编写开启注解的代码。
@Service
public class HelloServiceImpl implements HelloService {
}
@Autowired 与@Resource
@Qualifier
一起指定使用。https://blog.csdn.net/weixin_38237873/article/details/83650429
https://www.jianshu.com/p/2f1c9fad1d2d
https://blog.csdn.net/weixin_40423597/article/details/80643990
自动装配
public interface AutowireCapableBeanFactory{
//无需自动装配
int AUTOWIRE_NO = 0;
//按名称自动装配bean属性
int AUTOWIRE_BY_NAME = 1;
//按类型自动装配bean属性
int AUTOWIRE_BY_TYPE = 2;
//按构造器自动装配
int AUTOWIRE_CONSTRUCTOR = 3;
//设置全局默认的
}
AOP (Aspect Orient Programming,AOP 是一种编程思想,就面向切面编程。作用就是保证开发者不修改源代码的前提下,去为系统中的业务组件添加某种通用功能。aop
按照 AOP 框架修改源代码的时机,可以将其分为两类:
1、静态 AOP 实现, AOP 框架在编译阶段对程序源代码进行修改,生成了静态的 AOP 代理类(生成的 *.class 文件已经被改掉了,需要使用特定的编译器),比如 AspectJ。
2、动态 AOP 实现, AOP 框架在运行阶段对动态生成代理对象(在内存中以 JDK 动态代理,或 CGlib 动态地生成 AOP 代理类),如 SpringAOP。
https://www.cnblogs.com/yy3b2007com/p/9129612.html
https://www.cnblogs.com/joy99/p/10865391.html
https://www.cnblogs.com/joy99/p/10941543.html
静态代理参考:代理类和被代理的类实现了同样的接口,代理类同时持有被代理类的引用。装饰者模式就是静态代理的一种体现,需要一开始就实现所有的代码并加载好。
public class Secretary implements IWork {
private Leader mLeader;
public Secretary(Leader mLeader) {
this.mLeader = mLeader;
}
@Override
public void meeting() {
System.out.println("秘书先给qsm老板准备材料");
mLeader.metting();
}
@Override
public int evaluate(String name) {
return mLeader.evaluate(name);
}
}
动态代理参考:动态代理的代理类是动态生成的,有一个模板代码,直接去调用。
区别:
JDK代理只能对实现接口的类生成代理;
CGlib是针对类实现代理,对指定的类生成一个子类,并覆盖其中的方法,这种通过继承类的实现方式,不能代理final修饰的类。
性能:
从 jdk6 到 jdk7、jdk8 ,动态代理的性能得到了显著的提升,而 cglib 的表现并未跟上,甚至可能会略微下降。传言的 cglib 比 jdk动态代理高出 10 倍的情况也许是出现在更低版本的 jdk 上吧。
设计:
2个,都运行10万次,100万次的某个方法。
因此SpringAOP是由动态代理来实现的;
也可以在SpringBoot项目中使用AspectJ,但是它是静态代理,在编译的时候会修改被代理的class字节码文件。
在spring中,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式。
InvocationHandler接口 和Proxy类:基于接口的动态代理
Proxy生成代理对象的,InvocationHandler 增强方法,并执行对象的方法,也需要返回值
/**
* Proxy类中的newProxyInstance方法:
* ClassLoader:类加载器;它是用于加载代理对象字节码的。写和被代理对象使用相同的类加载器。
* interfaces:字节码数组;它是让代理对象和被代理对象有相同的方法。即,接口集合。
* InvocationHandler:用于增强逻辑的代码;可以写一个实现它的类,也可以使用匿名内部类。
*/
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
实际的例子
public class Teacher implements Person {
public static void main(String[] args) {
Teacher teacher = new Teacher();
//匿名内部类InvocationHandler
Person proxyInstance = (Person)Proxy.newProxyInstance(teacher.getClass().getClassLoader(), teacher.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("前置通知");
Object result = method.invoke(teacher, args);
System.out.println("后置通知");
return result;
}
});
proxyInstance.tea();
//实现了MyInvocationHandler,并且内部维护一个Object的被代理人,并把Proxy.newProxyInstance的方法也维护了。主要是简化外部生成代理的代码。
MyInvocationHandler myInvocationHandler = new MyInvocationHandler(teacher);
Person proxy = (Person) myInvocationHandler.getProxy();
//实现了MyInvocationHandler,可以不维护被代理人。也就是显示的实现InvocationHandler的invoke方法而已。
Person proxy = (Person) Proxy.newProxyInstance(teacher.getClass().getClassLoader(), teacher.getClass().getInterfaces(), myInvocationHandler);
System.out.println(proxy.tea());
}
@Override
public String tea() {
System.out.println("tea");
return "1";
}
}
public class MyInvocationHandler implements InvocationHandler {
private Object target;
public MyInvocationHandler(Object target) {
this.target = target;
}
public Object getProxy() {
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
/**
* proxy:代理类代理的真实代理对象com.sun.proxy.$Proxy0
* method:我们所要调用某个对象真实的方法的Method对象
* args:指代代理对象方法传递的参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("反射开始");
System.out.println("proxy" + proxy.getClass().getSimpleName());
System.out.println("target" + target.getClass().getSimpleName());
Object result = method.invoke(target, args);
System.out.println("反射结束");
return result;
}
}
MethodInterceptor接口和Enhancer 类:基于类的动态代理
public class Teacher implements Person {
public static void main(String[] args) {
//使用MethodInterceptor匿名内部类
Teacher teacher2 = new Teacher();
Teacher cglibProxyTeacher2 = (Teacher)Enhancer.create(teacher2.getClass(), new MethodInterceptor() {
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("增强逻辑");
Object result = methodProxy.invokeSuper(proxy, args);
System.out.println("收尾逻辑");
return result;
}
});
cglibProxyTeacher2.tea();
//MethodInterceptor的实现类
Teacher teacher3 = new Teacher();
Teacher cglibProxyTeacher3 = (Teacher)Enhancer.create(teacher3.getClass(), new CglibProxyIntercepter());
cglibProxyTeacher3.tea();
}
}
public class CglibProxyIntercepter implements MethodInterceptor {
@Override
public Object intercept(Object sub, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("执行前...");
Object object = methodProxy.invokeSuper(sub, objects);
System.out.println("执行后...");
return object;
}
}
aop利用
https://www.cnblogs.com/kismetv/p/8757260.html
AspectJ的通知:基于静态代理。
可以xml配置,也可以注解。可参考https://www.cnblogs.com/huangzhe1515023110/p/9276055.html
/**
* @author qsm
* log功能
*/
@Component("logginAspectJ")
@Aspect
public class LogginAspectJ {
/*
*定义一个方法,用于声明切点表达式,该方法一般没有方法体
*@Pointcut用来声明切点表达式
*通知直接使用定义的方法名即可引入当前的切点表达式
*/
@Pointcut("execution(* com.linjie.aop.Arithmetic.*(..))")
public void PointcutDeclaration() {
}
//前置通知,方法执行之前执行
@Before("PointcutDeclaration()")
public void BeforeMethod(JoinPoint jp) {
String methodName = jp.getSignature().getName();
Object[] args = jp.getArgs();
System.out.println("BeforeMethod The method "+ methodName +" parameter is "+ Arrays.asList(args));
System.out.println("add before");
System.out.println();
}
//返回通知,方法正常执行完毕之后执行
@AfterReturning(value="PointcutDeclaration()",returning="result")
public void AfterReturningMethod(JoinPoint jp,Object result) {
String methodName = jp.getSignature().getName();
Object[] args = jp.getArgs();
System.out.println("AfterReturningMethod The method "+ methodName +" parameter is "+Arrays.asList(args)+" "+result);
System.out.println();
}
//后置通知,方法执行之后执行(不管是否发生异常)
@After("PointcutDeclaration()")
public void AfterMethod(JoinPoint jp) {
String methodName = jp.getSignature().getName();
Object[] args = jp.getArgs();
System.out.println("AfterMethod The method "+ methodName +" parameter is "+Arrays.asList(args));
System.out.println();
}
//异常通知,在方法抛出异常之后执行
@AfterThrowing(value="PointcutDeclaration()",throwing="e")
public void AfterThrowingMethod(JoinPoint jp,Exception e) {
String methodName = jp.getSignature().getName();
System.out.println("AfterThrowingMethod The method "+ methodName +"exception :"+e);
}
//环绕通知需要携带ProceedingJoinPoint类型的参数
//环绕通知类似于动态代理的全过程,这个类型ProceedingJoinPoint的参数可以决定是否执行目标方法
//且环绕通知必须有返回值,返回值即为目标方法返回值
@Around(value = "execution(* aopImpl.ArithmeticCalculatorImpl.*(int ,int ))")
public void around(ProceedingJoinPoint proceedingJoinPoint){
Object result = null;
String methodName = proceedingJoinPoint.getSignature().getName();
Object[] args = proceedingJoinPoint.getArgs();
//执行目标方法
try {
//前置通知
System.out.println("beforeMethod "+methodName+" start with "+args);
result = proceedingJoinPoint.proceed();
//返回通知
System.out.println("afterMethod "+methodName+" end with "+result);
} catch (Throwable throwable) {
//异常通知
System.out.println("afterThrowing "+methodName+" exception with "+ throwable );
throwable.printStackTrace();
}
//后置通知
System.out.println("afterMethod "+methodName+" end with "+args);
//System.out.println("around "+proceedingJoinPoint);
return;
}
}
ACID,分别是原子性、一致性、隔离性和持久性
声明式事务 xml文件
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource">property>
bean>
<tx:advice id="tx" transaction-manager="dataSourceTransactionManager">
<tx:attributes>
<tx:method name="buyBook"/>
<tx:method name="checkOut"/>
<tx:method name="select*" read-only="true"/>
<tx:method name="insert*"/>
<tx:method name="update*"/>
<tx:method name="delete*"/>
<tx:method name="*"/>
tx:attributes>
tx:advice>
<aop:config>
<aop:pointcut id="pointCut" expression="execution(* com.atguigu.book_xml.service.impl.*.*(..))" />
<aop:advisor advice-ref="tx" pointcut-ref="pointCut"/>
aop:config>
注解事务 @Transactional,也属于声明式事务,利用SpringAOP的动态代理。
@Transactional(rollback =Exception.class)
public void create(String staffId, Integer alloted) {
//执行
this.variationDao.create(staffId, alloted);
//手动制造异常,由于加了事务,上一条语句产生的数据库更新会被回滚
int i = 10 / 0;
}
属性名 | 说明 |
---|---|
name | 当在配置文件中有多个 TransactionManager , 可以用该属性指定选择哪个事务管理器。 |
propagation | 事务的传播行为,默认值为 REQUIRED,一定有事务。查询可以使用SUPPORTS |
isolation | 事务的隔离度,默认值采用 DEFAULT,标识用于数据库的默认隔离级别 |
timeout | 事务的超时时间,默认值为-1。如果超过该时间限制但事务还没有完成,则自动回滚事务。 |
read-only | 指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。 |
rollback-for | 用于指定能够触发事务回滚的异常类型,如果有多个异常类型需要指定,各类型之间可以通过逗号分隔。 |
no-rollback- for | 抛出 no-rollback-for 指定的异常类型,不回滚事务。 |
事务的传播行为有7个:常见的有REQUIRED和REQUIRES_NEW,SUPPORTS
**REQUIRED:**支持当前事务,如果当前没有事务,就新建一个事务。如果有,就加入当前事务。适合绝大多数情况。
SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行。
MANDATORY:支持当前事务,如果当前没有事务,就抛出异常。
REQUIRES_NEW:新建事务,如果当前存在事务,把当前事务挂起。
NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
NESTED:支持当前事务,如果当前事务存在,则执行一个嵌套事务,如果当前没有事务,就新建一个事务。
下面是required
下面是required_new
并发下事务会产生的问题
1、脏读
所谓脏读,就是指事务A读到了事务B还没有提交的数据。
比如银行取钱,事务A开启事务,此时切换到事务B,事务B开启事务-->取走100元,此时切换回事务A,事务A读取的肯定是数据库里面的原始数据,因为事务B取走了100块钱,并没有提交,数据库里面的账务余额肯定还是原始余额,这就是脏读。
2、不可重复读
所谓不可重复读,就是指在一个事务里面读取了两次某个数据,读出来的数据不一致。导致不可重复读出现的原因主要是update操作
还是以银行取钱为例,事务A开启事务-->查出银行卡余额为1000元,此时切换到事务B事务B开启事务-->事务B取走100元-->提交,数据库里面余额变为900元,此时切换回事务A,事务A再查一次查出账户余额为900元,这样对事务A而言,在同一个事务内两次读取账户余额数据不一致,这就是不可重复读。
3、幻读
所谓幻读,就是指在一个事务里面的操作中发现了未被操作的数据。导致幻读问题的原因主要是insert和delete操作。
幻读的例子
比如学生信息,事务A开启事务-->修改所有学生当天签到状况为false,此时切换到事务B,事务B开启事务-->事务B插入了一条学生数据,此时切换回事务A,事务A提交的时候发现了一条自己没有修改过的数据,这就是幻读,就好像发生了幻觉一样。幻读出现的前提是并发的事务中有事务发生了插入、删除操作。
事务的隔离级别
READ_UNCOMMITTED:读未提交,即能够读取到没有被提交的数据,所以很明显这个级别的隔离机制无法解决脏读、不可重复读、幻读中的任何一种,因此很少使用
READ_COMMITED:读已提交,即能够读到那些已经提交的数据,自然能够防止脏读,但是无法限制不可重复读和幻读。
REPEATABLE_READ:可重复读取,即在数据读出来之后加锁,类似"select * from XXX for update",明确数据读取出来就是为了更新用的,所以要加一把锁,防止别人修改它。REPEATABLE_READ的意思也类似,读取了一条数据,这个事务不结束,别的事务就不可以改这条记录,这样就解决了脏读、不可重复读的问题,但是幻读的问题还是无法解决
SERLALIZABLE:可序列化,即串行话。最高的事务隔离级别,不管多少事务,挨个运行完一个事务的所有子事务之后才可以执行另外一个事务里面的所有子事务,这样就解决了脏读、不可重复读和幻读的问题了。
编程式事务 TransactionTemplate
https://www.jianshu.com/p/066c989274c6
正在去BAT的路上修行