Spring介绍
概述:Spring 是一个开源框架,是为了解决企业应用程序开发复杂性而创建的。
框架的主要优势之一就是其分层架构,分层架构允许您选择使用哪一个组件,同时为 J2EE 应用程序开发提供集成的框架。
简单来说,Spring是一个分层的JavaSE/EE full-stack(一站式) 轻量级开源框架。
一站式:Spring提供了三层解决方案.
Spring 的发展历程
1997 年 IBM 提出了 EJB 的思想
1998 年, SUN 制定开发标准规范 EJB1.0
1999 年, EJB1.1 发布
2001 年, EJB2.0 发布
2003 年, EJB2.1 发布
2006 年, EJB3.0 发布
Rod Johnson(spring 之父)
Expert One-to-One J2EE Design and Development(2002),阐述了 J2EE 使用 EJB 开发设计的优点及解决方案
Expert One-to-One J2EE Development without EJB(2004),阐述了 J2EE 开发不使用 EJB 的解决方式(Spring 雏形)
2017 年 9 月份发布了 spring 的最新版本 spring 5.0 通用版(GA)
Spring的优点
1.方便解耦,简化开发
通过Spring提供的IoC容器,我们可以将对象之间的依赖关系交由Spring进行控制,避免硬编码所造成的过度程序耦合。有了Spring,用户不必再为单实例模式类、属性文件解析等这些很底层的需求编写代码,可以更专注于上层的应用。
2.AOP编程的支持
通过Spring提供的AOP功能,方便进行面向切面的编程,许多不容易用传统OOP实现的功能可以通过AOP轻松应付。
3.声明式事务的支持
在Spring中,我们可以从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活地进行事务的管理,提高开发效率和质量。
4.方便程序的测试
可以用非容器依赖的编程方式进行几乎所有的测试工作,在Spring里,测试不再是昂贵的操作,而是随手可做的事情。例如:Spring对Junit4支持,可以通过注解方便的测试Spring程序。
5.方便集成各种优秀框架
Spring不排斥各种优秀的开源框架,相反,Spring可以降低各种框架的使用难度,Spring提供了对各种优秀框架(如Struts,Hibernate、Hessian、Quartz)等的直接支持。
6.降低Java EE API的使用难度
Spring对很多难用的Java EE API(如JDBC,JavaMail,远程调用等)提供了一个薄薄的封装层,通过Spring的简易封装,这些Java EE API的使用难度大为降低。
程序的耦合
耦合性(Coupling),也叫耦合度,是对模块间关联程度的度量。耦合的强弱取决于模块间接口的复杂性、调用模块的方式以及通过界面传送数据的多少。模块间的耦合度是指模块之间的依赖关系,包括控制关系、调用关系、数据传递关系。模块间联系越多,其耦合性越强,同时表明其独立性越差( 降低耦合性,可以提高其独立性)。 耦合性存在于各个领域,而非软件设计中独有的,但是我们只讨论软件工程中的耦合。
自定义IOC(工厂模式解耦)
<dependencies>
<!-- 解析 xml 的 dom4j -->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<!-- dom4j 的依赖包 jaxen -->
<dependency>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
<version>1.1.6</version>
</dependency>
<!--单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
</dependency>
</dependencies>
IOC概述
IOC(inversion of control)的中文解释是“控制反转”,对象的使用者不是创建者. 作用是将对象的创建 反转给spring框架来创建和管理。
控制反转怎么去理解呢。 其实它反转的是什么呢,是对象的创建工作。 举个例子:平常我们在servlet或者service里面创建对象,都是使用new 的方式来直接创建对象,现在有了spring之后,我们就再也不new对象了,而是把对象创建的工作交给spring容器去维护。我们只需要问spring容器要对象即可
ioc 的作用:削减计算机程序的耦合(解除我们代码中的依赖关系)。
配置文件详解(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.xsd">
<!--
bean标签: 注册bean, 把对象交给Spring管理
1.【掌握】属性:
id/name属性: 随便写,作为bean的唯一标识,不要重复(建议写接口的名字,首字母小写)
class属性: 类的全限定名
scope属性: singleton 单例, 不管获得多少次 都是同一个, 创建出来存到Spring容器里面 【默认】
prototype 多例, 每获得一次 就创建一个新的对象,创建出来不会存到Spring容器里面
request 把创建的对象存到request域,针对web项目【了解】
session 把创建的对象存到session域,针对web项目【了解】
我们一般也是单例的, 特殊情况才是设置为prototype多例 eg:struts2里面的action
2. 【了解】的属性
init-method属性: 指定初始化方法,写方法名
destroy-method属性: 指定销毁的方法, 象征着当前对象从Spring容器里面移除了 写方法名
-->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl" scope="prototype"
init-method="initMethod" destroy-method="destoryMethod"
></bean>
</beans>
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
两者的区别:
ApplicationContext加载方式是框架启动时就开始创建所有单例的bean,存到了容器里面
BeanFactory加载方式是用到bean时再加载(目前已经被废弃)
实例化Bean的三种方式【了解】
方式一:无参构造方法方式
需要实例化的类,提供无参构造方法
配置代码
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
方式二:静态工厂方式
需要额外提供工厂类 , 工厂方法是静态方法
public class StaticFactory {
public static Object getBean(){
return new AccountServiceImpl02();
}
}
配置代码
<!--方式二:静态工厂方式 -->
<bean id="accountService02" class="com.itheima.utils.StaticFactory" factory-method="getBean"/>
方式三:实例工厂实例化的方法
创建实例化工厂类
public class InstanceFactory {
public Object getBean(){
return new AccountServiceImpl03();
}
}
配置
<!-- 方式三: 实例化工厂 -->
<!--注册工厂 -->
<bean id="factory" class="com.itheima.utils.InstanceFactory"></bean>
<!--引用工厂 -->
<bean id="accountService03" factory-bean="factory" factory-method="getBean"/>
一般情况下不会使用第二和三种, 主要使用第一种
依赖注入全称是 dependency Injection 翻译过来是依赖注入.其实就是如果我们托管的某一个类中存在属性,需要spring在创建该类实例的时候,顺便给这个对象里面的属性进行赋值。 这就是依赖注入。
现在, Bean的创建交给Spring了, 需要在xml里面进行注册
我们交给Spring创建的Bean里面可能有一些属性(字段), Spring帮我创建的同时也把Bean的一些属性(字段)给赋值, 这个赋值就是注入.
注册: 把bean的创建交给Spring
依赖注入: bean创建的同时, bean里面可能有一些字段需要赋值, 这个赋值交给Spring, 这个过程就是依赖注入
构造方法方式注入
set方法方式的注入
P名称空间注入
SpEL的属性注入
构造方法方式注入【掌握】
public class AccountServiceImpl implements AccountService {
private String name;
public AccountServiceImpl(String name) {
this.name = name;
}
@Override
public void save() {
System.out.println("AccountServiceImpl... save()"+name);
}
}
<!--注册AccountService-->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
<constructor-arg name="name" value="张三"></constructor-arg>
</bean>
set方法方式的注入【重点】
注入简单类型
public class AccountServiceImpl implements AccountService {
private String name;
public void setName(String name) {
this.name = name;
}
@Override
public void save() {
System.out.println("AccountServiceImpl... save()"+name);
}
}
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
<property name="name" value="李四"></property>
</bean>
注入数组类型
public class AccountServiceImpl implements AccountService {
private String[] hobbys;
public void setHobbys(String[] hobbys) {
this.hobbys = hobbys;
}
@Override
public void save() {
System.out.println("AccountServiceImpl... save()"+ Arrays.toString(hobbys));
}
}
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
<property name="hobbys">
<array>
<value>篮球</value>
<value>足球</value>
<value>乒乓球</value>
<value>排球</value>
</array>
</property>
</bean>
注入Map类型
public class AccountServiceImpl implements AccountService {
private Map<String,String> map;
public void setMap(Map<String, String> map) {
this.map = map;
}
@Override
public void save() {
Set<Map.Entry<String, String>> set = map.entrySet();
for (Map.Entry<String, String> entry : set) {
System.out.println(entry.getKey()+":"+entry.getValue());
}
System.out.println("AccountServiceImpl... save()");
}
}
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
<property name="map">
<map>
<entry key="akey" value="aaa"/>
<entry key="bkey" value="bbb"/>
<entry key="ckey" value="ccc"/>
</map>
</property>
</bean>
或者
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
<property name="map">
<props>
<prop key="akey">aaa</prop>
<prop key="bkey">bbb</prop>
<prop key="ckey">ccc</prop>
</props>
</property>
</bean>
注入Java对象类型
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public void save() {
System.out.println("AccountServiceImpl... save()");
accountDao.save();
}
}
<!--注册AccountService-->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
<!--注册accountDao-->
<bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
</bean>
名称空间注入【了解】
p名称空间的方式
<?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:p="http://www.springframework.org/schema/p"
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">
</beans>
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl" p:name="张三">
</bean>
Spring3.0之后 SpEL的属性注入【了解】
这种方式是使用spring 提供的一种表达式方式去赋值, 如果是给前面提过的几个类型注入赋值,那么使用spEL 表达式来注入赋值倒显得繁琐。但是这个spEL 方式赋值,最大的好处在于,它能像EL表达式一般,在里面进行运算、 逻辑判断,还可以调用其它Bean的属性和方法给当前属性赋值
<property name="name" value="#{'张三'}" ></property>
小结
<bean id="" class="">
<constructor-arg name="" value=""></constructor-arg>
<constructor-arg name="" ref=""></constructor-arg>
</bean>
<bean id="" class="">
<property name="" value=""></property>
</bean>
<bean id="" class="">
<property name="">
<array>
<value></value>
</array>
</property>
</bean>
<bean id="" class="">
<property name="">
<map>
<entry key="" value=""></entry>
</map>
</property>
</bean>
<bean id="" class="">
<property name="" ref="">
</property>
</bean>
使用Spring的IoC的实现账户的CRUD配置文件
<?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">
<!--注册AccountService-->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"/>
</bean>
<!--注册accountDao-->
<bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
<property name="queryRunner" ref="queryRunner"/>
</bean>
<!--注册queryRunner-->
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner">
<constructor-arg name="ds" ref="dataSource"/>
</bean>
<!--注册数据源-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="jdbc:mysql://localhost:3306/spring_day02?characterEncoding=utf8"/>
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>
</beans>
测试案例
public class DbTest {
@Test
public void fun01() throws Exception {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
AccountService accountService = (AccountService) applicationContext.getBean("accountService");
List<Account> list = accountService.findAll();
System.out.println(list);
}
}
@NonNull 注解和@Nullable 注解的使用
用 @Nullable 和 @NotNull 注解来显示表明可为空的参数和以及返回值。这样就够在编译的时候处理空值而不是在运行时抛出 NullPointerExceptions。
添加依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
</dependencies>
引入context的命名空间
applicationContext.xml中需要引入context的命名空间,可在xsd-configuration.html中找到
<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 http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
</beans>
配置扫描
在bean标签内部,使用context:component-scan ,让spring扫描该基础包下的所有子包注解
<context:component-scan base-package="com.itheima"></context:component-scan>
用于创建对象的
相当于:
@Component
作用:
把资源让 spring 来管理。相当于在 xml 中配置一个 bean。
属性:
value:指定 bean 的 id。如果不指定id属性,默认 bean 的 id 是当前类的类名。首字母小写。
web里面的三层结构 中的所有类,在spring里面都称之为 Component (组件) , 但是它也提供了更详细的注解来针对不同的层级声明 。
三个衍生注解如下:
@Controller :修饰WEB层类 —>action | SpringMVC
@Service :修饰业务层类 —>service
@Repository :修饰DAO层类 —>dao
用于改变作用范围的@scope
@scope
singleton: 单例(默认)
prototype:多例
@Scope注解用来描述类的作用范围的,默认值singleton。如同xml中bean标签的属性scope .如果配置成多例的使用prototype。
@Scope("prototype")
@Component("accountService")
public class AccountServiceImpl implements AccountService {}
和生命周期相关的【了解】
初始化和销毁回调方法对应的注解
@PostConstrut:如同xml中bean标签的属性init-method ,用来设置spring框架初始化此类实例时调用的初始化方法,标注在此类的初始化方法上
@PreDestroy:如同xml中bean标签的属性init-method ,用来设置spring框架销毁此类实例时调用的销毁方法,标注在此类的销毁方法上
注意:这两个注解都是配在方法上的
总结:
- 注册Bean的 相当于配置了
- @Component(“id”)
- @Controller(“id”) 配置web层
- @Service(“id”) 配置Service层
- @Repository(“id”) 配置持久层 如果id不配, 默认就是类的名字,首字母小写
- 配置bean的作用范围
- @Scope(“类型”)
- singleton 单例【默认】
- prototype 多例
- 配置和生命周期相关的
- @PostConstruct init-method=""
- @PreDestroy destroy-method=""
使用注解注入属性
@Value
@Value("奥巴马")
private String name;
@Autowired
@Component("accountService")
public class AccountServiceImpl implements AccountService {
@Autowired
//当有多个AccountDao实现类时候, @Autowired会在在Spring容器里面找id为accountDao的对象注入,找不到就报错
private AccountDao accountDao;
@Override
public void save() {
System.out.println("AccountServiceImpl---save()");
accountDao.save();
}
}
@Qualifier
@Component("accountService")
public class AccountServiceImpl implements AccountService {
@Autowired
@Qualifier(value = "accountDao02")
private AccountDao accountDao;
@Override
public void save() {
System.out.println("AccountServiceImpl---save()");
accountDao.save();
}
}
@Resource
如果上面一个接口有多种实现,那么现在需要指定找具体的某一个实现,那么可以使用
@Component("accountService")
public class AccountServiceImpl implements AccountService {
@Resource(name = "accountDao02")
private AccountDao accountDao;
@Override
public void save() {
System.out.println("AccountServiceImpl---save()");
accountDao.save();
}
}
小结:
混合开发特点
使用xml(注册bean)来管理bean
使用注解来注入属性
混合开发环境搭建
创建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" xmlns:context="http://www.springframework.org/schema/context"
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
">
</beans>
配置扫描
<context:component-scan base-package="com.itheima" />
小结:
纯注解测试实现
@Configuration
@Configuration
public class SpringConfiguration {
}
@ComponentScan
@Configuration
@ComponentScan("com.itheima")
public class SpringConfiguration {
}
@Bean
/**
* 该类是一个配置类,它的作用和bean.xml是一样的
*/
@Configuration
@ComponentScan("com.itheima")
public class SpringConfiguration {
private String driver = "com.mysql.jdbc.Driver";
private String url = "jdbc:mysql:///spring_day02";
private String username = "root";
private String password = "123456";
/**
* 用于创建一个QueryRunner对象
* @param dataSource
* @return
*/
@Bean("runner")
public QueryRunner createQueryRunner(@Qualifier(value ="dataSource") DataSource dataSource){
return new QueryRunner(dataSource);
}
/**
* 用于创建DataSource
* @return
* @throws PropertyVetoException
*/
@Bean(name = "dataSource")
public DataSource createDataSource() throws PropertyVetoException {
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setDriverClass(driver);
ds.setJdbcUrl(url);
ds.setUser(username);
ds.setPassword(password);
return ds;
}
}
@Import
/**
* 该类是一个配置类,它的作用和bean.xml是一样的
*/
@Configuration
@ComponentScan("com.itheima")
@Import({JdbcConfig.class})
public class SpringConfiguration {
}
public class JdbcConfig {
private String driver = "com.mysql.jdbc.Driver";
private String url = "jdbc:mysql:///spring_day02";
private String username = "root";
private String password = "123456";
/**
* 用于创建一个QueryRunner对象
* @param dataSource
* @return
*/
@Bean("runner")
@Scope("prototype")
public QueryRunner createQueryRunner(@Qualifier(value ="dataSource") DataSource dataSource){
return new QueryRunner(dataSource);
}
/**
* 用于创建DataSource
* @return
* @throws PropertyVetoException
*/
@Bean(name = "dataSource")
public DataSource createDataSource() throws PropertyVetoException {
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setDriverClass(driver);
ds.setJdbcUrl(url);
ds.setUser(username);
ds.setPassword(password);
return ds;
}
}
@PropertySource
jdbc.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring_day02
jdbc.username=root
jdbc.password=123456
JdbcConfig.java
@PropertySource(value = {“classpath:jdbc.properties”})
public class JdbcConfig {
@Value(" j d b c . u r l " ) p r i v a t e S t r i n g u r l ; @ V a l u e ( " {jdbc.url}") private String url; @Value(" jdbc.url")privateStringurl;@Value("{jdbc.driver}")
private String driver;
@Value(" j d b c . u s e r n a m e " ) p r i v a t e S t r i n g u s e r n a m e ; @ V a l u e ( " {jdbc.username}") private String username; @Value(" jdbc.username")privateStringusername;@Value("{jdbc.password}")
private String password;
@Bean(“queryRunner”)
public QueryRunner createQueryRunner(DataSource dataSource){
QueryRunner queryRunner = new QueryRunner(dataSource);
return queryRunner;
}
@Bean(“dataSource”)
public DataSource createDataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl(url);
dataSource.setDriverClassName(driver);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
}
通过注解获取容器
ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
在测试类中,每个测试方法都有以下两行代码:
ApplicationContext ac = new ClassPathXmlApplicationContext(“bean.xml”);
AccountService as= ac.getBean(“accountService”,AccountService.class);
这两行代码的作用是获取容器,如果不写的话,直接会提示空指针异常。所以又不能轻易删掉。
导入spring整合Junit的坐标
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
在测试类上面标记注解
@RunWith(SpringJUnit4ClassRunner.class)//指明运行的测试环境
//@ContextConfiguration("classpath:applicationContext.xml")//指明spring框架的加载的配置文件【有applicationContext.xml解使用这种】
@ContextConfiguration(classes = SpringConfiguration.class)//指明spring框架的加载的配置类【纯注解使用这种】
public class DbTest
{
@Autowired
private AccountService accountService;
@Test
public void testSaveAccount() throws Exception {
Account account = new Account();
account.setName("王二麻子");
account.setMoney(100f);
accountService.save(account);
}
}
思考问题为什么不把测试类配到xml中
什么是AOP
AOP:全称是AspectOriented Programming, 即面向切面编程。在不修改源码的基础上,对我们的已有方法进行增强。
说白了就是把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,进行增强
AOP实现原理
使用动态代理技术
在AOP 这种思想还没有出现的时候,我们解决 切面的问题思路无非有以下两种:
AOP 的底层动态代理实现有两种方案。
一种是使用JDK的动态代理。 这种是早前我们在前面的基础增强说过的。 这一种主要是针对有接口实现的情况。 它的底层是创建接口的实现代理类, 实现扩展功能。也就是我们要增强的这个类,实现了某个接口,那么我就可以使用这种方式了. 而另一种方式是使用了cglib 的动态代理,这种主要是针对没有接口的方式,那么它的底层是创建被目标类的子类,实现扩展功能.
小结:
学习spring中的AOP要明确的事
AOP中的术语
前置通知
导入坐标
<dependencies>
<!--Spring核心容器-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!--SpringAOP相关的坐标-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
<!--Spring整合单元测试-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!--单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
定义被增强的业务逻辑类和切面类(增强的)
//被增强的类
public class AccountDaoImpl implements AccountDao {
@Override
public void save() {
System.out.println("save()...");
}
}
//增强的类(切面类)
public class MyAscept {
public void checkPrivilege() {// 通知
System.out.println("权限的校验...");
}
}
xml中声明 增强的bean (扩展的bean) 和 被增强的bean (被扩展的bean)
<!--注册accountDao-->
<bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl"/>
<!--注册增强(切面)类-->
<bean id="myAscept" class="com.itheima.aspect.MyAscept"/>
定义aop的配置
<!--配置切入点(哪个包下的哪个类哪个方法需要增强)
expression="execution(* com.xyz.myapp.service.*.*(..))"
第一个* 表示返回值 ,不管任何返回值。 匹配任意返回值
com.xyz.myapp.service : 表示这个包下
第二个* 表示这个包下的所有类
第三个* 表示这个包下的所有类中的所有方法
(..) : 这个表示方法的参数 .. 任意参数
spring 根据这一套规则,能够找到具体哪一个方法来做增强了。
-->
-->
<!--配置AOP-->
<aop:config>
<aop:pointcut id="pointCut01" expression="execution(* com.itheima.dao.impl.AccountDaoImpl.save(..))"/>
<!--配置切面(切入点和通知的结合)-->
<aop:aspect ref="myAscept">
<!--前置通知-->
<aop:before method="checkPrivilege" pointcut-ref="pointCut01"></aop:before>
</aop:aspect>
</aop:config>
</aop:config>
后置通知
在原来方法之后执行. 特点: 可以得到被增强方法的返回值
<!--后置通知 -->
<aop:after-returning method="showLog" pointcut-ref="pointCut2" returning="obj"/>
环绕通知
在方法之前和方法之后执行. 特点:可以阻止目标方法执行
//环绕通知(ProceedingJoinPoint 代表目标方法)
public void showTime(ProceedingJoinPoint joinPoint) throws Throwable {
//打印系统时间
System.out.println("调用之前的时间="+System.currentTimeMillis());
//执行目标方法
joinPoint.proceed();
//打印系统时间
System.out.println("调用之后的时间="+System.currentTimeMillis());
}
//配置
<!--环绕通知 -->
<aop:around method="showTime" pointcut-ref="pointCut3"/>
异常通知【了解】
方法出现异常执行. 如果方法没有异常,不会执行. 特点:可以获得异常的信息
增强的方法:
public void afterThrowing(Throwable e){
System.out.println("异常抛出通知========"+e.getMessage());
}
配置文件:
<aop:after-throwing method="afterThrowing" throwing="e" pointcut-ref="pointCut4"/>
最终通知【了解】
指的是无论是否有异常,总是被执行的。
<!--最终通知 -->
<aop:after method="after" pointcut-ref="pointCut4"/>
定义切面类,在切面增强类上面使用注解 @Aspect并且在切面类中定义切入点方法
@Aspect
public class MyAscept {
@Before(value = "execution(* com.itheima.dao.impl.AccountDaoImpl.save(..))")
public void checkPrivilege() {// 通知
System.out.println("权限的校验...");
}
}
创建配置文件
<?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.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--注册accountDao-->
<bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl"/>
<!--注册切面类-->
<bean id="myAscept" class="com.itheima.aspect.MyAscept"/>
<!--开启AOP注解-->
<aop:aspectj-autoproxy/>
</beans>
后置通知
@AfterReturning(value = "execution(* com.itheima.dao.impl.AccountDaoImpl.delete(..))",returning="obj")
public void showLog(Object obj) {
System.out.println("显示删除之后的日志..." + obj);
}
环绕通知
// 打印时间(环绕通知)
@Around(value = "execution(* com.itheima.dao.impl.AccountDaoImpl.findAll(..))")
public void showTime(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("执行之前的时间:" + System.currentTimeMillis());
//执行findAll
joinPoint.proceed();
System.out.println("执行之后的时间:" + System.currentTimeMillis());
}
异常通知【了解】
//测试异常通知
@AfterThrowing(value="execution(* com.itheima.dao.impl.AccountDaoImpl.update(..))",throwing="e")
public void showException(Throwable e) {
System.out.println("异常通知...." + e.getMessage());
}
最终通知【了解】
//测试最终通知
@After(value = "execution(* com.itheima.dao.impl.AccountDaoImpl.update(..))")
public void showFinal() {
System.out.println("最终通知...");
}
方式一:
AccountDaoImpl.java
public class AccountDaoImpl implements AccountDao {
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Override
public Account findAccountById(Integer id) {
List<Account> list = jdbcTemplate.query("select * from account where id = ? ",new AccountRowMapper(),id);
return list.isEmpty()?null:list.get(0);
}
@Override
public Account findAccountByName(String name) {
List<Account> list = jdbcTemplate.query("select * from account where name = ? ",new AccountRowMapper(),name);
if(list.isEmpty()){
return null;
}
if(list.size()>1){
throw new RuntimeException("结果集不唯一,不是只有一个账户对象");
}
return list.get(0);
}
@Override
public void updateAccount(Account account) {
jdbcTemplate.update("update account set money = ? where id = ? ",account.getMoney(),account.getId());
}
}
applicationContext.xml
<!-- 配置一个dao -->
<bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
<!-- 注入jdbcTemplate -->
<property name="jdbcTemplate" ref="jdbcTemplate"></property>
</bean>
<!-- 配置一个数据库的操作模板:JdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置数据源 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql:///spring_day04"></property>
<property name="username" value="root"></property>
<property name="password" value="123456"></property>
</bean>
方式二:
AccountDaoImpl.java
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {
@Override
public Account findAccountById(Integer id) {
//getJdbcTemplate()方法是从父类上继承下来的。
List<Account> list = getJdbcTemplate().query("select * from account where id = ? ",new AccountRowMapper(),id);
return list.isEmpty()?null:list.get(0);
}
@Override
public Account findAccountByName(String name) {
//getJdbcTemplate()方法是从父类上继承下来的。
List<Account> list = getJdbcTemplate().query("select * from account where name = ? ",new AccountRowMapper(),name);
if(list.isEmpty()){
return null;
}
if(list.size()>1){
throw new RuntimeException("结果集不唯一,不是只有一个账户对象");
}
return list.get(0);
}
@Override
public void updateAccount(Account account) {
//getJdbcTemplate()方法是从父类上继承下来的。
getJdbcTemplate().update("update account set money = ? where id = ? ",account.getMoney(),account.getId());
}
}
applicationContext.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">
<!-- 配置dao2 -->
<bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
<!-- 注入dataSource -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置数据源 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql:///spring_day04"></property>
<property name="username" value="root"></property>
<property name="password" value="123456"></property>
</bean>
</beans>
概述:
由于Spring对持久层的很多框架都有支持 , Hibernate 、 jdbc 、JdbcTemplate , MyBatis 由于使用的框架不同,所以使用事务管理操作API 也不尽相同。 为了规范这些操作, Spring统一定义一个事务的规范 ,这其实是一个接口 。这个接口的名称 : PlatformTrasactionManager.并且它对已经做好的框架都有支持.
如果dao层使用的是JDBC, JdbcTemplate 或者mybatis,那么 可以使用DataSourceTransactionManager 来处理事务
如果dao层使用的是 Hibernate, 那么可以使用HibernateTransactionManager 来处理事务
相关的API
PlatformTransactionManager
平台事务管理器是一个接口,实现类就是Spring真正管理事务的对象。
常用的实现类:
DataSourceTransactionManager :JDBC开发的时候(JDBCTemplate,MyBatis),使用事务管理。
HibernateTransactionManager :Hibernate开发的时候,使用事务管理。
TransactionDefinition
事务定义信息中定义了事务的隔离级别,传播行为,超时信息,只读。TransactionStatus:事务的状态信息
事务状态信息中定义事务是否是新事务,是否有保存点。
编程式(硬编码)事务【了解】
public class JDBCTempleTest {
@Test
public void fun01() throws PropertyVetoException{
//1. 创建数据源
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass("com.mysql.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/spring_day03");
dataSource.setUser("root");
dataSource.setPassword("123456");
//2. 创建jdbc模版
final JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource);
//3.创建事务管理器
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
//4.创建事务模版
TransactionTemplate transactionTemplate = new TransactionTemplate();
transactionTemplate.setTransactionManager(transactionManager);
//4. 进行事务操作
transactionTemplate.execute(new TransactionCallback<Object>() {
@Override
public Object doInTransaction(TransactionStatus status) {
//操作数据库
jdbcTemplate.update("update t_account set money = money - ? where name = ? ", 100.0,"zs");
int i = 1/0;
jdbcTemplate.update("update t_account set money = money + ? where name = ? ", 100.0,"ls");
return null;
}
});
}
}
我们项目开发 一般不会使用编程式(硬编码)方式. 一般使用声明式(配置)事务
Spring声明式事务-xml配置方式【重点】
配置事务管理器
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
配置事务建议(事务的规则)
<tx:advice id="adviceId" transaction-manager="transactionManager">
<!--在tx:advice标签内部 配置事务的属性 -->
<tx:attributes>
<!-- 指定方法名称:是业务核心方法
read-only:是否是只读事务。默认false,不只读。
isolation:指定事务的隔离级别。默认值是使用数据库的默认隔离级别。
propagation:指定事务的传播行为。
timeout:指定超时时间。默认值为:-1。永不超时。
rollback-for:用于指定一个异常,当执行产生该异常时,事务回滚。产生其他异常,事务不回滚。没有默认值,任何异常都回滚。
no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时,事务回滚。没有默认值,任何异常都回滚。
-->
<tx:method name="*" read-only="false" propagation="REQUIRED"/>
<tx:method name="find*" read-only="true" propagation="SUPPORTS"/>
</tx:attributes>
</tx:advice>
配置事务的AOP
<aop:config>
<!-- 定义切入点 ,也就是到底给那些类中的那些方法加入事务的管理 -->
<aop:pointcut expression="execution(* com.itheima.service.impl.*.*(..))" id="pointCat"/>
<!-- 让切入点和事务的建议绑定到一起 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pointCat"/>
</aop:config>
spring声明式事务-注解方式【重点】
在applicationContext里面打开注解驱动
<!--一,配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--二配置事务注解 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
在业务逻辑类上面使用注解
@Transactional : 如果在类上声明,那么标记着该类中的所有方法都使用事务管理。也可以作用于方法上, 那么这就表示只有具体的方法才会使用事务。