Spring学习笔记
核心技术:ioc和aop
ioc:使用di(依赖注入)实现控制反转,底层使用的是反射机制
spring可以创建自己写的类的对象,也可以创建非自定义对象,只要知道类的全限定名即可。
spring创建对象:默认调用的是无参构造方法。
使用spring框架的步骤
-
加入spring-context依赖
-
创建类:接口,实现类、没有接口的类
-
创建spring的配置文件applicationContext.xml,使用声明对象
-
使用容器中的对象:
ApplicationContext context = new ClassPathXmlApplicationContext(applicationContext.xml); Student myStudent = (Student) context.getBean("myStudent");
IOC
依赖注入方式
-
set注入(设值注入)
该方式下,spring先调用无参构造,再调用set方法。
- 简单类型的set注入:
<bean id="myStudent" class="xxx"> <property name="name" value="张三"/> <property name="age" value="20"/> </bean>
注:只要有set方法,就可以给任意类对象设置属性值。
- 对引用类型的set注入:
<bean id="myStudent" class="com.springstudy.DiSet02.Student"> <property name="name" value="陈汉源"/> <property name="age" value="22"/> <!--引用类型的赋值--> <property name="school" ref="mySchool"/> </bean> <bean id="mySchool" class="com.springstudy.DiSet02.School"> <property name="name" value="上海应用技术大学"/> <property name="address" value="上海奉贤区"/> </bean>
-
构造注入
使用标签
标签属性:
name:表示构造方法的形参名
index:表示构造方法的参数的位置,从左到右依次是0、1、2…
value:构造方法的形参类型是简单类型的,使用value
ref:构造方法的形参类型是引用类型的,使用ref
<bean id="myStudent" class="com.springstudy.DiSet03.Student">
<constructor-arg name="name" value="zs"/>
<constructor-arg name="age" value="21"/>
<constructor-arg name="school" ref="mySchool"/>
</bean>
<bean id="mySchool" class="com.springstudy.DiSet03.School">
<property name="name" value="上海应用技术大学"/>
<property name="address" value="上海奉贤区"/>
</bean>
什么样的对象需要放入容器中?
dao类、service类、controller类、工具类等。
不需要放入到spring容器中的对象?
- 实体类对象,实体类数据是来自数据库的
- servlet、listener、filter等
引用类型的自动注入
-
byName(按名称注入)
java类中引用类型的属性名和bean的id一样,且数据类型是一致的,这样spring就能够赋值给引用类型。
语法:
<bean id="xx" class="yyy" autowire="byName"> 简单类型属性赋值 </bean>
-
byType(按类型注入)
java类中引用类型的数据类型和bean的class的类型是同源关系时,这样spring就能够赋值给引用类型。
何为同源关系:
- java类中引用类型的数据类型和bean的class值是一样的
- java类中引用类型的数据类型和bean的class值是父子类的关系
- java类中引用类型的数据类型和bean的class值是接口和实现类的关系
语法:autowire=“byType”
注:在byType中,在xml配置文件中声明的bean只能有一个符合条件,否则会报错。
多配置文件的导入
使用标签来导入其他的spring配置文件到主配置文件中。
注解方式DI
使用注解的步骤:
-
加入maven的依赖spring-context,使用注解必须使用spring-aop依赖;
-
在类中使用spring的注解(@xxx)
-
在spring配置文件中,加入一个组件扫描器的标签(component-scan)
component-scan的工作方式:spring会扫描遍历base-package指定的包,把包中和子包中的所有类,找到类中的注解,按照注解的功能创建对象,或给属性赋值。
主要注解
-
@Component:相当于spring配置文件中bean的声明
-
@Repository:用在持久层(DAO)类上面,创建dao对象,dao对象是能访问数据库的
-
@Service:用在业务层上面的,创建service对象,service对象是做业务处理的,可以有事务等功能
-
@Controller:用在控制器上面,创建控制器对象的,控制器对象能够接受用户提交的参数,显示请求的处理结果
以上四个注解的功能是一样的,语法也一样,只不过使用的地方不一样,有额外的功能。
-
@Value:简单类型的属性赋值
- 写在属性定义上面,无需set方法(推荐)
- 写在set方法的上面
-
@Autowired:引用类型的属性赋值
byType和byName都可以,默认是ByType
- 写在属性定义上面,无需set方法(推荐)
- 写在set方法的上面
若要使用byName方式,需@Autowired和@Qualifier搭配使用:
- 在属性上面加@Autowired
- 在属性上面加入@Qualifier(value=“bean的id”)
-
@Resource:引用类型的属性赋值
默认byName赋值,若赋值失败,则byType
到底用配置文件赋值好还是注解赋值好?
经常改动的话使用xml配置文件,不常改的话用注解。注解为主,配置文件为辅。
AOP
面向切面编程,底层就是采用动态代理实现的。
在不改变代码的情况下,增加功能,解耦合。
动态代理
可以在程序的执行过程中,创建代理对象,通过代理对象执行方法,给目标类的方法增加额外的功能(功能增强)。
常用方式有两种:
- JDK动态代理
- CGLIB动态代理(了解)
JDK动态代理
实现步骤:
- 创建目标类
- 创建InvocationHandler接口的实现类,在这个类实现给目标方法增加功能
- 使用JDK中类Proxy,创建代理对象。
//2.创建InvocationHandler接口的实现类
public class MyInvocationHandler implements InvocationHandler {
private Object target;
public MyInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object res = null;
Tool.log();
res = method.invoke(target, args);
Tool.commitTrans();
return res;
}
}
//3.创建代理对象
SomeService target = new SomeServiceImpl();
MyInvocationHandler handler = new MyInvocationHandler(target);
SomeService proxy = (SomeService) Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
handler);
proxy.doSome();
System.out.println("======================");
proxy.doOther();
动态代理的作用:
1)在目标类源代码不改变的情况下,增加功能
2)减少代码的重复
3)专注业务逻辑代码
4)解耦合,让你的业务功能和日志、事务等非业务功能分离
aop总结
就是动态代理的规范化,它把动态代理的实现步骤、方式都定义好了。
aop相关术语
Aspect:切面,表示增强的功能,就是一堆代码,完成某一个功能。非业务方法,常见的切面功能有日志、事务、统计信息、参数检查、权限验证
JoinPoint:连接点,连接业务方法和切面的位置。即某类中的业务方法
Pointcut:切入点,带有通知的连接点
Advice:通知,在特定切入点上执行的增强处理,有before、after、afterReturning、afterThrowing,around。
Aop实现
- spring内部支持aop,但比较笨重。
- aspectJ:一个开源的专门做aop的框架spring框架中已集成aspectJ框架,有两种使用方法,xml配置文件和注解。
execution表达式:
execution(访问权限 方法返回值 方法声明(参数类型) 异常类型)
AspectJ框架实现AOP步骤
@Before案例
-
加入maven依赖,spring-context和spring-aspects
-
创建一个接口和它的实现类,即目标类
-
创建切面类,里面有需要增强的功能代码,功能的执行时间,功能执行的位置
//切面类 @Aspect public class MyAspect { //增强功能的执行位置 @Before(value = "execution(public void com.springstudy.ba01.impl.SomeServiceImpl.doSome(String,Integer))") public void myBefore(){ //增强的功能 System.out.println("前置通知,功能增强,记录方法执行时的时间:"+new Date()); } }
-
在spring的配置文件applicationContext.xml中利用bean创建以上类的对象,并加入自动代理生成器的标签aop:aspectj-autoproxy
-
编写测试类测试代码
public class MyTest01 { @Test public void test01(){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); SomeService proxy = (SomeService) context.getBean("someService"); proxy.doSome("chy",22); }
}
注意:增强的功能方法myBefore()中可以有一个参数JoinPoint,它可以获取方法的完整定义、方法的实参等信息
```java
//切面类
@Aspectpublic class MyAspect {
//增强功能的执行时间
@Before(value = "execution(public void com.springstudy.ba01.impl.SomeServiceImpl.doSome(String,Integer))") public void myBefore(JoinPoint jp){
//获取方法的完整定义
System.out.println("方法定义:"+jp.getSignature());
//获取方法的名称
System.out.println("方法名称:"+jp.getSignature().getName());
//获取方法的实参
Object[] args = jp.getArgs();
for (Object arg : args) {
System.out.println("参数:"+arg); }
//增强的功能
System.out.println("前置通知,功能增强,记录方法执行时的时间:"+new Date());
}
}
@AfterReturning
后置通知,在目标方法之后执行,能获取目标方法的返回值,可以修改这个返回值。属性:
- value:切入点表达式(execution)
- returning:自定义的变量,表示目标方法的返回值,自定义变量名必须和通知方法的形参名一样
@Around
环绕通知,经常用于做事务方面功能。特点:
在目标方法的前后都能增强功能
控制目标方法是否被调用执行
修改原来的目标方法的执行结果,影响最后的调用结果
该方法的定义格式:
- public的
- 必须有一个返回值,推荐使用Object
- 方法有固定参数,为ProceedingJoinPoint
@ AfterThrowing,异常通知
@After,最终通知,类似finally语句
辅助功能的注解@Pointcut,给切入点表达式起别名用的,在某方法上加该注解,则该方法名就是切入点表达式的别名。
@Pointcut(value = "execution(* *..SomeServiceImpl.doFirst(..))")
public void myPointCut(){ //无需写代码 }
现在,通知注解上就只需要写成
@Around(value = "myPointCut()")
即可。
CGLIB动态代理
有接口时,框架默认使用的是JDK动态代理
CGLIB动态代理是在没有接口时使用的,要想在有接口的情况下使用,则需要在spring配置文件中写
<aop:aspectj-autoproxy proxy-target-class="true"/>
Spring的事务处理
什么时候想到要使用事务?
当我的操作,涉及到多个表,或者是多个sql语句的insert、update、delete。需要保证这些语句都是成功的才能完成我的功能,或者都失败,保证操作是符合要求的。
在java代码中控制事务,此时事务应该放在哪里?
写在service类的业务方法上,业务业务方法会调用多个dao方法,执行多条sql语句。
有jdbc、mybatis、hibernate等方式访问数据库,他们的不足之处?
多种数据库的访问技术,有不同的事务处理的机制、对象、方法,对开发人员要求较高。
如何解决以上不足?
Spring提供一种处理事务的统一模型,能使用统一的步骤或方式完成多种不同数据库访问技术的事务处理。
spring是声明式事务:把事务相关的资源和内容都提供给spring,spring就能处理事务提交、回滚。几乎不用写代码。
spring如何处理事务?
有固定的步骤,只需把事务需要的信息提供给spring即可。
事务管理器
事务内部提交、回滚事务,使用事务管理器对象,代替你完成commit、rollback操作。
事务管理器是一个接口和它的众多实现类。
接口:PlatformTransactionManager,定义事务的重要方法commit、rollback
实现类:spring把每一种数据库访问技术对应的事务处理类都创建好了。
mybatis访问数据库:spring创建好的是DataSourceTransactionManager
hibernate访问数据库:spring创建好的是HibernateTransactionManager
怎么使用:在spring的配置文件中声明bean,例如你要使用mybatis访问数据库,bean为:
<bean id="xxx" class="DataSourceTransactionManager">
事务的4个隔离级别(isolation)
MySql默认REPEATABLE_READ;Oracle默认READ_COMMITTED
- READ_UNCOMMITTED:读未提交。未解决任何并发问题
- READ_COMMITTED:读已提交。解决脏读,存在不可重复读与幻读
- REPEATABLE_READ:可重复读。解决脏读、不可重复读,存在幻读
- SERIALIZABLE:串行化。不存在并发问题
事务的超时时间(不用管,默认即可)
表示一个方法最长的执行时间,如果方法执行超过了时间,事务就回滚。单位是秒,整数值,默认-1。
事务的传播行为(propagation)
控制业务方法是不是有事务的,怎样的事务
7个传播行为,表示你的业务方法调用时,事务在方法之间是如何使用的。
PROPAGATION_REQUIRED:支持当前事务,假设当前没有事务,就新建一个事务
PROPAGATION_REQUIRES_NEW:新建事务,假设当前存在事务,把当前事务挂起
PROPAGATION_SUPPORTS:支持当前事务,假设当前没有事务,就以非事务方式运行
以上三个需要掌握
PROPAGATION_MANDATORY
PROPAGATION_NESTED
PROPAGATION_NEVER
PROPAGATION_NOT_SUPPORTED
提交事务、回滚事务的时机
rollback-for:指定的异常类名,发生异常时一定回滚
1)当你的业务方法执行成功,没有异常抛出。当方法执行完毕,spring在方法执行后提交事务,调用事务管理器的commit
2)当你的业务方法抛出运行时异常或ERROR。spring执行回滚操作,调用事务管理器的rollback
3)当你的业务方法抛出非运行时异常,主要是受查异常时,默认提交事务
受查异常:在你写代码中,必须处理的异常。例如IOException,SQLException。
总结:你要告诉spring,你的项目中类信息,方法的名称,方法的事务传播行为。
spring框架中提供的事务处理方案
-
注解方案:适合中小型项目使用
spring框架使用AOP实现给业务方法增加事务功能,使用@Transactional注解增加事务
@Transactional注解是属于spring框架,放在public方法上面,表示当前方法具有事务
可以给注解的属性赋值,表示具体的隔离级别、传播行为、异常信息等等
@Transactional使用步骤:
1)声明事务管理器对象,指定数据源
<!--声明事务管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!--连接的数据库,指定数据源--> <property name="dataSource" ref="myDataSource"/> </bean>
2)开启事务注解驱动,告诉spring我要使用注解的方式管理事务
<!--transaction-manager:事务管理器对象的id--> <tx:annotation-driven transaction-manager="transactionManager"/>
spring使用AOP机制,创建@Transactional所在的类代理对象,给方法加入事务功能:在你的业务方法执行之前,先开启事务,在业务方法执行之后提交或回滚事务,使用的是AOP的环绕通知
3)在业务方法上加入@Transactional
@Transactional( propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, readOnly = false, rollbackFor = { NullPointerException.class,NotEnoughException.class } ) @Override public void buy(Integer goodsId, Integer nums) { }
或者只加上@Transactional即可
-
使用AspectJ框架方案:大型项目使用
在大型项目中,有很多的类、方法,需要大量的配置事务,使用AspectJ框架功能,在spring配置文件中声明类、方法需要的事务。这种方式的业务方法和事务配置完全分离。
实现步骤:(都是在xml配置文件中实现的)
1)加入spring-aspects依赖
2)声明事务管理器对象
3)声明方法需要的事务类型(配置方法的事务属性【隔离级别、传播行为、超时等】)
<tx:advice id="myAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="buy" propagation="REQUIRED" isolation="DEFAULT" rollback-for="java.lang.NullPointerException,com.chenhanyuan.excep.NotEnoughException"/> </tx:attributes> </tx:advice>
若有多个方法,可以使用通配符*指定
4)配置AOP:指定哪些类要创建代理
<!--AOP的配置--> <aop:config> <!--配置切入点表达式:指定哪些类要使用事务,aspectj会创建代理对象--> <aop:pointcut id="servicePC" expression="execution(* *..service..*.*(..))"/> <!--配置增强器,关联advice和pointcut advice-ref:通知,上面tx:advice的配置 pointcut-ref:切入点表达式的id--> <aop:advisor advice-ref="myAdvice" pointcut-ref="servicePC"/> </aop:config>
aspectj方式是固定格式的,可当作模板使用,不用记。
Spring Web项目
配置监听器:目的是创建spring容器对象,就能把spring配置文件中的所有对象都创建好,用户发起请求就可以直接使用对象了。不用每次都读取配置文件,重新创建容器的对象,减少了内存的开销,提高程序运行速度。