文章目录
spring简介
spring框架诞生初衷是让原有技术使用起来更方便,不重复造轮子,主要表现在解耦,提高程序的扩展性和可维护性。
技术思想
Inversion of Control
IOC——Inversion of Control(控制反转):是spring的一个核心思想——由spring容易控制对象的生命周期,不在由程序员在代码中new一个个的对象。
Dependency Injection
DI——Dependency Injection(依赖注入):spring在读取xml配置文件后主动创建某个对象A,由于对象A中的某个属性是另外一个对象B,那么spring容器会将先创建对象B然后注入到对象A的属性中,完成对象A的创建。这个过程称为依赖注入。
Aspect Oriented Programming
AOP——面向切面编程(Aspect Oriented Programming),程序在执行的时候是从上到下线性的,如果由于业务需求,我们需要在原有程序上的某个方法A的前面加上某段逻辑(方法B),后面加上某段逻辑(方法C),原来方法是修改源代码,但是spring提供了一种更方便的方式——通过在xml文件中把方法A配置为PointCut(切点)
,为切点配置Advice(通知)
:前置通知——方法B,后置通知——方法C,以达到在不修改原有程序上,扩展程序。
声明式事务
与由程序员在代码中写commit,rollback这种事务控制代码的编程式事务不同,spring中声明式事务的事务控制代码都已经封装好了,程序员只需要声明出哪些方法需要进行事务控制和如何进行事务控制.通过aop的思想来控制事务管理。
环境搭建
Maven项目
<!--版本管理-->
<properties>
<spring.version>4.3.14.RELEASE</spring.version>
</properties>
<!--Spring-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
开发配置
XML文件配置
bean
bean标签是spring的基础标签,定义需要交给spring管理的类。
给bean对象的属性注入值
- 通过构造方法设置值:
<?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-4.2.xsd">
<!--
构造方法创建对象
class属性指向要管理的bean对象的全路径
id属性代表此对象(bean)的唯一标识
-->
<bean id="peo" class="com.spring.pojo.People">
<!-- 指定构造方法(默认是无参构造)
index:构造方法的参数索引
type:构造方法的参数类型
name:构造方法的参数名称
value:构造方法的参数值————基础数据类型或者String
ref:构造方法的参数值————关联的对象(填bean的id)
-->
<constructor-arg index="0" type="int" name="id" value="1"/>
<constructor-arg index="1" type="java.lang.String" name="name" value="haoze"/>
</bean>
<!--实例工厂创建对象-->
<bean id="peoFactory" class="com.spring.pojo.PeopleFactory"/>
<bean id="peoImpl" factory-bean="peoFactory" factory-method="newPeoImpl"/>
<!--静态工厂创建对象-->
<bean id="peoImpl2" class="com.spring.pojo.PeopleStacticFactory" factory-method="newPeoImpl"/>
</beans>
- 设值注入(通过 set 方法)
- 如果属性是基本数据类型或 String 等简单数据类型
<bean id="peo" class="com.bjsxt.pojo.People">
<property name="id" value="222"></property>
<property name="name" value=" 张三 "></property>
</bean>
<!--等效于-->
<bean id="peo" class="com.bjsxt.pojo.People">
<property name="id">
<value>456</value>
</property>
<property name="name">
<value>zhangsan</value>
</property>
</bean>
- 如果属性是 Set<?>
<property name="sets">
<set>
<value>1</value>
<value>2</value>
<value>3</value>
<value>4</value>
</set>
</property>
- 如果属性是 List<?>
<property name="list">
<list>
<value>1</value>
<value>2</value>
<value>3</value>
</list>
</property>
<!--如果 list 中就只有一个值-->
<property name="list" value="1">
</property>
- 如果属性是数组(只有一个值,可以直接通过 value 属性赋值)
<property name="strs" >
<array>
<value>1</value>
<value>2</value>
<value>3</value>
</array>
</property>
- 如果属性是 map
<property name="map">
<map>
<entry key="a" value="b" ></entry>
<entry key="c" value="d" ></entry>
</map>
</property>
- 如果属性 Properties 类型
<property name="demo">
<props>
<prop key="key">value</prop>
<prop key="key1">value1</prop>
</props>
</property>
- 如果属性是对象
<property name="demo">
<!--peo是另外一个bean的id-->
<property name="a" ref="peo"/>
</property>
aop:config
前面介绍了AOP的原理,而在spring中就是通过配置定义切点(pointcut)和需要用到的前置通知(before advice)或者后置通知(after advice)。
Schema-based方式配置
我们要在对下面这个类进行扩展,在执行doSth()之前加一段逻辑,之后加一段逻辑。
package com.spring.aop;
public class PointCut {
public void doSth(){
System.out.println("切点出现");
}
}
创建需要增加的逻辑——通知类:
- 前置通知类需要实现
MethodBeforeAdvice
package com.spring.aop;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
public class MyBeforeAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
// method 是切点方法对象
//objects 是切点方法参数
//o 是切点方法对应的类对象
System.out.println("前置通知");
}
}
- 后置通知类需要实现
AfterReturningAdvice
package com.spring.aop;
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;
public class MyAfterAdvice implements AfterReturningAdvice {
@Override
public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
// method 是切点方法对象
//objects 是切点方法参数
//o 是切点方法返回值
//o1 是切点对应的类对象
System.out.println("后置通知");
}
}
- 异常通知类需要实现
throwsAdvice
(没有需要实现的方法,想通过scheme-base的方式实现必须afterThrowing方法)
package com.spring.aop;
import org.springframework.aop.ThrowsAdvice;
import java.lang.reflect.Method;
public class ExceptionThrowAdvice implements ThrowsAdvice {
public void afterThrowing(Exception ex) throws Throwable {
System.out.println("执行出错了哦,错误正在处理:"+ex.getMessage());
}
//另一种可以获取切点方法的afterThrowing方法
// public void afterThrowing(Method m, Object[] args, Object target, Exception ex) {
// System.out.println("执行出错了哦,错误正在处理:"+ex.getMessage());
// }
}
- 环绕通知类:前置通知和后置通知组合在一起
实现MethodInterceptor
package com.spring.aop;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class AroundAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
System.out.println("环绕-前置");
Object result = methodInvocation.proceed();//放行,调用切点方式
System.out.println("环绕-后置");
return result;
}
}
配置spring的配置文件,首先添加 xmlns:aop命名空间
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd">
<!--切点类-->
<bean id="pointcut" class="com.spring.aop.PointCut"></bean>
<!--前置通知类-->
<bean id="mybefore" class="com.spring.aop.MyBeforeAdvice"></bean>
<!--后置通知类-->
<bean id="myafter" class="com.spring.aop.MyAfterAdvice"></bean>
<!--异常通知类-->
<bean id="exceptionadvice" class="com.spring.aop.ExceptionThrowAdvice"></bean>
<!--环绕通知类-->
<bean id="aroundadvice" class="com.spring.aop.AroundAdvice"></bean>
<aop:config>
<!--
配置切面和对应通知的关系
expression是固定格式:execution(* *..*ServiceImpl.*(..))
* 通配符,匹配任意方法名,任意类名,任意一级包名
第一个*号: 表示返回类型,*号表示所有的类型。
*(..): 匹配任意参数方法
方法如果是多参数的格式:com.spring.aop.PointCut.doSth(String,int) and args(name,id)
-->
<aop:pointcut expression="execution(* com.spring.aop.PointCut.doSth())" id="mypoint"/>
<aop:advisor advice-ref="mybefore" pointcut-ref="mypoint"/>
<aop:advisor advice-ref="myafter" pointcut-ref="mypoint"/>
<aop:advisor advice-ref="exceptionadvice" pointcut-ref="mypoint"/>
<aop:advisor advice-ref="aroundadvice" pointcut-ref="mypoint"/>
</aop:config>
</beans>
测试结果
从结果上来看,环绕通知和前置通知,后置通知的执行顺序。
AspectJ方式配置
相比于Schema-based方式,AspectJ方式在XML文件中声明了通知类的类型,因此无需实现通知类接口。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd">
<!--通知类-->
<bean id="advice" class="com.spring.aop.MyAdvice"/>
<aop:config>
<!--aspectj配置异常通知 此配置只作用于切点方法,前置后置通知方法出错不会触发此方法-->
<aop:aspect ref="advice"><!--ref指向对应的异常通知bean-->
<aop:pointcut expression="execution(* com.spring.aop.PointCut.doSth())" id="mypoint"/>
<!-- 各个标签的执行顺序
try{
try{
//@Before
method.invoke(..);
}finally{
//@After
}
//@AfterReturning
}catch(){
//@AfterThrowing
}
-->
<aop:before method="myBefore" pointcut-ref="mypoint"/>
<!--
throwing 必须与异常通知类对应方法的参数名相同
method 调用异常通知中的指定方法
pointcut-ref 给指定切点方法配置此异常通知
-->
<aop:after-throwing method="myexception" pointcut-ref="mypoint" throwing="e"/>
<aop:after-returning method="myAfterReturn" pointcut-ref="mypoint"/>
<aop:after method="myAfter" pointcut-ref="mypoint"/>
<aop:around method="myAround" pointcut-ref="mypoint"/>
</aop:aspect>
</aop:config>
</beans>
对应的通知类
package com.spring.aop;
import org.aspectj.lang.ProceedingJoinPoint;
public class MyAdvice {
public void myBefore(){
System.out.println("AspectJ配置前置通知");
}
//对应<aop:after>标签,切点方法出错依然会执行
public void myAfter(){
System.out.println("AspectJ配置after通知");
}
//对应aop:after-returning标签 若切点方法出错不会执行
public void myAfterReturn(){
System.out.println("AspectJ配置after-returning通知");
}
//对应<aop:aspect>标签自定义配置异常通知
public void myexception(Exception e){
System.out.println("AspectJ配置错误正在处理:"+e.getMessage());
}
//对应<aop:around>标签自定义配置环绕通知
public Object myAround(ProceedingJoinPoint p) throws Throwable{
System.out.println("AspectJ配置环绕-前置");
Object result = p.proceed();
System.out.println("AspectJ配置环绕后置");
return result;
}
}
注解方式配置(基于AspectJ)
- xml文件配置:
- 首先xml文件引入 xmlns:context命名空间
- 设置注解扫描包
- 开启cglib动态代理
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd">
<!--扫描存在注解的包 -->
<context:component-scan base-package="*.*.aop" />
<!--
true: 开启cglib动态代理
false: 使用jdk动态代理
-->
<aop:aspectj-autoproxy proxy-target-class="true"/>
</beans>
- 切点类注解
@Component
1.1 相当于<bean/>
1.2 如果没有参数,把类名首字母变小写,相当于<bean id=””/>
1.3 @Component(“自定义名称”)
1.4 方法上添加@Pointcut(“”) 定义切点方法
package com.spring.aop;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
//Component 相当于一个bean标签,若没有参数直接将类名称首字母小写为bean的ID
@Component
public class AnnotionPointCut {
@Pointcut("execution(* com.spring.aop.AnnotionPointCut.doSth())")
public void doSth(){
System.out.println("注解切点方法");
}
}
- 通知类注解
@Aspect 相当于<aop:aspect/>
表示通知方法在当前类中
package com.spring.aop;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class AnnotionAdvice {
@Before("com.spring.aop.AnnotionPointCut.doSth()")
public void myBefore(){
System.out.println("注解配置前置通知");
}
@After("com.spring.aop.AnnotionPointCut.doSth()")
public void myafter(){
System.out.println("后置通知");
}
@AfterThrowing("com.spring.aop.AnnotionPointCut.doSth()")
public void mythrow(){
System.out.println("异常通知");
}
@Around("com.spring.aop.AnnotionPointCut.doSth()")
public Object myarround(ProceedingJoinPoint p) throws
Throwable{
System.out.println("环绕-前置");
Object result = p.proceed();
System.out.println("环绕-后置");
return result;
}
}
tx:advice
- 引入xmlns:tx命名空间
- 配置XML文件
<!--事务管理器:spring-jdbc.jar 中 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--连接数据库-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置声明式事务-->
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<!--需要进行事务管理的方法,支持通配符-->
<tx:method name="ins*" propagation="REQUIRED" isolation="DEFAULT"/>
<tx:method name="*" read-only="true"/>
</tx:attributes>
</tx:advice>
<!--通过aop的通知来设置声明式事务-->
<aop:config>
<!-- 切点范围设置大一些 -->
<aop:pointcut expression="execution(* com.ssm.service.impl.*.*(..))" id="mypoint" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="mypoint" />
</aop:config>
属性名 | 属性值 | 作用 |
rollback-for | 异常类 | 当出现什么异常时需要进行回滚 |
no-rollback-for | 异常类 | 当出现什么异常时不滚回事务 |
read-only | true | 此事务为只读事务 |
false(默认值) | 需要提交的事务 | |
propagation | REQUIRED (默认值) | 如果当前有事务,就在事务中执行,如果当前没有事务,新建一个事务. |
SUPPORTS | 如果当前有事务就在事务中执行,如果当前没有事务,就在非事务状态下执行 | |
MANDATORY | 必须在事务内部执行,如果当前有事务,就在事务中执行,如果没有事务,报错 | |
REQUIRES_NEW | 必须在事务中执行,如果当前没有事务,新建事务,如果当前有事务,把当前事务挂起 | |
NOT_SUPPORTED | 必须在非事务下执行,如果当前没有事务,正常执行,如果当前有事务,把当前事务挂起 | |
NEVER | 必须在非事务状态下执行,如果当前没有事务,正常执行,如果当前有事务,报错 | |
NESTED | 必须在事务状态下执行.如果没有事务,新建事务,如果当前有事务,创建一个嵌套事务 | |
isolation | DEFAULT(默认值) | 由底层数据库自动判断应该使用什么隔离界别 |
READ_UNCOMMITTED | 可以读取未提交数据,可能出现脏读,不重复读,幻读. 效率最高 | |
READ_COMMITTED | 只能读取其他事务已提交数据.可以防止脏读,可能出现不可重复读和幻读. | |
REPEATABLE_READ | 读取的数据被添加锁,防止其他事务修改此数据,可以防止不可重复读.脏读,可能出现幻读 | |
SERIALIZABLE | 排队操作,对整个表添加锁.一个事务在操作数据时,另一个事务等待事务操作完成后才能操作这个表,安全,效率低 |
脏读:
多线程下一个事务(A)读取到另一个事务(B)中未提交的数据,此时A事务读取的数据可能和数据库中数据是不一致的,此时认为数据是脏数据,读取脏数据过程叫做脏读。
不可重复读:
当事务A第一次读取事务后,事务B对事务A读取的数据进行修改,事务 A 中再次读取的数据和之前读取的数据不一致,过程不可重复读。
幻读:
事务A按照特定条件查询出结果,事务B新增或者删除了一条符合条件的数据事务 A 中查询的数据量和数据库中的数据量不一致的,这种情况称为幻读。
context:component-scan
此标签用于指定spring容器扫描存在注解的类,属性base-package指定类路径,一般直接配置包路径,就扫描包下所有类,支持通配符配置。
<context:component-scan base-package="com.ssm.user.service.impl"/>
<!--"*" 标示一层包的通配,如:com.aa|com.bb-->
<context:component-scan base-package="com.*" />
<!-- "*" 标示一层包的通配,如:com.aa.dao|com.bb.dao-->
<context:component-scan base-package="com.*.dao" />
<!-- "**" 标示不确定层包通配,如:com.aa.dao|com.aa.aa1.dao -->
<context:component-scan base-package="com.**.dao" />
<!-- "*dao" 以dao结尾的包通配,如:com.adao|com.bdao-->
<context:component-scan base-package="com.*dao" />
context:property-placeholder
此标签用于引入资源文件,可加载多个资源文件,使用,分隔
<context:property-placeholder location="classpath:db.properties"/>
<!--可在配置文件调用资源文件内容-->
<bean id="dataSouce" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
资源文件如下
jdbc.username=root
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/zee
jdbc.password=root
注解配置
对应bean标签
注解 | 用处 |
---|---|
@Component | 写在类上 |
@Service | 写在 ServiceImpl 类上. |
@Repository | 写在数据访问层类上 |
@Controller | 写在控制器类上 |
成员属性
注解 | 用处 |
---|---|
@Resource | 写在成员属性上之后,不需要写对象的 get/set,java 中的注解默认按照 byName 注入,如果没有名称对象,按照 byType 注入 建议把对象名称和 spring 容器中对象名相同 |
@Autowired | 写在成员属性上之后,不需要写对象的 get/set,spring 的注解,默认按照 byType 注入. |
@Value | 获取 properties 文件中内容赋值给注解标注的对象 |
AOP相关
注解 | 用处 |
---|---|
@Pointcut | 定义切点 |
@Aspect | 定义切面类 |
@Before | 前置通知 |
@Pointcut | 后置通知 |
@After | 定义切点 |
@AfterReturning | 后置通知,必须切点正确执行 |
@AfterThrowing | 异常通知 |
@Arround | 环绕通知 |
使用方式
package com.spring;
import com.spring.pojo.People;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class HelloSpring {
public static void main(String[] args) {
//若XML文件路径不在根路径下要填写相对路径
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
People peo = ac.getBean("peo", People.class);
System.out.println(peo);
}
}