Spring框架
Spring是一个大框架:包括Spring,SpringMVC,SpringBoot,SpringCloud
Spring功能:类和类之间的管理,帮助开发人员创建和管理对象
Spring核心技术:Ioc 和 AOP,能实现模块之间和类之间的解耦合
Spring优点
- 轻量,Jar 包一般都较小
- 针对接口编程,解耦合
- AOP 编程的支持
- 可以集成其他各种框架
Spring体系结构
- 数据库模块
- Web模块
- AOP模块
- 集成功能模块
- 核心容器模块
- 测试模块
IOC (Inversion of Control)控制反转
本质是一种理论,一种思想,即把对象的创建,赋值,管理工作都交给代码之外的容器实现
容器本质可以是一种服务器,或者一种框架
服务器:如 Tomcat 帮助我们自动创建 Servlet,Listener,Filter 对象
框架:如 Spring 框架
控制:创建对象,对象赋值,对象关系管理
正转:由开发人员用 new 构造方法来创建对象
反转:创建对象,对象赋值,对象关系管理交给代码之外的容器实现
使用 IOC 的优点
-
减少对代码的改动,也能实现不同功能的修改
-
易于维护,解耦合
IOC 技术实现
DI 是 IOC 的技术实现,意为依赖注入(Dependency Injection)
只需要提供要使用的对象名称即可,创建赋值查找等过程由容器封装实现
Spring 使用 DI 实现了 IOC 功能,底层创建对象使用的是反射机制。
Spring的配置文件
<?xml version="1.0" encoding="UTF-8"?>
<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 id="student" class="service.impl.StudentImpl"/>
</beans>
beans:是根标签,spring把java对象成为bean。
spring-beans.xsd 是约束文件,和mybatis指定dtd是一样的。
id: 对象的自定义名称,唯一值。spring通过这个名称找到对象
class: 类的全限定名称(不能是接口,因为spring是反射机制创建对象,必须使用类)
Spring 完成 Someservice someservice = new SomeserviceImpl();
被Spring生成的对象会放置Spring容器中,是一个Map集合的方式
Spring的使用方法
public static void main(String[] args){
String springConfig = "applicationContext.xml";
ApplicationContext ac = new ClassPathXmlApplicationContext(springConfig);
Student student = (Student) ac.getBean("student");
int num = ac.getBeanDefinitionCount();
student.doService();
System.out.println("对象数量:" + num);
String[] objects = ac.getBeanDefinitionNames();
for(String name:objects){
System.out.println(name);
}
}
/*
通过 getBean() 方法来获取生成好的对象
ac.getBeanDefinitionCount() 获取Map集合中对象数量
ac.getBeanDefinitionNames() 获取对象名称数组
*/
ApplicationContext 就是表示Spring容器的对象
当ApplicationContext被创建时,Spring中Map中的所有对象都被构造方法创建
通过Spring 给 Java 对象赋值
基于 XML文件的 DI 给对象赋值
1. 通过 set 方法
本质是在调用完构造方法创建对象后去调用对象中的set方法
PS:有set方法,但是对象没有对应的属性并不影响其执行
① 简单类型
<bean id="student" class="service.impl.StudentImpl">
<property name="id" value="20"/>
<property name="age" value="21"/>
</bean>
② 引用类型
<bean id="mySchool" class="service.impl.School">
<property name="name" ref="北京大学"/>
</bean>
<bean id="student" class="service.impl.StudentImpl">
<property name="school" ref="mySchool"/>
</bean>
ref中填写某个bean的id
2. 通过构造方法
<bean id="student" class="service.impl.StudentImpl">
<constructor-arg name="name" value="李华"/>
<constructor-arg Index="1" ref="lihuaSchool"/>
</bean>
name为形参名称,value为简单类型的值
Index为参数位置(从0开始计算),ref为引用类型的值
引用类型自动注入
1. byName
java对象中的属性和Spring中 <bean> 的id是名称一样的,且数据类型相同
<bean id="student" class="class" autowire="byName">
简单类型属性赋值
</bean>
这种方法本质通过set 方法为引用类型赋值,而构造方法中只能包含简单类型的属性
2. byType
- java 类中的数据类型和Spring中的数据类型是同源的
同源的定义:
- java类中引用类型的数据类型和bean的class的值是―样的。
- java类中引用类型的数据类型和bean的class的值父子类关系的。
- java类中引用类型的数据类型和bean的class的值接口和实现类关系的语法
<bean id="xx" class="yyy" autowire="byType"> 简单类型属性赋值 </bean>
PS:在byType中,在xml配置文件中声明bean只能有一个符合条件的,多余一个是错误的
引入配置文件
<import resource="classpath:其他配置文件路径">
注解 DI
使用注解 DI 必须加入spring-aop依赖,而加入spring-context依赖同时会间接加入spring-aop依赖
@Component
用于创建对象,等同于<bean>的功能
@Component(value="myStudent")
public class Student{
private String name;
}
属性:value就是对象的名称,也就是bean的id值(当只有value一个属性时,value可以省略不写)
value的值是唯一的,创建的对象在整个spring容器中就一个
当不对属性进行赋值时,默认的value为类名的首字母小写
声明组件扫描器
<context:component-scan base-package="包名,包名1"/>
包名和包名之间可以用 ; 或者 , 来分隔
Spring中和@component功能一致,创建对象的注解还有:
- @Repository (用在持久层类的上面):放在dao的实现类上面,表示创建dao对象,dao对象是能访问数据库的。
- @Service(用在业务层类的上面):放在service的实现类上面,创建service对象,service对象是做业务处理,可以有事务等功能的。
- @Controller(用在控制器的上面):放在控制器(处理器)类的上面,创建控制器对象的控制器对象,能够接受用户提交的参数,显示请求的处理结果。
以上三个注解的使用语法和@component一样的。都能创建对象,但是这三个注解还有额外的功能,他们三个可以用来给项目分层
@Value 简单属性赋值
位置:
1. 属性定义上面,推荐使用
2. 在set方法的上面
@Autowired : Spring框架提供的注解,实现引用类型自动注入
默认使用 byType
位置:
1. 属性定义上面,推荐使用
2. 在set方法的上面
byName方法
在属性上方加入@Autowired并且在属性上方加入@Qualifier(value="beanId")
@Autowired 属性 required 默认为true
意为引用类型赋值失败,报错并终止程序,可以手动赋值为false,使引用类型可以为null
@Resource
来源于JDK,而不是Spring框架
也是自动注入原理,支持byName和byType
默认为byName(如果byName赋值失败,改用byType )
位置:
1. 属性定义上面,推荐使用
2. 在set方法的上面
@Resource(name="school")指定了name之后假如赋值失败也不会使用byType方式
注解方式缺点:
- 对代码有较强的侵入性,建议是不常更改的代码才使用注解方式
- 代码结构繁杂
注解可以和配置文件联用,可维护性更高
在Spring主配置文件中引入 property 配置文件
<context:property-placeholder location="classpath:----"/>
<!--
引用配置信息
@Value("${key}");
-->
AOP 面向切面编程
Jdk动态代理要求目标对象必须实现接口
动态代理可以给目标类方法增加额外功能(功能增强)而不改变原目标类的代码,如添加日志
如果不使用动态代理而改变目标类方法很有可能使 业务方法 与 非业务方法(增加日志功能) 冗杂在一起,不符合开闭原则,从而使代码耦合度提高,并且不利于维护
AOP实现就是基于动态代理(规范化的动态代理)
当目标类有接口时使用的是JDK动态代理,而当目标类没有接口时,使用的是cgLib的动态代理
如果有接口时也想要用cglib动态代理,在主配置文件下加入如下代码:
<aop:aspectj-autoproxy proxy-target-class="true"/>
Aspect:切面,给目标类增加功能就是切面。
切面特点:一般都是非业务功能,可以独立使用
怎么理解面向切面编程?
需要在分析项目功能时,找出切面
合理的安排切面的执行时间(在目标方法前,还是目标方法后)
合理的安全切面执行的位置,在哪个类,哪个方法增加增强功能
术语:
- 切面 Aspect:给目标类增加功能就是切面。
- 连接点 JionPoint,连接业务方法和切面的位置。即某类中的业务方法
- 切入点 Pointcut,多个连接点方法的集合
- 目标对象 哪个类增加功能哪个类就是目标对象
- 通知 Advice,通知切面功能执行的时间
项目开发中很少使用Spring 的 aop 实现,因为Spring的aop比较笨重。
aspectJ
-
一个开源的aop框架
aspectJ 框架实现 aop 有两种方式
- 使用xml配置文件
- 使用注解,aspectJ 有 5个注解
AspectJ 的切入点表达式
execution(访问权限 方法返回值 方法声明(参数) 异常类型)
aspectJ依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.9.RELEASE</version>
</dependency>
@Aspect
public class MyAspect{
//定义切面方法
@Before(value="execution(public void classpath.class(...))")
public void myBefore(){}
}
@Aspect表示当前类是切面类
切面方法规范:
- 修饰符 public
- 返回值 void
- 如果有参数,参数不是自定义的
@Before:前置通知注解
在目标方法之前执行,不会改变方法的执行结果
声明切面类对象
<bean id="myAspect" class="classpath"/>
声明自动代理生成器(会把Spring中所有目标对象都生成代理对象)
<aop:aspectj-auto proxy/>
实现步骤:
创建业务功能接口
public interface SomeService {
void doThis();
void doThat();
}
创建业务接口实现类
public class DoSomeService implements SomeService {
public void doThis(){
System.out.println("I am doThis");
}
public void doThat(){
System.out.println("I am doThat");
}
}
创建AspectJ切面类
@Aspect
public class MyAspectJ {
@Before("execution(public void service.impl.DoSomeService.*())")
public void doAspectJ(){
System.out.println("AspectJ Time is" + new Date());
}
}
将对象交给Spring容器管理,并声明切面类对象和自动代理生成器
<bean id="doSomeService" class="service.impl.DoSomeService"/>
<bean id="MyAspectJ" class="util.MyAspectJ"/>
<aop:aspectj-autoproxy/>
测试AspectJ代码
@Test
public void testAspectJ(){
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
SomeService someService = (SomeService) ac.getBean("doSomeService");
someService.doThat();
}
代理中获得的必须是SomeService类型的接口,而不能是DoSomeService的接口实现类
JoinPoint 参数
如果要有JoinPoint参数,它必须在参数列表的第一个位置
@Before("execution(public void service.impl.DoSomeService.*(..))")
public void doAspectJ(JoinPoint jp){
System.out.println(jp.getSignature()); //获取完整的jp信息
System.out.println(jp.getSignature().getName());//获取触发的函数名
Object[] objects = jp.getArgs(); //获取实参列表
for(Object arg:objects){
System.out.println(arg);
}
}
@AfterReturning:后置通知
属性:
- value切入点表达式
- returning自定义的变量,表示目标方法返回值。自定义变量名必须和通知方法的形参名一样。
特点:
- 在目标方法之后执行的。
- 能够获取到目标方法的返回值,可以根据这个返回值做不同的处理功能
- 可以修改这个返回值
@AfterReturning(value = "execution(public int service.impl.DoSomeService.*(..))",returning = "result")
public void doAfterJ(Object result){
System.out.println(result);
System.out.println((int)result*2);
}
可以读取和修改result的值,但是因为他是在目标方法执行完毕后该进行的,
因此目标方法已经将返回值赋值给了某个变量,而修改的result并不能改变原目标方法的返回值
@Around:环绕通知
特点:
- 在目标方法前后都增强功能
- 控制目标方法是否被调用
- 修改原目标方法执行结果
环绕通知可以理解为JDK动态代理
规范:
- public
- 必须有一个返回值,推荐使用Object
- 方法名称自定义
- 方法有参数,固定的参数ProceedingJoinpoint
@Around("execution(* *..impl.DoSomeService.doAround(..))")
public Object doAroundJ(ProceedingJoinPoint jp) throws Throwable{
Object[] objs = jp.getArgs();
int num = 0;
Object result = 1;
if(objs != null && objs.length >=1){
num = (int) objs[0];
}
System.out.println("环绕通知,前通知");
if(num == 2){
result = jp.proceed(); //相当于动态代理的method.invoke()
}
System.out.println("环绕通知,前通知");
return result;
}
返回类型为Object,通过if判断实参来控制是否执行环绕通知
最后修改result的值会影响目标方法的返回值
@AfterThrowing:异常通知
规范:
- public
- 没有返回值
- 方法名称自定义
- 方法可以没有参数,如果有是Joinpoint
属性:
- value切入点表达式
- throwinng自定义的变量,表示目标方法抛出的异常对象。变量名必须和方法的参数名一样
特点:
- 在目标方法抛出异常时执行的
- 可以做异常的监控程序,监控目标方法执行时是不是有异常。 (如果有异常,可以发送邮件,短信进行通知 )
@After:最终通知
一般用于做资源清除
规范:
1.public
2.没有返回值
3.方法名称自定义
4.方法可以没有参数,如果有是Joinpoint
属性:value切入点表达式
位置:在方法的上面
特点:
1.总是会执行
2.在目标方法之后执行的
辅助功能
@Pointcut
用于给切入点表达式(execution())取别名
@Pointcut(value = "execution(* *..impl.*(..))")
private void myPointcut(){
//内部不需要代码
//其他切入点可以直接将myPointcut()当中切入点表达式使用
}
Spring 集成 MyBatis
Spring 事务处理
Spring 事务管理器
事务管理器是一个接口和众多实现类
接口:PlatfromTransactionManager,定义了 commit 和 rollback 方法
实现类: 创建了每一种数据库访问技术对应的事务处理类
1) 事务的隔离级别:有4个值。
- READ_UNCOMMITTED: 读未提交。未解决任何并发问题。
- READ_COMMITTED: 读已提交。解决脏读,存在不可重复读与幻读。
- REPEATABLE READ: 可重复读。解决脏读、不可重复读,存在幻读。
- SERIALIZABLE: 串行化。不存在并发问题。
2) 事务超时时间
表示一个方法执行的最长执行时间,如果时间超出则事务回滚
单位是秒,整数值,默认是 -1(项目中影响因素过多,一般保持默认)
3) 事务传播行为
控制业务方法是不是事务的,是什么样的事务的。
PROPAGATION_REQUIRED | 若当前存在事务,则该方法加入当前事务之中,否则新建一个事务 |
PROPAGATION_REQUIRES_NEW | 总是新建一个事务 |
PROPAGATION_SUPPORTS | 不论有没有事务都可以执行事务 |
提交事务,回滚事务的时机
-
当你的业务方法,执行成功,没有异常抛出,当方法执行完毕,spring在方法执行后提交事务。事务管理器的 comnit
-
当你的业务方法抛出运行时异常或ERROR,spring执行回滚,调用事务管理器的 rollback 运行时异常的定义: RuntimeException和他的子类都是运行时异常,例如NullPointException ,NumberFormatExcept.
-
当你的业务方法抛出非运行时异常,主要是受查异常时,提交事务( 受查异常:在你写代码中,必须处理的异常。例如IOException,SQLException )
事务管理实现流程
方法1:使用@Transactional的步骤,适合中小型项目
1.声明事物管理器对象
<bean id="transactionManager" class="DataSourceTransactionManager">
<property name="dataSource" ref="myDataSource"><!--指定对于的数据源-->
</bean>
2.开启事物注解驱动
Spring使用aop机制,创建@Transactional所在的类代理对象,给其方法加入事物功能
本质是:利用环绕通知,在方法执行前后分别开启和提交(回滚)事物
<tx:annotation-driven transaction-manager="transactionManager">
PS:有多个不同包下的annotation-driven,应该使用结尾为tx的,否则会报错
3.在相应方法上添加@Transactional
@Transactional(
propagation = Propagation.REQUIRED,
isolation = Isolation.DEFAULT,
readOnly = false,
rollbackFor = {异常类.class}
)
/*
propagation 指定传播方式
isolation 使用默认即可
readOnly 当事物只用查询功能时为true,可以提高数据库查询效率
rollbackFor 指定当抛出指定异常时,事务发生回滚
(一般继承与RuntimeException运行时异常,
当异常在rollbackFor列表中,不论是什么类型的异常都会回滚,
当不在表中时,会判断是否继承于RuntimeException异常,是则回滚)
PS:当属性全都不写时,全都使用默认值
*/
方法2:配置 xml 文件实现,适合大型项目
1.需要引入 aspectj 依赖
2.声明事物管理器对象
3.声明业务方法的事务属性(隔离级别,传播行为)
<tx:advice id="xx" transaction-manager="transactionManager">
<!--transaction-manager要写声明的事物管理器的 id-->
<tx:attributes>
<!--
name:方法名,不带用包名,可以使用通配符
rollback-for: 必须是全限定名称
-->
<tx:method name="" propagation="" rollback-for=""/>
</tx:attributes>
</tx:advice>
4.配置aop(用于指定哪些包中需要使用到事务)
<aop:config>
<!--配置事务方法切入点-->
<aop:pointcut id="" expression="execution(* *..service..* .*(..))"/>
<!--
配置增强器:
advice-ref 使用 <tx:advice> 标签的id
pointcut-ref 使用 <aop:pointcut> 标签的id
-->
<aop:advisor advice-ref="" pointcut-ref=""/>
</aop:config>
Spring 和 Web
1.创建监听器
将 applicationContext 对象交给全局变量管理,可以使用框架中提供的,也可以自己创建
<listener>
<listener-class>ContextLoaderListener</listener-class>
</listener>
2.修改默认文件位置
因为在创建监听器时,会默认到WEB-INF/目录下寻找applicationContext.xml文件
<context-param>
<param-name>contextConfigLocation<param-name>
<param-value>classpath:spring.xml</param-value>
</context-param>
3.获取 applicationContext 对象
WebApplicationContext ctx = null;
ServletContext sc = getServlertContext();
ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(sc);
扩展:
单元测试
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
测试方法
@Test
public void test01(){
System.out.println("Test is running");
}
必须有@Test注释,方法修饰符必须是public,返回类型必须是void