1. IOC(控制反转,Inversion of Control)
IOC:把创建对象的权力交给框架,是框架的重要特征,包括依赖注入和依赖查找。可降低类之间的耦合度。如果自己写代码,可以简单地通过工厂创建对象,若为单例可以将在初始化工厂时将对象放入容器中,再根据类名取单例对象。
之前一个类内部需要创建另外一个类的对象时,自己在类内部通过new或者反射的方式创建一个对象,直通通过工厂或者框架创建,我理解的这就是控制反转吧。
2. 使用Spring的ioc解决程序耦合问题
把对象的创建交给spring管理
获取spring的IOC核心容器,并根据id获取对象
例:通过ApplicationContext获取核心容器。
//XmlName为配置文件的名称,如Bean.xml
ApplicationContext ac = new ClassPathXmlApplicationContext(XmlName);
//BeanName为xml文件中配置的类的名称
Object ocj = ac.getBean(BeanName);
<bean id="Service" class="com.service.impl.ServiceImpl"></bean>
2.1 ApplicationContext的三个常用实现类
- ClassPathXmlApplicationContext:
加载类路径下的配置文件,要求配置文件必须在类路径下。 - FileSystemApplicationContext:
加载磁盘任意路径下的配置文件(必须有访问权限) - AnnotationConfigApplicationContext:
用于读取注解创建文件
2.2 创建核心容器的两个接口的不同
创建容器有两个接口:ApplicationContext和BeanFactory,其中BeanFactory是ApplicationContext的子接口。
ApplicationContext:
适用于单例对象。ApplicationContext在创建核心容器时,创建对象采取的策略是立即加载的方式,即只要读取完配置文件,马上通过反射的方式创建配置文件中的对象,并加入到核心容器中。因其是底层接口,更多的采用此接口定义容器对象。
在上例代码中,可以在ServiceImpl的默认构造函数中输出一句话,在读取配置文件的那一行打断点,即可发现在配置文件读取完毕后,配置文件中相应的对象都已经加载完毕。
BeanFactory:
适用于多例对象。BeanFactory在创建核心容器时,创建对象采取的策略是延时加载的方式,运行到根据id查找对象时,即如下代码,才真正的创建对象并把对象方入容器。
Object ocj = ac.getBean(BeanName);
2.3 Spring对Bean的管理细节
(1).创建Bean的三种方式
① 使用默认构造函数创建:在spring配置文件中使用bean标签,配以id和class(全限定类名)属性之后,且没有其他属性和标签时,采用的就是默认构造函数创建bean对象。若没有默认构造函数则对象无法创建。
//默认构造函数创建对象xml文件示例
<bean id="Service" class="com.service.impl.ServiceImpl"></bean>
<bean id="Dao" class="com.dao.impl.DaoImpl"></bean>
② 使用普通工厂类中的方法创建对象,即使用某个类(工厂类)中的方法创建对象,并存入spring容器
//首先创建一个普通工厂类
public class InstanceFactory {
public IService getService(){
return new ServiceImpl();
}
}
factory-bean用于指定创建该对象的工厂类id,factory-method用于指定创建该bean对象的方法。
<!--xml配置文件中的实现如下-->
<bean id="instanceFactory" class="com.factory.InstanceFactory"></bean>
<bean id="Service" factory-bean="instanceFactory" factory-method="getService"></bean>
③使用静态工厂中的静态方法创建对象(使用某个类中的静态方法创建对象并存入spring)
静态工厂类:
public class StaticFactory {
public static IService getService(){
return new ServiceImpl();
}
}
配置文件:
<bean id="Service" class="com.factory.StaticFactory" factory-method="getService"></bean>
(2).bean的作用范围调整
spring创建的对象默认就是单例的,通过bean中的scope属性调整
scope:
- singleton单例
- prototype多例
- request:作用于web应用的请求范围
- session:作用于web应用的会话范围
- global-session:作用于集群环境的会话范围(全局会话范围),当不是集群环境是,它就是session
(3).bean对象的生命周期
单例对象
出生:当容器创建时对象出生,即之前的ApplicationContext
活着:只要容器还在,对象一致活着
死亡:容器销毁,对象消亡
多例对象
出生:当使用对象时spring框架为我们创建,即之前说的BeanFactory
活着:对象只要在使用过程中就一致活着
死亡:当对象长时间不用,且没有别的对象引用时,有java的GC回收
可在标签中指定初始方法init-method和销毁方法destroy观察
(4).spring中的依赖注入
依赖注入:Dependency Injection
IOC作用:降低程序间的耦合(依赖关系)
依赖关系的管理:以后都交给了Spring来维护,在当前类需要用到其他类的对象时,由spring为我们提供,我们只需要在配置文件中说明
依赖关系的维护就称之为依赖注入
依赖注入
能注入的数据,有三类
① 基本数据类型和String
② 其他bean类型(在配置文件中或者注解配置过的bean)
③ 复杂类型/集合类型
注入的方式,有三种
① 使用构造函数提供
在bean标签内部使用constructor-arg标签,标签中的属性
type:用于指定要注入的数据类型,该数据类型也是构造函数中某个或某些参数的类型
index:用于指定要注入的数据给构造函数中指定索引位置的参数赋值,从0开始
name:用于指定给构造函数中指定名称的参数赋值 ,常用
以上三个用于指定构造函数中哪个参数赋值
value:用于提供基本类型和String类型数据
ref:用于提供其他bean类型,如下指的就是在spring的ioc核心容器中出现过的bean对象date
<bean id="Service" class="com.service.impl.ServiceImpl">
<constructor-arg name="name" value="kang"></constructor-arg>
<constructor-arg name="age" value="19"></constructor-arg>
<constructor-arg name="birthday" ref="now"></constructor-arg>
</bean>
<bean id="now" class="java.util.Date"></bean>
注:对应类中必须有对应的有相同参数的构造方法
构造函数注入方式分析:
优势:在获取bean对象时,注入数据时必须的操作,否则对象无法创建成功,如果某些类只想提供某些参数创建对象时比其他方式更简单。
弊端:改变了bean对象的实例化方式,即使用不到这些数据也必须提供
② 使用set方法提供 更常用
在bean标签内部使用property标签,标签中的属性有:
name:用于指定注入时所调用的set方法名称
value:用于提供基本类型和String类型数据
ref:用于提供其他bean类型,指的就是在spring的ioc核心容器中出现过的bean对象
<bean id="Service" class="com.service.impl.ServiceImpl">
//对应实体类中必须有相应属性的set方法
<property name="name" value="kang"></property>
<property name="age" value="19"></property>
<property name="birthday" ref="now"></property>
</bean>
<bean id="now" class="java.util.Date"></bean>
set方法
优势:创建对象没有明确限制,可以直接使用默认构造函数
弊端:如果有某个成员必须有值,无法保证一定注入,有可能没有set方法,则获取对象是有可能set方法没有执行
当类中有集合类型的属性时,set方法注入时结构相同标签可以互换
总体上分为两种标签
map类标签:map和property
array类标签:array、list和set
<bean id="accountService" class="com.service.impl.AccountServiceImpl">
<property name="myStr" >
<array>
<value>aaa</value>
<value>bbb</value>
</array>
</property>
<property name="myList" >
<list>
<value>aaa</value>
<value>bbb</value>
</list>
</property>
<property name="myMap" >
<map>
<entry key="1">
<value>aa</value>
</entry>
<entry key="2" value="bb"></entry>
</map>
</property>
<property name="myProps">
<props>
<prop key="3">cc</prop>
</props>
</property>
</bean>
③ 使用注解提供
注:如果是经常变化的数据,并不使用于注入的方式,因为单例只有一个对象,属性永远不能变。
3 基于注解的IOC配置
以下为用xml配置文件实现的创建bean对象的基本方式
<bean id="Service" class="com.service.impl.ServiceImpl" scope="singleton" init-method="" destroy-method="">
//对应实体类中必须有相应属性的set方法
<property name="name" value="kang"></property>
<property name="age" value="19"></property>
<property name="birthday" ref="now"></property>
</bean>
spring中作用于bean对象的各个注解对应于上边xml中各个部分实现的功能大体一致。要想通过使用注解的方式创建对象,就要修改xml配置文件中的相关配置。告知spring在创建容器时要扫描context名称空间和约束,而不是去扫描配置文件中的bean标签。
<?xml version="1.0" encoding="GBK"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!--告知spring在创建容器时要扫描的包,配置所需要的标签不是在bean的约束中,而在一个名称为context名称空间和约束中-->
<!--会扫描所有位于com包下的类和接口上的注解-->
<context:component-scan base-package="com"></context:component-scan>
</beans>
用注解的方式创建bean对象中的相关注解大概有这么几类
- 用于创建对象的:作用就和在xml配置文件中的bean标签一样
@Component(value=name)
作用:放在加载类定义的前一行,用于把当前类对象存入spring容器中。
属性:value用于指定bean的id。可省略,默认值时当前类名,且首字母改小写。
以下三个注解的作用和属性与@Component基本一致
这三个注解分别时spring框架为我们提供明确的三层使用的注解,是我们的三层对象更加清晰。
@Controller:一般用于表现层
@Service:一般用于业务层
@Repository:一般用于持久层
下面代码将类型为DaoImpl 的对象放入到Spring的ioc核心容器中,其中key为注解中的值Dao,value为DaoImpl类型的对象,下边两个类都可以看做时IDao类型
@Component("Dao1")
public class DaoImpl1 implements IDao {}
@Component("Dao2")
public class DaoImpl2 implements IDao {}
-
用于注入数据的:作用就和在xml配置文件中的bean标签中写一个property标签时一样的
@Autowired
作用:自动按照类型注入。只要容器(key,value)中有唯一的一个bean对象类型和要注入的变量类型匹配,就可以注入成功。如果ioc容器中没有和要注入变量类型一致的对象时,则报错。如果ioc容器中有多个类型匹配时,首先按照类型(value)找出匹配的对象,然后再按照变量名称(key)查找一致的对象
位置:可在方法上,也可在变量上。
细节:在使用注解注入时,set方法就不是必要的了。如下面代码,在ioc核心容器执行了getBean(“Service”)时,当执行有关dao属性的方法时,就会自动的从容器中寻找IDao类型的对象,若容器中只有一个直接注入
@Component("Service")
public class AccountServiceImpl implements IAccountService {
@Autowired
private IDao dao;
}
@Qualifier
作用:在按照类中注入的基础之上再按照名称注入。它在给类成员注入时不能单独使用,但是在给方法参数注入时可以。
属性:value,用于指定bean的注入的id
注:不能独立使用,只能和@Autowired配合使用
下例中中,ioc容器中有两个IDao类型的对象,用Qualifier注解指定属性自动注入的Bean的id
@Component("Service")
public class ServiceImpl implements IService {
@Autowired
@Qualifier("Dao1")
private IDao dao;
}
@Component("Dao1")
public class DaoImpl1 implements IDao {}
@Component("Dao2")
public class DaoImpl2 implements IDao {}
@Resource
作用:直接按照bean的id(key)注入
属性:name,用于指定bean的id
注意:@Autowired、@Qulifier和@Resource三个注解只能注入其他bean类型的数据,而基本类型和String类型无法使用上述注解实现。另外,集合类型的注入只能通过XML来实现。
@Value
作用:用于注入基本类型和String类型的数据
属性:用于指定数据的值。可以使用spring中的SpEL(Spring中的el表达式)
SpEL写法:${表达式}
- 用于改变作用范围的:作用和在bean标签中的scope属性是一样的
@Scope
作用:用于指定bean的作用范围
属性:value,指定取值范围。常用取值:singleton和prototype,默认为singleton
//在上边默认的情况下,为单例,as和as2指向的同一个对象
IService as = (IService) ac.getBean("Service");
IService as2 = (IService) ac.getBean("Service");
System.out.println(as==as2); //true
//若改为以下代码
@Component("Service")
@Scope("prototype")
public class ServiceImpl implements IService {
@Autowired
@Qualifier("Dao1")
private IDao dao;
}
IService as = (IService) ac.getBean("Service");
IService as2 = (IService) ac.getBean("Service");
System.out.println(as==as2); //false,但as和as2中的dao仍为一个对象
-
和生命周期相关:和bean标签中init-method和destroy-method一样
@ProDestroy
作用:用于指定销毁方法,单例对象随容器关闭销毁,容器不负责多例对象,由GC回收
@PostConstruct
作用:用于指定初始化方法 -
在使用注解创建和注入自定义的类时比较方便,由于所导入的jar包无法在其类头部加上@Service的注解,并且需要指定创建ioc容器时需要扫描的包,故只能在xml文件中定义bean标签将其放入ioc容器中。为了进一步实现没有xml的基于注解的ioc配置,引入一个新的注解
@Configuration
作用:指定当前类是一个配置类@ComponentScan(value=packageName)
作用:用于通过注解指定spring在创建容器时要扫描的包
属性:value和basePackages的作用一样
注意:当配置类作为AnnotationConfigApplicationContext对象创建的参数时,该注解可以不写。
使用此注解作用相当于xml中context:component-scan标签@Bean
作用:用于吧当前方法的返回值作为bean对象存入spring的ioc容器中
属性:用于指定bean的id,默认值为当前方法的名称
注意:当用注解配置方法时,如果方法有参数,spring框架回去容器中查找有没有可用的bean对象,查找方式和Autowired一样。@Import
作用:用于导入其他的配置类
属性:value用于指定其他配置类的字节码
注意:当我们使用Import注解之后,有Import注解的为父配置类,而导入的都是子配置类@PropertySource
作用:用于指定properties文件的位置
属性:value用于指定文件的名称和路径
关键字:classpath表示类路径下
思考:Spring为什么要整合junit,怎么整合。
3.AOP
AOP(Aspect Oriented Programming):面向切面编程
通过预编译的方式和运行期静态代理实现程序功能的统一维护的一种技术.AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
–摘自百度百科
简单的说它就是把我们程序重复的代码抽取出来,在需要执行的时候,使用倒台代理的技术,在不修改源码的基础上,对我们的已有方法进行增强。
优势:减少重复代码,提高开发效率,维护方便
Spring中的AOP通过配置的方式实现动态代理。
3.1 AOP相关术语:
Joinpoint:连接点,是指那些被拦截到的点。在Spring中,这些点只支持方法。
Pointcut:切入点,只的是我们要对那些Joinpoint进行拦截的定义。所有的切入点都是连接点,只有被增强的连接点才是切入点。
Advice:通知/增强,是指拦截到Joinpoint之后所要做的事情。通知的类型有:前置通知,后置通知,异常通知,最终通知和环绕通知。
Introducetion:引介,是一种特殊的通知,在不修改类代码的前提下,Introducetion可以在运行期间为类动态地添加一些方法或者属性。
Target:代理的目标对象。
Weaving:织入,是把增强应用到目标对象来创建新的代理对象的过程。Spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。
Proxy:代理对象,一个被AOP织入增强后,就产生的一个结果代理类。
Aspect:切面,是切入点和通知(引介)的结合。
3.2 学习Spring中AOP要明确的事
a. 开发阶段(我们做的)
编写核心业务代码(开发主线):大部分程序员来做,要求熟悉业务需求。
把公用代码抽取出来,制作成通知。(开发阶段最后在做):AOP编程人员来做。
在排至文件中,声明切入点与通知之间的关系,通知哪些方法不需要增强,哪些通知什么时候执行等,即切面:AOP编程人员来做。
b. 运行阶段(Spring框架完成的)
Spring框架监控切入点方法的执行,一旦监控到切入点发方被循行,使用代理机制,动态创建目标对象的带俩对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。
3.3 spring中AOP配置步骤
3.3.1 基于xml的AOP配置
-
把通知Bean也交给spring管理配置
-
使用aop:config标签表明开始AOP的配置–>
-
使用aop:aspect标签表明开始配置切面
id属性:给切面指定一个唯一标识
ref属性:指明通知类的id–> -
在aop:aspect标签内部使用对应标签类配置通知的类型
我们现在示例是让println方法在切入点之前执行:前置通知
aop:before标签表示前置通知
method属性:用于指定Logger类中的哪个方法
pointcut属性:用于指定切入点表达式,该表达式的含义值得是对业务层中那些方法增强切入点表达式的写法: 关键字:execution(表达式) 表达式:访问修饰符 返回值 包名.包名...类名.方法名(参数列表) 表达式标准写法:public void com.service.impl.AccountServiceImpl.saveAccount() 访问修饰符可省略:void com.service.impl.AccountServiceImpl.saveAccount() 返回值可以使用通配符表示任意返回值:* com.service.impl.AccountServiceImpl.saveAccount() 全通配写法 :* *..*.*(..)
3.3.1 基于xml的AOP配置
通过以下注解进行配置
括号中须指明配置有@Pointcut(切入点表达式)的方法名
@Before()
@AfterReturning()
@AfterThrowing()
@After()
@Around()
4. Spring中的事务控制
JavaEE体系进行分层开发,事务控制位于业务层,Spring提供了分层涉及业务层的事务处理解决方案。Spring框架为我们提供了一组事务控制的接口,在spring-tx-包中。spring事务控制都是基于AOP的,它既可以使用编程的 方式实现,也可以使用配置的方式实现。
4.1 Spring中事务控制的API
PlatformTransactionManager接口
该接口有两个实现类:
DataSourceTransactionManager:使用Spring JDBC或者MyBatis进行持久化数据时使用。
HibernateTransactionManager:使用Hibernate版本进行持久化数据时使用。
实现类就是相当于配置bean。
TransctionDefinition接口
事务的定义信息对象。
TransactionStatus接口
提供的是事务具体的运行状态,描述了某个时间点上事务对象的状态信息。