Spring框架学习笔记
Spring概念
Sping是分层的Java SE/EE 应用 Full-stack 轻量级开源框架,以Ioc(Inverse Of Control:反转控制)和AOP(Aspect Oriented Programming:面向切面编程)为内核,提供了展现层SpringMVC和持久层Spring JDBC以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多著名的第三方框架和类库,逐渐成为使用最多的Java EE企业应用开源框架。
Spring体系结构
核心容器(Core Container),数据访问/集成(Data Access/Integration)层,Web层,AOP(Aspect Oriented Programming)模块,植入(Instrumentation)模块,消息传输(Messaging),测试(Test)模块。
IOC概念与背景
程序的耦合
耦合:程序间的依赖关系,包括类之间的依赖与方法间的依赖
解耦:
1.使用反射来创建对象,而避免使用new关键字
2.通过读取配置文件来获取要创建的对象全限定类名
普通程序中的耦合举例:(耦合现象严重)
持久层dao接口:提供保存账户方法
持久层dao实现类:实现保存账户的方法
业务层service接口:提供保存账户方法
业务层service实现类:创建持久层实现类的对象,由该对象实现保存账户的方法
模拟表现层client:在main函数中创建业务层实现类的对象,由该对象调用业务层实现类中的保存账户方法
解耦的思想
思想概述:
把三层(dao,service,client)的对象都使用配置文件配置起来。
当启动服务器应用加载的时候,让一个工厂类中的方法通过读取配置文件,把这些对象创建出来并保存到一个map容器中。
在接下来的使用的时候,直接使用创建好的对象即可。
创建Bean对象的工厂:
baen在计算机英语中,是可重用组件的含义
javabean是用java语言编写的可重用组件。
正式介绍Ioc
全称为:inverse of control
是Spring框架的重要特征。把创建对象的权力交给框架。常见的方式有:依赖注入,依赖查找。
意义:削减耦合。
基于xml配置的ioc程序实现
步骤1:新建一个maven工程
步骤2:在弹出来的pom.xml文件中写packaging标签,并导入springframework依赖
1 <?xml version="1.0" encoding="UTF-8"?> 2 <project xmlns="http://maven.apache.org/POM/4.0.0" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 5 <modelVersion>4.0.0</modelVersion> 6 <groupId>org.example</groupId> 7 <artifactId>springIoc</artifactId> 8 <version>1.0-SNAPSHOT</version> 9 <packaging>jar</packaging> 10 <dependencies> 11 <dependency> 12 <groupId>org.springframework</groupId> 13 <artifactId>spring-context</artifactId> 14 <version>5.0.2.RELEASE</version> 15 </dependency> 16 </dependencies> 17 </project>
步骤3:在main/resources包下创建一个bean.xml配置文件,下面开始配置:
打开Spring Framework Documentation网页,点core,按ctrl+f,搜索xmlns,复制配置头到配置文件中,并补全标签尾部。在其中写两个bean,分别包含service和dao的id与class。
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xsi:schemaLocation="http://www.springframework.org/schema/beans 5 http://www.springframework.org/schema/beans/spring-beans.xsd"> 6 <bean id="accountService" class="service.impl.AccountServiceImpl"></bean> 7 <bean id="accountDao" class="dao.impl.AccountDaoImpl"></bean> 8 </beans>
步骤4:分别写好dao层接口,实现类,service层接口,实现类。
dao层接口:定义一个saveAccount()方法即可。
1 package dao; 2 3 public interface IAccountDao { 4 void saveAccount(); 5 }
dao层实现类:实现dao接口,对dao层接口中的方法进行实现即可。
1 package dao.impl; 2 3 import dao.IAccountDao; 4 5 public class AccountDaoImpl implements IAccountDao { 6 7 public void saveAccount() { 8 System.out.println("保存了账户"); 9 } 10 }
service层接口:定义一个saveAccount()方法即可。
1 package service; 2 3 public interface IAccountService { 4 void saveAccount(); 5 }
service层实现类:实现service接口,创建一个dao接口的对象,写一个构造方法(用来指示service层对象的创建),对seivice层接口中的方法进行实现。
1 package service.impl; 2 3 import dao.IAccountDao; 4 import service.IAccountService; 5 6 public class AccountServiceImpl implements IAccountService { 7 private IAccountDao accountDao ; 8 public AccountServiceImpl(){ 9 System.out.println("对象创建了"); 10 } 11 public void saveAccount() { 12 accountDao.saveAccount(); 13 } 14 }
步骤5:创建一个表现层的类Client,其中进行如下操作:
用ApplicationContext接口创建一个ClassPathXmlApplicationContext类的Spring核心容器,参数传入bean.xml配置文件,这个容器用来存放创建好的对象。
根据id获取dao层与service层的对象,可打印查看。
1 package ui; 2 3 import dao.IAccountDao; 4 5 import org.springframework.context.ApplicationContext; 6 import org.springframework.context.support.ClassPathXmlApplicationContext; 7 import service.IAccountService; 8 9 public class Client { 10 public static void main(String[] args) { 11 //获取Spring核心容器的对象ac 12 ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); 13 //根据id获取bean对象,这两行用了两种方法 14 IAccountService as = (IAccountService)ac.getBean("accountService"); 15 IAccountDao adao = ac.getBean("accountDao",IAccountDao.class); 16 System.out.println(as); 17 System.out.println(adao); 18 } 19 }
补充一些知识:
ApplicationContext 接口的实现类:
ClassPathXmlApplicationContext:它是从类的根路径下加载配置文件 推荐使用这种
FileSystemXmlApplicationContext:它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置。
AnnotationConfigApplicationContext:当我们使用注解配置容器对象时,需要使用此类来创建spring 容器。它用来读取注解。
关于Spring核心容器的两个接口:
BeanFactory 才是Spring 容器中的顶层接口。
ApplicationContext 是它的子接口。
ApplicationContext:立即加载。只要一读取配置文件,默认情况下就会创建对象。
BeanFactory:延迟加载。什么使用什么时候创建对象。
在bean.xml种创建bean的3种方式:
1.使用默认构造函数创建,要求对应的类中必须有默认的构造函数,否则必须用到constructor-arg标签(在依赖注入中详细说)
格式:<bean id="accountService" class="service.impl.AccountServiceImpl"></bean>
2.使用普通工厂中的方法创建对象(使用某个类中的方法创建对象,并存入Spring容器)
格式:<bean id="instanceFactory" class="factory.instanceFactory"></bean>
<bean id="accountService" factory-bean="instanceFactory" factory-method="getAccountService"></bean>
3.使用工厂中的静态方法创建对象(使用某个类中的静态方法创建对象,并存入Spring容器)
格式:<bean id="accountService" class="factory.StaticFactory" factory-method="getAccountService"></bean>
bean的作用范围
单例:所有请求使用同一个对象
多例:不同的请求用不同的对象
Spring中默认为单例,可以用bean标签的scope属性来设置
其中singleton为单例,prototype为多例。
bean的生命周期
单例对象:与Spring核心容器共存亡
多例对象:使用时由Spring创建,直到被垃圾回收机制回收时死亡。
Spring中的依赖注入
Dependency Injection
依赖关系:在当前类中需要用到其他类的对象
依赖注入:依赖关系的维护,就是前面我们在bean.xml文件中配置bean的过程。
Spring如何管理:在当前类中需要用到其他类的对象时,交由Spring来提供,我们只需要在配置文件中说明即可。
能注入的数据包括:基本数据类型,bean类型,复杂/集合类型。
注入方式1:通过构造函数注入
事先创建好需要注入的类,一般为service实现类和dao实现类。如果需要注入基本类型数据,则事先定义好私有属性。
在bean标签内部使用constructor-arg标签。
标签属性:type用于指明要注入的数据的数据类型。index指构造函数中的索引位置,注入第几个数据,从0开始。name指数据名称(一般用name)。value用于赋值。ref用于指向别的bean的id名称。
例:<bean id="accountService" class="service.impl.AccountServiceImpl">
<constructor-arg name=xxx value="xxx"></constructor-arg>
</bean>
注入方式2:set方法注入(常用)
事先生成属性的set方法。
在bean标签内部使用property标签。
标签属性与方式1相同。需要注意,name只看set方法的名字。
注入集合时,指定name属性后,再选择相应的子标签array,set等,再使用value或ref属性。
特别地:注入map时,须先写明map子标签,再写entry标签,然后写key与value。或写props子标签,写好key后,props标签体的内容默认为value。
AOP概念与背景
问题导入:
以客户增删改查程序为例:
版本1:service实现类中,我们创建了一个dao层对象,并为其定义了一系列的增删改查方法。
存在问题:这里没有手动设置自动提交事务,默认为true,即自动提交事务。一旦方法中出现异常,就可能会不满足事务的一致性。
版本2:另外写一个TransactionManager类,定义一个DBAssit对象。并写好开启事务,提交事务,事务回滚,释放资源的方法。然后修改原service实现类中的增删改查方法:在try中先开启事务,最后提交事务,在catch中回滚事务,在finally中释放资源。
存在问题:业务操作得到了实现。但是业务层方法变得臃肿了,重复代码多。并且业务层方法和事务控制方法耦合了。如果我们此时提交,回滚,释放资源中任何一个方法名变更,都需要修改业务层的代码。
版本3:使用动态代理。
动态代理的特点:字节码随用随创建,随用随加载。
动态代理常用的两种方式 :
基于接口的动态代理:JDK官方的Proxy类
与三大类中平行建一个factory包,其中建一个beanfactory类。
该类中的核心操作:
1.创建service层的对象(被代理对象)
2.创建代理对象
3.在InvocationHandler()中写一个方法,这样执行被代理(service)对象的任何方法时,都会先执行它。
4.在该方法中写上基于TransactionManager类的开启事务,提交事务,事务回滚,释放资源方法。
基于子类的动态代理 提供者:第三方的CGLib,需要导入asm.jar。(略)
而aop,就是通过配置的方式,实现这个功能!
正式介绍aop
AOP:全称是Aspect Oriented Programming即:面向切面编程。
简单的说它就是把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的基础上,对我们的已有方法进行增强。
一些专业术语:
Joinpoint(连接点): 指被拦截到的点。在spring中,这些点指service层接口中的方法。
Pointcut(切入点): 指我们要对哪些Joinpoint进行拦截的定义(有些连接点被指定不被拦截,那就不是切入点)。
Advice(通知/增强): 拦截到Joinpoint之后所要做的事情就是通知。
在通知代码中,我们总会执行method.invoke()方法(调用切入点方法),所以我们根据通知语句的位置,分为前置通知(invoke方法前的语句),后置通知(invoke方法后的语句),异常通知(catch语句中),最终通知(finally语句中),环绕通知(整体)
Target(目标对象): 被代理对象,这里就是service层的对象
Weaving(织入): 是指把增强应用到目标对象来创建新的代理对象的过程。
Proxy(代理): 一个类被AOP织入增强后,就产生一个结果代理类。
Aspect(切面): 是切入点和通知(引介)的结合。(把通知配置出来就是切面)
aop编程的核心:
a、开发阶段
1.编写核心业务代码(开发主线),
2.把公用代码抽取出来,制作成通知。
3.在配置文件中,声明切入点与通知间的关系,即切面。
b、运行阶段(Spring框架完成)
Spring框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。
基于xml配置的aop程序实现
步骤1:新建一个maven工程
步骤2:在弹出来的pom.xml文件中写packaging标签,并导入springframework依赖和aspectj依赖
1 <?xml version="1.0" encoding="UTF-8"?> 2 <project xmlns="http://maven.apache.org/POM/4.0.0" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 5 <modelVersion>4.0.0</modelVersion> 6 7 <groupId>org.example</groupId> 8 <artifactId>springAop</artifactId> 9 <version>1.0-SNAPSHOT</version> 10 <packaging>jar</packaging> 11 12 <dependencies> 13 <dependency> 14 <groupId>org.springframework</groupId> 15 <artifactId>spring-context</artifactId> 16 <version>5.0.2.RELEASE</version> 17 </dependency> 18 19 <dependency> 20 <groupId>org.aspectj</groupId> 21 <artifactId>aspectjweaver</artifactId> 22 <version>1.8.7</version> 23 </dependency> 24 </dependencies> 25 26 </project>
步骤3:在main/resources包下创建一个bean.xml配置文件,下面开始配置:
打开Spring Framework Documentation网页,点core,按ctrl+f,搜索xmlns:aop,复制配置头到配置文件中,并补全标签尾部。在其中写两个bean(ioc与aop的配置),分别包含service和logger的id与class。
在aop配置中分别对5种类型的通知进行配置。(注意:环绕通知运行与其他四种不能同时存在)
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xmlns:aop="http://www.springframework.org/schema/aop" 5 xsi:schemaLocation="http://www.springframework.org/schema/beans 6 http://www.springframework.org/schema/beans/spring-beans.xsd 7 http://www.springframework.org/schema/aop 8 http://www.springframework.org/schema/aop/spring-aop.xsd"> 9 <!--ioc配置--> 10 <bean id="accountService" class="service.impl.AccountServiceImpl"></bean> 11 <!--aop配置--> 12 <bean id="logger" class="utils.Logger"></bean> 13 <aop:config> 14 <!--配置切入点表达式,id为标识,expression为内容--> 15 <aop:pointcut id="pt1" expression="execution(* service..impl.*.*(..))"></aop:pointcut> 16 <!--配置切面--> 17 <aop:aspect id="logAdvice" ref="logger"> 18 <!--配置通知类型,并且建立通知方法和切入点方法的关联--> 19 <!--前置通知--> 20 <aop:before method="BeforePrintLog" pointcut-ref="pt1"></aop:before> 21 <!--后置通知--> 22 <aop:after-returning method="AfterPrintLog" pointcut-ref="pt1"></aop:after-returning> 23 <!--异常通知--> 24 <aop:after-throwing method="ThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing> 25 <!--最终通知--> 26 <aop:after method="FinalPrintLog" pointcut-ref="pt1"></aop:after> 27 <!--环绕通知--> 28 <!--aop:around method="AroundPrintLog" pointcut-ref="pt1"></aop:around--> 29 </aop:aspect> 30 </aop:config> 31 </beans>
步骤4:main/java/service下分别写好service层接口,实现类。
service层接口:定义几个方法,这里尝试了三种不同类型的方法(有无参数,有无返回值)。
1 package service; 2 3 public interface IAccountService { 4 void saveAccount(); 5 void updateAccount(int i); 6 int deleteAccount(); 7 8 }
service层实现类:实现service接口,对seivice层接口中的方法进行实现。
1 package service.impl; 2 3 import service.IAccountService; 4 5 public class AccountServiceImpl implements IAccountService { 6 public void saveAccount() { 7 System.out.println("执行了保存"); 8 } 9 10 public void updateAccount(int i) { 11 System.out.println("执行了更新"+i); 12 } 13 14 public int deleteAccount() { 15 System.out.println("执行了删除"); 16 return 0; 17 } 18 }
步骤5:main/java下创建一个utils包,其中创建一个logger类,其中写好5种通知的方法体。
1 package utils; 2 3 import org.aspectj.lang.ProceedingJoinPoint; 4 5 public class Logger { 6 //用于打印日志,在切入点方法之前执行 7 public void BeforePrintLog(){ 8 System.out.println("前置通知"); 9 } 10 public void AfterPrintLog(){ 11 System.out.println("后置通知"); 12 } 13 public void ThrowingPrintLog(){ 14 System.out.println("异常通知"); 15 } 16 public void FinalPrintLog(){ 17 System.out.println("最终通知"); 18 } 19 public Object AroundPrintLog(ProceedingJoinPoint pjp) { 20 Object rtValue = null; 21 try { 22 Object[] args = pjp.getArgs(); 23 System.out.println("环绕通知-前置"); 24 rtValue = pjp.proceed(args); 25 System.out.println("环绕通知-后置"); 26 return rtValue; 27 } catch (Throwable t) { 28 System.out.println("环绕通知-异常"); 29 throw new RuntimeException(t); 30 } finally { 31 System.out.println("环绕通知-最终"); 32 } 33 } 34 }
步骤6:test/java/test下创建一个AOPTest类。
用ApplicationContext接口创建一个ClassPathXmlApplicationContext类的Spring核心容器,参数传入bean.xml配置文件,这个容器用来存放创建好的对象。
用该容器对象调用service实现类种的方法。
1 package test; 2 3 import org.springframework.context.ApplicationContext; 4 import org.springframework.context.support.ClassPathXmlApplicationContext; 5 import service.IAccountService; 6 7 import java.sql.SQLOutput; 8 9 public class AOPTest { 10 public static void main(String[] args) { 11 //获取Spring核心容器的对象ac 12 ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); 13 IAccountService as = (IAccountService)ac.getBean("accountService"); 14 as.saveAccount(); 15 System.out.println("------------------"); 16 as.deleteAccount(); 17 System.out.println("------------------"); 18 as.updateAccount(1); 19 System.out.println("------------------"); 20 } 21 }