Spring
此文章部分引用java3y的《三歪教你Spring》;
简介:
Spring框架是一个开放源代码的J2EE应用程序框架,由Rod Johnson发起,是针对bean的生命周期进行管理的轻量级容器(lightweight container)。 Spring解决了开发者在J2EE开发中遇到的许多常见的问题,提供了功能强大IOC、AOP及Web MVC等功能。Spring可以单独应用于构筑应用程序,也可以和Struts、Webwork、Tapestry等众多Web框架组合使用,并且可以与 Swing等桌面应用程序AP组合。因此, Spring不仅仅能应用于JEE应用程序之中,也可以应用于桌面应用程序以及小应用程序之中。Spring框架主要由七部分组成,分别是 Spring Core、 Spring AOP、 Spring ORM、 Spring DAO、Spring Context、 Spring Web和 Spring Web MVC。
Spring 框架中重要概念
- 容器(Container): Spring 当作一个大容器.
- BeanFactory 接口.老版本.
- 新版本中 ApplicationContext 接口,是 BeanFactory 子接 口.BeanFactory 的功能在 ApplicationContext 中都有.
从 Spring3 开始把 Spring 框架的功能拆分成多个 jar. Spring2 及以前就一个 jar
官方包目录介绍:
-
test: spring 提供测试功能
-
Core Container:核心容器.Spring 启动最基本的条件.
-
Beans : Spring 负责创建类对象并管理对象
-
Core: 核心类
-
Context: 上下文参数.获取外部资源或这管理注解等
-
SpEl: expression.jar
-
AOP: 实现 aop 功能需要依赖
-
Aspects: 切面 AOP 依赖的包
-
Data Access/Integration : spring 封装数据访问层相关内容
-
JDBC : Spring 对 JDBC 封装后的代码.
-
ORM: 封装了持久层框架的代码.例如 Hibernate
-
transactions:对应 spring-tx.jar,声明式事务使用.
-
web:需要 spring 完成 web 相关功能时需要.
- 例如:由 tomcat 加载 spring 配置文件时需要有 spring-web包
环境搭建:
导入四大核心包和一个日志包:
- logging
- beans
- context
- core
- expression
Maven直接导入坐标就行。
在resources下新建xml: applicationContext.xml,spring的配置文件可以叫任意名字,推荐叫applicationContext
Spring 容器 ApplicationContext,applicationContext.xml 配置的信息最终存储到了 AppliationContext 容器中;
idea创建的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">
</beans>
Spring核心功能
IoC/DI 控制反转/依赖注入:
通过spring方式获取对象
配置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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="employee" class="top.young.domain.Employee"/>
</beans>
获取employee对象:
@Test
public void test01() throws Exception{
ApplicationContext ac = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
Employee employee = ac.getBean("employee", Employee.class);
System.out.println(employee);
}
运行结果:
经测试,可以正常获取对象。
bean的配置方式:
这里来归纳一下Bean的配置方式:
1-无参构造配置方式:
<bean id="person" class="cn.test._03_configuration_way.Person"/>
2-静态工厂创建对象:
通过factory-method属性来指定方法名称,这个方法必须是静态方法,因为静态方法可以直接使用 类名称.方法名称() 调用,返回Person对象
<bean id="factory" class="cn.test._03_configuration_way.static_factory.StaticFactory"
factory-method="createPerson"/>
3-实例工厂创建对象
先配置一个工厂bean 因为它不是静态方法 所以必须要先创建工厂bean对象才能调用方法,再配置一个Person的bean,注意:第二个bean标签不要写class属性了
<bean id="factory"class="cn.test.instance_factory.instance_factory"/>
<bean id="person" factory-method="createFactory" factory-bean="factory"/>
4-FactoryBean创建对象
配置方式与默认使用无参构造创建bean对象没区别,区别就在于这个类实现了FactoryBean接口,那么Spring在初始化的时候就会自动去调用getObject方法来创建对象
以上是通过无参构造创建的对象,如果一个类有成员属性,那么怎么给属性赋值呢
spring的注入方式:
1-set注入
<bean id="employeeDao" class="cn.itsource._01_di_set.EmployeeDao"/>
<bean id="employeeService" class="cn.itsource._01_di_set.EmployeeService">
<property name="dao" ref="employeeDao"/>
</bean>
2-constructor注入
2.1-constructor参数名注入
<bean id="employeeDao" class="cn.itsource._02_constructor.byname.EmployeeDao"/>
<bean id="employeeService" class="cn.itsource._02_constructor.byname.EmployeeService">
<constructor-arg name="dao" ref="employeeDao"/>
</bean>
2.2-constructor参数顺序注入
<bean id="employeeDao" class="cn.itsource._02_constructor.byindex.EmployeeDao"/>
<bean id="employeeService" class="cn.itsource._02_constructor.byindex.EmployeeService">
<constructor-arg index="0" ref="employeeDao"/>
</bean>
2.3-constructor参数类型注入
根据参数类型注入,如果有多个想同类型的参数,那么还需要注意标签的顺序。
<bean id="employeeDao" class="cn.itsource._02_constructor.bytype.EmployeeDao"/>
<bean id="employeeService" class="cn.itsource._02_constructor.bytype.EmployeeService">
<constructor-arg
type="cn.itsource._02_constructor.bytype.EmployeeDao"
ref="employeeDao"/>
</bean>
3-内部bean和外部bean
<!--内部-->
<bean id="employeeService"class="cn.itsource._03_inner_outer_bean.inner.EmployeeService">
<property name="dao">
<bean class="cn.itsource._03_inner_outer_bean.inner.EmployeeDao"/>
</property>
</bean>
<!--外部-->
<bean id="employeeDao" class="cn.itsource._03_inner_outer_bean.outer.EmployeeDao"/>
<bean id="employeeService"class="cn.itsource._03_inner_outer_bean.outer.EmployeeService">
<property name="dao" ref="employeeDao"/>
</bean>
4-注入数组
array标签注入
<!-- 数组-->
<property name="arrays">
<array>
<value>张三</value>
<value>李四</value>
<value>王二</value>
</array>
</property>
property value注入(不推荐)
<property name="arrays" value="张三,李四,王二"/>
5-注入List
list标签注入
<!-- list-->
<property name="list">
<list>
<value>孙权</value>
<value>曹操</value>
<value>刘备</value>
</list>
</property>
6-注入Set
<!-- set-->
<property name="set">
<set>
<value>猴子</value>
<value>猴子</value>
<value>赵信</value>
</set>
</property>
7-注入Map
普通类型写法:
其他类型写法:
使用内部bean:
<!-- map-->
<property name="map">
<map>
<entry key="mapkey" value="obj"></entry>
<entry key="mapkey2" value-ref="otherBean"></entry>
<entry key="mapke3" value="123"></entry>
<entry key="mp3">
<ref bean="otherBean"/>
</entry>
</map>
</property>
8-注入property
8.1-prop标签
<!-- property-props-->
<property name="props1">
<props>
<prop key="driverClassName">com.mysql.jdbc.Dirver</prop>
<prop key="url">jdbc:mysql://localhost:3306/test</prop>
<prop key="username">root</prop>
<prop key="password">123456</prop>
</props>
</property>
8.2-value标签
<!-- property-value-->
<property name="props2">
<value>
##注释
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/test
username=root
password=123456
</value>
</property>
面向切面编程Spring-AOP
在学习AOP之前,我们要先学习一个设计模式,代理模式。
1.代理模式:
1-1.代理模式简介:
代理模式,提供了间接对目标对象进行访问的方式;即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的功能上,增加额外的功能补充,即扩展目标对象的功能。—引用自互联网
1-2.静态代理
使用明星和经纪人举例,你想找一个明星来唱歌,你不能直接找明星谈,你需要找他的经纪人,首先签合同,然后明星过来唱歌,最后经纪人来收钱。
/**
* @author young
* @date 2020/4/18
*
* 由真实对象抽象出来的接口。
*/
public interface Star {
/**
* 签合同
*/
void contract();
/**
* 唱歌
*/
void singing();
/**
* 收钱
*/
void collectMoney();
}
-------------------------------------------------------------------------------------
/**
* @author young
* @date 2020/4/18
*
* 经纪人
*/
public class AgentStar implements Star {
private Star star;
private String name;
public AgentStar(Star star, String name) {
this.star = star;
this.name = name;
}
@Override
public void contract() {
System.out.println(this.name+"签合同");
}
@Override
public void singing() {
this.star.singing();
}
@Override
public void collectMoney() {
System.out.println(this.name+"收钱");
}
}
--------------------------------------------------------------------------------------
/**
* @author young
* @date 2020/4/18
*
* 明星本人
*/
public class RealStart implements Star {
private String name;
public RealStart(String name) {
this.name = name;
}
@Override
public void contract() {
System.out.println(this.name+"签合同");
}
@Override
public void singing() {
System.out.println(this.name+"唱歌");
}
@Override
public void collectMoney() {
System.out.println(this.name+"收钱");
}
}
-----------------------------------------------------------------------------------------
/**
* @author young
* @date 2020/4/18
* 单元测试
*/
public class Test05 {
@Test
public void test() throws Exception{
//明星本人
Star realStart=new RealStart("周伦杰");
//明星经纪人
Star agentStar=new AgentStar(realStart,"宋喆");
//你联系经纪人,签合同
agentStar.contract();
//联系经纪人,约时间唱歌
agentStar.singing();
//经纪人来收钱
agentStar.collectMoney();
}
}
----------------------------------------------------------------------------------------
运行结果
1-3.动态代理
此处使用JDK动态代理
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* @author young
* @date 2020/4/18
*
*
* jdk动态代理需要实现InvocationHandler接口
*/
public class StarInvocationHandler implements InvocationHandler {
/**
* 真实对象
*/
private Star star;
//经纪人对象。
private Star agent;
public StarInvocationHandler(Star star, Star agent) {
this.star = star;
this.agent = agent;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result=null;
//如果是唱歌就让明星来唱歌,否则都教给经纪人做
if(method.getName().equals("singing")){
return method.invoke(star, args);
}else {
return method.invoke(agent, args);
}
}
}
通过以上案例,已经基本了解了代理模式的设计思想,接下来就该学习AOP了。
2.面向切面编程Spring-AOP
2-1.简介
AOP的全称是 Aspect-Oriented Programming,即面向切面编程(也称面向方面编程)。目前是一种比较成熟的编程方式。
在传统的业务处理代码中,经常会有事务处理,日志处理等与核心业务无关的代码,同样的代码会分散到各个方法中。这样如果想要关闭某个功能,或者对其进行修改,就必须要修改所有的相关方法。这违反了程序设计的开闭原则 。
2-2.四种通知:
@before 前置通知,执行目标方法之前执行。
@after 后置通知,执行目标方法之后执行
@after-throwing 异常通知,执行目标方法过程中出现异常则执行此通知。
@after-returning 返回后通知,和@after的区别是此通知出异常不执行
2-3.execution表达式
execution用来匹配需要在哪些类的哪些方法上添加通知。
语法解析
那么它的语法是这样⼦的:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-
pattern(param-pattern) throws-pattern?)符号讲解:
- ?号代表0或1,可以不写
- “*”号代表任意类型,0或多
- ⽅法参数为…表示为可变参数
参数讲解:- modifiers-pattern?【修饰的类型,可以不写】
- ret-type-pattern【⽅法返回值类型,必写】
- declaring-type-pattern?【⽅法声明的类型,可以不写】
- name-pattern(param-pattern)【要匹配的名称,括号⾥⾯是⽅法的参数】
- throws-pattern?【⽅法抛出的异常类型,可以不写】
2-4.XML实现AOP:
2-4.1.使用四大通知
此处模拟的三层架构中的service层,用一个类TransactionManager来模拟事务。
/**
* @author young
* @date 2020/4/18
* 此处隐藏service的接口。
*/
@Service
public class UserServiceImpl implements IUserService {
@Override
public void test() {
System.out.println("执行方法");
}
}
模拟事务类:
/**
* @author young
* @date 2020/4/18
*
* 模拟事务
*/
public class TransactionManager {
public void begin() {
System.out.println("开启事务");
}
public void commit() {
System.out.println("提交事务");
}
public void rollback() {
System.out.println("回滚事务");
}
public void close() {
System.out.println("关闭资源");
}
}
配置文件:
<!-- 配置bean-->
<bean id="manager" class="cn.itsource._07_springaop_xml.TransactionManager"/>
<!-- 扫描包-->
<context:component-scan base-package="cn.itsource._07_springaop_xml.service"/>
<aop:config>
<aop:pointcut id="point" expression="execution(* cn.itsource._07_springaop_xml.service.*.*(..))"/>
<aop:aspect ref="manager">
<!--前置通知-->
<aop:before method="begin" pointcut-ref="point"/>
<!--由于出异常不执行此通知,此通知可以用来做事务提交-->
<aop:after-returning method="commit" pointcut-ref="point"/>
<!--异常通知-->
<aop:after-throwing method="rollback" pointcut-ref="point"/>
<!--后置置通知-->
<aop:after method="close" pointcut-ref="point"/>
</aop:aspect>
</aop:config>
测试类:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:cn/itsource/_07_springaop_xml/ApplicationContext.xml")
public class Test07 {
@Autowired
private IUserService service;
@Test
public void test() throws Exception{
service.test();
}
}
运行结果:
正常运行情况下,异常通知不会执行。
下面将UserServiceImpl类修改为:
@Service
public class UserServiceImpl implements IUserService {
@Override
public void test() {
System.out.println("执行方法");
int i=1/0;
}
}
运行结果:
可以看到,当出了异常的时候,spring会自动调用异常通知。
2-4.2.使用环绕通知
将模拟事务类修改为
/**
* @author young
* @date 2020/4/18
*/
public class TransactionManager {
public void begin() {
System.out.println("开启事务");
}
public void commit() {
System.out.println("提交事务");
}
public void rollback() {
System.out.println("回滚事务");
}
public void close() {
System.out.println("关闭资源");
}
public void around(ProceedingJoinPoint point) {
try {
//开启事务
this.begin();
//执行目标方法
point.proceed();
//提交事务
this.commit();
} catch (Throwable throwable) {
//回滚事务
this.rollback();
} finally {
//关闭资源
this.close();
}
}
}
xml修改为:
<!-- 配置bean-->
<bean id="manager" class="cn.itsource._07_springaop_xml.TransactionManager"/>
<!-- 扫描包-->
<context:component-scan base-package="cn.itsource._07_springaop_xml.service"/>
<aop:config>
<aop:pointcut id="point" expression="execution(* cn.itsource._07_springaop_xml.service.*.*(..))"/>
<aop:aspect ref="manager">
<aop:around method="around" pointcut-ref="point"/>
</aop:aspect>
</aop:config>
正常执行结果:
异常执行结果:
可以看到,环绕通知也可以实现相同的效果。
以上基本了解了spring的AOP功能,接下来我们看一下Spring的事务管理
3.事务管理:
3-1。简介:
事务(Transaction),一般是指要做的或所做的事情。在计算机术语中是指访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。事务通常由高级数据库操纵语言或编程语言(如SQL,C++或Java)书写的用户程序的执行所引起,并用形如begin transaction和end transaction语句(或函数调用)来界定。事务由事务开始(begin transaction)和事务结束(end transaction)之间执行的全体操作组成。
关于事务可以看之前的JPA的文章里面有详细介绍,此处只写Spring事务的用法
3-2.编程式事务与声明式事务:
3-2.1编程式事务
-
⾃⼰⼿动控制事务,就叫做编程式事务控制。
-
Jdbc代码:Conn.setAutoCommite(false); // 设置⼿动控制事务
-
JPA代码:entityManager.getTransaction().begin()
-
特点:细粒度的事务控制: 可以对指定的⽅法、指定的⽅法的某⼏⾏添加事务控制(⽐较灵活,但开发起来⽐较繁琐: 每次都要开启、提交、回滚.)
3-2.2声明式事务:
-
事务控制代码已经由 spring 写好.程序员只需要声明出哪些方法需要进行事务控制和如何进行事务控制.
-
声明式事务都是针对于 ServiceImpl 类下方法的
-
事务管理器基于通知(advice)的.
3-3在spring 配置文件中配置声明式事务
3-3.1:声明式事务中属性解释
-
name=”” 哪些方法需要有事务控制
- 支持*通配符
-
readonly=”boolean” 是否是只读事务.
- 如果为 true,告诉数据库此事务为只读事务.数据化优化,会对性能有一定提升,所以只要是查询的方法,建议使用此数据.
- 如果为 false(默认值),事务需要提交的事务.建议新增,删除,修改.
-
propagation 控制事务传播行为.
- 当一个具有事务控制的方法被另一个有事务控制的方法调用后,需要如何管理事务(新建事务?在事务中执行?把事务挂起?报异常?)
- REQUIRED (默认值): 如果当前有事务,就在事务中执行,如果当前没有事务,新建一个事务.
- SUPPORTS:如果当前有事务就在事务中执行,如果当前没有事务,就在非事务状态下执行.
- MANDATORY:必须在事务内部执行,如果当前有事务,就在事务中执行,如果没有事务,报错.
- REQUIRES_NEW:必须在事务中执行,如果当前没有事务,新建事务,如果当前有事务,把当前事务挂起.
- NOT_SUPPORTED:必须在非事务下执行,如果当前没有事务,正常执行,如果当前有事务,把当前事务挂起.
- NEVER:必须在非事务状态下执行,如果当前没有事务,正常执行, 如果当前有事务,报错.
- NESTED:必须在事务状态下执行.如果没有事务,新建事务,如果当前有事务,创建一个嵌套事务.
-
isolation=”” 事务隔离级别
在多线程或并发访问下如何保证访问到的数据具有完整性的.
- DEFAULT: 默认值,由底层数据库自动判断应该使用什么隔离界别
- READ_UNCOMMITTED: 可以读取未提交数据,可能出现脏读,不重复读,幻读.
- 效率最高.
- READ_COMMITTED:只能读取其他事务已提交数据.可以防止脏读,可能出现不可重复读和幻读.
- REPEATABLE_READ: 读取的数据被添加锁,防止其他事务修改此数据,可以防止不可重复读.脏读,可能出现幻读.
- SERIALIZABLE: 排队操作,对整个表添加锁.一个事务在操作数据时,另一个事务等待事务操作完成后才能操作这个表.
- 最安全的
- 效率最低的.
-
rollback-for=”异常类型全限定路径”
- 当出现什么异常时需要进行回滚
- 建议:给定该属性值.
- 手动抛异常一定要给该属性值.
-
no-rollback-for=””
- 当出现什么异常时不滚回事务.
此处orm框架为JPA (非SpringDataJpa):
<?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:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--扫描包-->
<context:component-scan base-package="top.young.dao,top.young.service"/>
<!--导入properties文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 配置数据库连接池-->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- 配置entityManagerFactory-->
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<!-- 指定数据库连接池-->
<property name="dataSource" ref="dataSource"/>
<!-- 指定实体类包-->
<property name="packagesToScan" value="top.young.domain"/>
<!-- 指定实现厂商-->
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<!-- 显示SQL-->
<property name="showSql" value="true"/>
<!-- 创建表-->
<property name="generateDdl" value="false"/>
<!-- 配置方言-->
<property name="databasePlatform" value="org.hibernate.dialect.MySQLDialect"/>
</bean>
</property>
</bean>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<!-- 配置声明式事务-->
<tx:advice id="txManager" transaction-manager="transactionManager">
<tx:attributes>
<!-- 哪些方法需要有事务控制-->
<tx:method name="save"/>
<tx:method name="update"/>
<tx:method name="delete"/>
</tx:attributes>
</tx:advice>
<!-- 范围直接设置为serviceImpl里面的所有类的所有方法。-->
<aop:config>
<aop:pointcut id="myPointcut" expression="execution(* top.young.service.impl.*.*(..))"/>
</aop:config>
</beans>
3-4使用注解方式配置:
@Transactional(readOnly = true)
public class BaseServiceImpl<T> implements IBaseService<T> {
@Autowired
private IBaseDao<T> dao;
@Transactional(readOnly = false)
@Override
public void save(T t) {
dao.save(t);
}
@Override
@Transactional(readOnly = false)
public void update(T t) {
dao.update(t);
}
@Override
@Transactional(readOnly = false)
public void delete(Long id) {
dao.delete(id);
}
@Override
public T find(Long id) {
return dao.find(id);
}
@Override
public List findAll() {
return dao.findAll();
}
@Override
public Long getTotal() {
return dao.getTotal();
}
@Override
public PageInfo findByPage(Integer pageNumber, Integer pageSize) {
PageInfo pageInfo=new PageInfo();
pageInfo.setTotal(dao.getTotal());
pageInfo.setRows(dao.findByPage(pageNumber, pageSize));
pageInfo.setPageNumber(pageNumber);
pageInfo.setPageSize(pageSize);
return pageInfo;
}
}