笔记参考:https://www.bilibili.com/video/BV1Sb411s7vP?t=5
目录
1、Spring的概述
Spring是分层的Java SE/EE应用full-stack轻量级开源框架,以IoC(Inverse of Control反转控制)和AOP(Aspect Oriented Programming面向切面编程)为内核,提供了展现层Spring MVC和持久层Spring JDBC以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多著名的第三方框架和类库,逐渐成为使用最多的Java EE企业应用开源框架。
Spring的优势:
- 方便解耦,简化开发
- 耦合:程序间的依赖关系(类之间的依赖,方法之间的依赖)
- 解耦:降低程序间的依赖关系。开发时应该做到编译期不依赖,运行期才依赖
- AOP编程的支持
- 声明式事务的支持(可以通过配置的方式实现事务的控制)
- 方便程序的测试
- 方便集成各种优秀的框架
- 降低JavaEE API的使用难度
- Java源码是经典学习范例
1.1、Spring的体系结构
1.2、程序间的耦合
解耦的基本思路:
- 使用反射来创建对象,避免使用new关键字
- 通过读取配置文件来获取要创建的对象的全限定类名
曾经我们写jdbc连接数据库时,包括表现层,服务层和持久层,表现层需要调用服务层的接口,服务层需要调用持久层的接口,所以,这就产生了依赖,那么我们如何避免这种依赖?——工厂模式
创建一个Bean对象的工厂:就是创建service和dao对象
Bean:在计算机英语中,有可重用组件的含义。
JavaBean:用java语言编写的可重用组件
步骤:
- 需要一个配置文件来配置我们的service和dao(因此需要将要创建的对象的全限定类名写在配置文件中)
- 通过通过读取配置文件中配置的内容,反射创建对象
1、创建bean.properties配置文件,配置要创建的javaBean的全限定类名(因为多个表现层可能用到同一个service层和dao层,因此可以通过配置文件反射创建对象降低耦合性)
accountService=service.impl.IAccountServiceImpl
accountDao=dao.impl.IAccountDaoImpl
2、factory.BeanFactory:
package factory;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
public class BeanFactory {
//定义一个Properties对象
private static Properties props;
//使用静态代码块为Properties对象赋值
static {
try {
//实例化对象
props=new Properties();
//获取properties文件的流对象
InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
props.load(in);
} catch (IOException e) {
throw new ExceptionInInitializerError("初始化properties失败!");
}
}
/**
* 根据Bean的名称获取bean对象
* @param beanName
* @return
*/
public static Object getBean(String beanName){
Object object=null;
try {
String beanPath=props.getProperty(beanName);
object=Class.forName(beanPath).newInstance(); //每次都会调用默认构造函数创建对象,因此多次创建的对象是多例的。这不会产生线程安全问题(也就是可以定义成员变量,但是会导致执行效率变低)
}catch (Exception e){
e.printStackTrace();
}
return object;
}
}
3、IAccountServiceImpl:
package service.impl;
import dao.IAccountDao;
import factory.BeanFactory;
import service.IAccountService;
//账户的业务层实现类
public class IAccountServiceImpl implements IAccountService {
//private IAccountDao iAccountDao=new IAccountDaoImpl(); 有依赖关系
private IAccountDao iAccountDao= (IAccountDao) BeanFactory.getBean("accountDao");//通过配置文件使用反射来创建对象,降低耦合性
public void saveAccount() {
iAccountDao.saveAccount();
}
}
4、cli.Client:
package cli;
import factory.BeanFactory;
import service.IAccountService;
public class Client {
public static void main(String[] args) {
/*IAccountService is=new IAccountServiceImpl();*/ //有依赖关系
IAccountService is= (IAccountService) BeanFactory.getBean("accountService");//通过配置文件使用反射来创建对象,降低耦合性
is.saveAccount();
}
}
由于使用反射创建javaBean对象时,使用的是newInstance(),那么创建的对象是多例的,这样的好处是不会产生线程安全问题(也就是可以定义成员变量而不会导致线程安全问题),但是实际中我们并不会定义成员变量,一般会定义在方法内,因此,这样会导致执行效率变低,那么如何改进呢?
改造BeanFactory:
package factory;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
public class BeanFactory {
//定义一个Properties对象
private static Properties props;
//定义一个Map,用于存放我们要创建的对象,我们把他称之为容器
private static Map<String,Object> beans;
//使用静态代码块为Properties对象赋值
static {
try {
//实例化对象
props=new Properties();
//获取properties文件的流对象
InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
props.load(in);
//实例化容器
beans=new HashMap<String, Object>();
//取出配置文件中所有的key
Enumeration keys = props.keys();
//遍历枚举
while (keys.hasMoreElements()){
//取出每个key
String key = keys.nextElement().toString();
//根据key获取value
String beanPath=props.getProperty(key);
//反射创建对象
Object value = Class.forName(beanPath).newInstance();
//把key和value存入beans
beans.put(key,value);
}
} catch (Exception e) {
throw new ExceptionInInitializerError("初始化properties失败!");
}
}
/**
* 根据Bean的名称获取bean对象
* @param beanName
* @return
*/
public static Object getBean(String beanName){
return beans.get(beanName); //因为是在static中就创建好了对象,这里获取,因此创建的是单例对象
}
}
2、Spring的IOC(控制反转)和DI(依赖注入)
IOC(控制反转):为什么叫控制反转呢,是因为它将创建对象方式的控制权交给了工厂,从而降低了程序间的耦合性,所以叫控制反转。
IOC真正的定义:把创建对象的权利交给框架,是框架的重要特征,并非面向对象编程的专用术语。他包括依赖注入(DI)和依赖查找
IOC的作用:削减计算机程序的耦合
2.1、使用Spring的IOC解决程序耦合
上面我们实现解耦需要自己写BeanFactory,但是现在我们可以使用Spring的IOC实现降低耦合
Spring基于XML的IOC环境搭建:
1、导约束
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.4</version>
</dependency>
2、创建配置文件bean.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">
<!--把对象的创建交给Spring来管理-->
<bean id="accountService" class="service.impl.IAccountServiceImpl"></bean>
<bean id="accountDao" class="dao.impl.IAccountDaoImpl"></bean>
</beans>
3、Client的改写:(使用Spring的IOC来降低耦合)
package cli;
import dao.IAccountDao;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import service.IAccountService;
import java.util.logging.XMLFormatter;
public class Client {
/**
* 获取spring的IOC核心容器,并根据id获取对象
* @param args
*/
public static void main(String[] args) {
//1、获取核心容器对象(其实就是我们之前写的map对象)
ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
//2、根据id获取bean对象
IAccountService as=(IAccountService) ac.getBean("accountService");
IAccountDao ad=ac.getBean("accountDao",IAccountDao.class);
System.out.println(as);
System.out.println(ad);
as.saveAccount();
/*-------使用BeanFactory-------*/
Resource resource=new ClassPathResource("bean.xml");
BeanFactory factory=new XmlBeanFactory(resource);
IAccountService as = (IAccountService) factory.getBean("accountService");
as.saveAccount();
}
}
ApplicationContext的三个常用实现类:
- ClassPathXmlApplicationContext:它可以加载类路径下的配置文件,要求配置文件必须在类路径下,不在的,加载不了
- FileSystemXmlApplicationContext:它可以加载磁盘任意路径下的配置文件(必须有访问权限)
- AnnotationConfigApplicationContext:它是用于读取注解创建容器的。
核心容器的两个接口引发的两个问题:
- ApplicationContext:单例对象适用。它在构建核心容器时,创建对象采取的策略是采用立即加载的方式,也就是说,只要一读取完配置文件,马上就创建配置文件中配置的对象
- BeanFactory:多例对象适用。它在构建核心容器时,创建对象采取策略是延迟加载的方式,也就是说,什么时候根据id获取对象,什么时候才真正的创建对象
2.2、spring对bean的管理细节
创建bean的三种方式:
- 使用默认构造函数创建,在spring的配置文件中使用bean标签配以id和class属性之后且没有其他属性和标签时,采用的就是默认构造函数创建bean对象,此时如果类中没有默认构造函数,则对象无法创建。
<bean id="accountService" class="service.impl.IAccountServiceImpl"></bean>
- 使用普通工厂中的方法创建对象(使用某个类中的方法创建对象并存入spring容器):
InstanceFactory:
package factory;
import service.IAccountService;
import service.impl.IAccountServiceImpl;
public class InstanceFactory {
public IAccountService getAccountService(){
return new IAccountServiceImpl();
}
}
<bean id="instanceFactory" class="factory.InstanceFactory"></bean>
<bean id="accountService" factory-bean="instanceFactory" factory-method="getAccountService"></bean>
- 使用工厂中的静态方法创建对象(使用某个类中的静态方法创建对象并存入spring容器)
InstanceFactory:
package factory;
import service.IAccountService;
import service.impl.IAccountServiceImpl;
public class InstanceFactory {
public static IAccountService getAccountService(){
return new IAccountServiceImpl();
}
}
<bean id="accountService" class="factory.InstanceFactory" factory-method="getAccountService"></bean>
bean对象的作用范围:
bean标签的scope属性用于调整bean的作用范围。属性的取值如下:
- singleton:单例(默认值)
- prototype:多例的
- request:作用于web应用的请求范围
- session:作用于web应用的会话范围
- global-session:作用于集群环境的会话范围(全局会话范围),当不是集群环境时,他就是session
全局session:
bean对象的生命周期:
对象 | 出生 | 活着 | 死亡 |
---|---|---|---|
单例对象(单例对象的生命周期和容器相同) | 当容器创建时,对象出生 | 只要容器还在,对象一直活着 | 容器销毁,对象消亡 |
多例对象 | 当使用对象时spring框架为我们创建 | 对象只要是在使用过程中就一直活着 | 当对象长时间不用且没有别的对象引用时,由java的垃圾回收器回收 |
测试bean的生命周期:
IAccountServiceImpl:
package service.impl;
import dao.IAccountDao;
import factory.BeanFactory;
import service.IAccountService;
public class IAccountServiceImpl implements IAccountService {
public void saveAccount() {
System.out.println("accountService...");
}
public void init(){
System.out.println("当对象创建时执行的方法");
}
public void destory(){
System.out.println("当对象销毁时执行的方法");
}
}
bean.xml:
<bean id="accountService" class="service.impl.AccountService" scope="singleton" init-method="init" destroy-method="destory"></bean>
2.3、Spring中的依赖注入(DI)
依赖关系的管理:以后都交给spring来维护,在当前类需要用到其他类时,由spring为我们提供,我们只需要在配置文件中说明。
依赖注入:Dependency Injection,依赖关系的维护,就称为依赖注入。
能注入的数据有三类:基本数据类型和String、其他bean类型(在配置文件中或注解配置过的bean)、复杂类型/集合类型。
注入的方式有三种:使用构造函数提供、使用set方法提供、使用注解提供。
2.3.1、构造函数注入
IAccountServiceImpl:
package service.impl;
import service.IAccountService;
import java.util.Date;
public class IAccountServiceImpl implements IAccountService {
//如果是经常发生变化的数据,并不适合注入的方式
private String name;
private Integer age;
private Date birthday;
public IAccountServiceImpl(String name,Integer age,Date birthday){
this.name=name;
this.age=age;
this.birthday=birthday;
}
public void saveAccount() {
System.out.println("service中的saveAccount方法执行了"+name+","+age+","+birthday);
}
}
使用的标签:constructor-arg
标签出现的位置:bean标签的内部
标签中的属性:
- type:用于指定要注入的数据的数据类型,该数据类型也是构造函数中某个或某些参数的类型
- index:用于指定要注入的数据给构造函数中指定索引位置的参数赋值。参数索引的位置是从0开始。
- name:用于指定给构造函数中指定名称的参数赋值。(最常用的)
- value:用于提供基本类型和String类型的数据
- ref:用于指定其他的bean类型数据。她指的就是在spring的IOC核心容器中出现过的bean对象
构造函数注入的优势:在获取bean对象时,注入数据是必须的操作,否则对象无法创建成功。
弊端:改变了bean对象的实例化方式,使我们在创建对象时,如果用不到这些数据也必须提供。
bean.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="accountService" class="service.impl.IAccountServiceImpl">
<constructor-arg name="name" value="Julia"></constructor-arg>
<constructor-arg name="age" value="22"></constructor-arg>
<constructor-arg name="birthday" ref="now"></constructor-arg>
</bean>
<bean id="now" class="java.util.Date"></bean>
</beans>
Client:
package cli;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import service.IAccountService;
public class Client {
/**
* 获取spring的IOC核心容器,并根据id获取对象
* @param args
*/
public static void main(String[] args) {
//1、获取核心容器对象(其实就是我们之前写的map对象)
ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
//2、根据id获取bean对象
IAccountService as=(IAccountService) ac.getBean("accountService");
as.saveAccount();
}
}
2.3.2、set方法注入
IAccountServiceImpl:
package service.impl;
import service.IAccountService;
import java.util.Date;
public class IAccountServiceImpl implements IAccountService {
private String name;
private Integer age;
private Date birthday;
public void setName(String name) {
this.name = name;
}
public void setAge(Integer age) {
this.age = age;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public void saveAccount() {
System.out.println("service中的saveAccount方法执行了"+name+","+age+","+birthday);
}
}
涉及的标签:property
标签出现的位置:bean标签的内部
标签的属性:
- name:用于指定注入时所调用的set方法名称
- value:用于提供基本类型和String类型的数据
- ref:用于指定其他的bean类型数据。她指的就是在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">
<!--set方式注入-->
<bean id="accountService" class="service.impl.IAccountServiceImpl">
<property name="name" value="Julia"></property>
<property name="age" value="18"></property>
<property name="birthday" ref="now"></property>
</bean>
<bean id="now" class="java.util.Date"></bean>
</beans>
set方式的优势:创建对象时,没有明确的限制,可以直接使用默认构造函数。
弊端:如果有某个成员必须有值,则获取对象时有可能set方法没有执行。
2.3.3、复杂类型的注入
IAccountServiceImpl:
package service.impl;
import service.IAccountService;
import java.util.*;
public class IAccountServiceImpl implements IAccountService {
private String[] myStrs;
private List<String> myList;
private Set<String> mySet;
private Map<String,String> myMap;
private Properties myProps;
public void setMyStrs(String[] myStrs) {
this.myStrs = myStrs;
}
public void setMyList(List<String> myList) {
this.myList = myList;
}
public void setMySet(Set<String> mySet) {
this.mySet = mySet;
}
public void setMyMap(Map<String, String> myMap) {
this.myMap = myMap;
}
public void setMyProps(Properties myProps) {
this.myProps = myProps;
}
public void saveAccount() {
System.out.println(Arrays.toString(myStrs));
System.out.println(myList);
System.out.println(mySet);
System.out.println(myMap);
System.out.println(myProps);
}
}
bean.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="accountService" class="service.impl.IAccountServiceImpl">
<property name="myStrs">
<array>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</array>
</property>
<property name="myList">
<list>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</list>
</property>
<property name="mySet">
<set>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</set>
</property>
<property name="myMap">
<map>
<entry key="Julia" value="AAA"></entry>
<entry key="Julia2" value="BBB"></entry>
</map>
</property>
<property name="myProps">
<props>
<prop key="test">test</prop>
</props>
</property>
</bean>
</beans>
结论:结构相同,标签可以互换。
3、Spring基于注解的IOC
注解的配置:
- 用于创建对象的:他们的作用就和在XML配置文件中编写一个< bean>标签实现的功能是一样的。但是,当我们使用注解配置时,主要要在xml中告诉spring要扫描的包在哪里,也就是告诉spring用注解配置了的类在哪里。
- @Component:用于把当前类对象存入spring容器中。属性:value用于指定bean的id;当我们不写时,默认值是当前类名且首字母改小写。
- @Controller:一般用在表现层
- @Service:一般用在业务层
- @Repository:一般用在持久层
- 以上三个注解的作用和属性和Component是一模一样的。他们三个是spring框架为我们提供明确的三层使用的注解,使我们的三层对象更加清晰
bean.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: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">
<!--告知spring在创建容器时要扫描的包,配置所需要的标签不是在beans的约束中,而是一个名称为
context名称空间和约束中-->
<context:component-scan base-package="com"></context:component-scan>
</beans>
IAccountServiceImpl:
package com.service.impl;
import org.springframework.stereotype.Component;
import com.service.IAccountService;
@Component(value = "accountService")
public class IAccountServiceImpl implements IAccountService {
public void saveAccount() {
System.out.println("Service中的方法执行了...");
}
}
- 用于注入数据的:他们的作用就在xml配置文件中的bean标签中写一个property标签作用一样。
- Autowried:自动按照类型注入,只要容器中有唯一的一个bean对象,类型和要注入的变量类型匹配,就可以注入成功。如果ioc容器中没有任何bean的类型和要注入的变量类型匹配,则报错。如果ioc容器中有多个类型匹配时,这个时候会先根据类型找对应ioc容器中是否有相同类型的bean对象,如果有并且有多个,那么这时候会根据变量名来查找相应的id是否匹配,如果找到,则成功,否则则报错。出现位置:可以是成员变量,也可以是方法上。细节:在使用注解注入时,set方法就不是必须的了
IAccountServiceImpl:
package com.service.impl;
import com.dao.IAccountDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.service.IAccountService;
@Component(value = "accountService")
public class IAccountServiceImpl implements IAccountService {
@Autowired
private IAccountDao accountDao;
public void saveAccount() {
accountDao.saveAccount();
}
}
IAccountDaoImpl:
package com.dao.impl;
import com.dao.IAccountDao;
import org.springframework.stereotype.Repository;
@Repository("accountDao")
public class IAccountDaoImpl implements IAccountDao {
public void saveAccount() {
System.out.println("保存了用户");
}
}
- Qualifer:作用:在按照类型注入的基础之上再按照名称注入,它在给类成员注入时不能单独使用,要和Atowired一起使用。但是在给方法参数注入时可以。属性:value,用于指定注入bean的id。
IAccountServiceImpl:
package com.service.impl;
import com.dao.IAccountDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import com.service.IAccountService;
@Component(value = "accountService")
public class IAccountServiceImpl implements IAccountService {
@Autowired
@Qualifier("accountDao")
private IAccountDao accountDao;
public void saveAccount() {
accountDao.saveAccount();
}
}
- Resource:作用:直接按照bean的id注入。它可以独立使用,但是它的属性是name用于指定bean的id
- 以上三个注解都只能注入其他bean类型的数据,而基本类型和String类型无法使用上述注解实现。另外,集合类型的注入只能通过xml来实现
package com.service.impl;
import com.dao.IAccountDao;
import org.springframework.stereotype.Component;
import com.service.IAccountService;
import javax.annotation.Resource;
@Component(value = "accountService")
public class IAccountServiceImpl implements IAccountService {
@Resource(name="accountDao")
private IAccountDao accountDao;
public void saveAccount() {
accountDao.saveAccount();
}
}
-
@Value的作用:用于注入基本类型和String类型的数据。属性value用于指定数据的值。它可以使用spring中的SpEL(spring中的EL表达式。
- SpEL的写法:${表达式}
-
用于改变作用范围的:他们的作用就和在bean标签中使用scope属性是一样的
- @Scope:用于指定bean的作用范围,属性:value指定范围的取值。常用取值:singleton、prototype,不写默认情况下是单例的
package com.service.impl;
import com.dao.IAccountDao;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import com.service.IAccountService;
import javax.annotation.Resource;
@Component(value = "accountService")
@Scope("prototype")
public class IAccountServiceImpl implements IAccountService {
@Resource(name="accountDao")
private IAccountDao accountDao;
public void saveAccount() {
accountDao.saveAccount();
}
}
- 和生命周期相关的(了解):他们的作用就和在bean标签中使用init-method和destroy-method的作用是一样的
- PreDestroy:用于指定销毁方法
- PostConstruct:用于指定初始化方法
package com.service.impl;
import com.dao.IAccountDao;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import com.service.IAccountService;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
@Component(value = "accountService")
public class IAccountServiceImpl implements IAccountService {
@Resource(name="accountDao")
private IAccountDao accountDao;
@PostConstruct
public void init(){
System.out.println("初始化方法");
}
@PreDestroy
public void destroy(){
System.out.println("销毁方法");
}
public void saveAccount() {
accountDao.saveAccount();
}
}
Client:
package com.cli;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.service.IAccountService;
public class Client {
/**
* 获取spring的IOC核心容器,并根据id获取对象
* @param args
*/
public static void main(String[] args) {
//1、获取核心容器对象(其实就是我们之前写的map对象)
ClassPathXmlApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
//2、根据id获取bean对象
IAccountService as=(IAccountService) ac.getBean("accountService");
as.saveAccount();
ac.close();
}
}
3.1、基于XML的IOC案例
1、环境准备:导入依赖和创建account表
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.4</version>
</dependency>
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.7</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.23</version>
</dependency>
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.5</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
2、基本代码的书写(跟之前一样):domain-account、dao-IAccountDao、dao-impl-AccountDao、service-IAccountService、service-impl-AccountServiceImpl
3、(加上spring的IOC降低程序耦合性)创建bean.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">
<!--配置Service-->
<bean id="accountService" class="service.impl.AccountServiceImpl">
<!--注入dao-->
<property name="accountDao" ref="accountDao"></property>
</bean>
<!--配置dao-->
<bean id="accountDao" class="dao.impl.AccountDao">
<!--注入QueryRunner-->
<property name="runner" ref="runner"></property>
</bean>
<!--配置QueryRunner-->
<bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"> <!--加上prototype是为了不让多个dao使用同一个对象-->
<!--注入数据源-->
<constructor-arg name="ds" ref="dataSource"></constructor-arg>
</bean>
<!--配置数据源-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--连接数据库的必备信息-->
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/db6"></property>
<property name="user" value="root"></property>
<property name="password" value="root"></property>
</bean>
</beans>
测试配置:
package test;
import domain.Account;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import service.IAccountService;
import java.util.List;
//使用Junit单元测试测试配置
public class AccountServiceTest {
ClassPathXmlApplicationContext ac;
IAccountService as;
@Before
public void init(){
//获取容器
ac = new ClassPathXmlApplicationContext("bean.xml");
//得到业务层对象
as = (IAccountService)ac.getBean("accountService");
}
@Test
public void testFindAll(){
List<Account> accounts = as.findAllAccount();
for (Account account : accounts) {
System.out.println(account);
}
}
@Test
public void testFindById(){
Account account = as.findAccountById(1);
System.out.println(account);
}
@Test
public void testSave(){
Account account = new Account();
account.setName("Julia");
account.setMoney(10000);
as.saveAccount(account);
}
@Test
public void testUpdate(){
Account account = new Account();
account.setId(4);
account.setMoney(20000);
as.updateAccount(account);
}
@Test
public void testDelete(){
as.deleteAccount(4);
}
}
3.2、基于注解的IOC案例
1、bean.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: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">
<!--告知spring在创建容器时要扫描的包-->
<context:component-scan base-package="com"></context:component-scan>
<!--配置QueryRunner-->
<bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"> <!--加上prototype是为了不让多个dao使用同一个对象-->
<!--注入数据源-->
<constructor-arg name="ds" ref="dataSource"></constructor-arg>
</bean>
<!--配置数据源-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--连接数据库的必备信息-->
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/db6"></property>
<property name="user" value="root"></property>
<property name="password" value="root"></property>
</bean>
</beans>
AccountServiceImpl:
package com.service.impl;
import com.dao.IAccountDao;
import com.domain.Account;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.service.IAccountService;
import java.util.List;
@Service("accountService")
public class AccountServiceImpl implements IAccountService {
@Autowired
private IAccountDao accountDao;
public List<Account> findAllAccount() {
return accountDao.findAllAccount();
}
public Account findAccountById(int id) {
return accountDao.findAccountById(id);
}
public void saveAccount(Account account) {
accountDao.saveAccount(account);
}
public void updateAccount(Account account) {
accountDao.updateAccount(account);
}
public void deleteAccount(int id) {
accountDao.deleteAccount(id);
}
}
AccountDaoImpl:
package com.dao.impl;
import com.dao.IAccountDao;
import com.domain.Account;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import java.util.List;
//账户的持久层实现类
@Repository("accountDao")
public class AccountDao implements IAccountDao {
@Autowired
private QueryRunner runner;
public List<Account> findAllAccount() {
try {
return runner.query("select * from account", new BeanListHandler<Account>(Account.class));
}catch (Exception e) {
throw new RuntimeException(e);
}
}
public Account findAccountById(int id) {
try{
return runner.query("select * from account where id=?", new BeanHandler<Account>(Account.class), id);
}catch (Exception e){
throw new RuntimeException(e);
}
}
public void saveAccount(Account account) {
try {
runner.update("insert into account(name,money) values(?,?)",account.getName(),account.getMoney());
}catch (Exception e){
throw new RuntimeException(e);
}
}
public void updateAccount(Account account) {
try {
runner.update("update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
}catch (Exception e){
throw new RuntimeException(e);
}
}
public void deleteAccount(int id) {
try {
runner.update("delete from account where id=?",id);
}catch (Exception e){
throw new RuntimeException(e);
}
}
}
3.3、spring的新注解
我们发现,不论如何我们还是需要写bean.xml文件,那么还有没有简化呢?(也就是不需要写配置文件的方法)——创建一个配置类,作用和bean.xml一样
spring中的新注解:
- Configuration。作用:指定当前类是一个配置类。
- 细节:当配置类作为AnnotationConfigApplicationContext对象创建的参数时,该注解可以不写。
- ComponentScan:作用:通过注解指定spring在创建容器时要扫描的包。
- 属性1:value,它和basePackages的作用是一样的,都是用于指定创建容器时要扫描的包。我们使用此注解就等同于在xml中配置了 < context:component-scan base-package=“com”></ context:component-scan>
- Bean注解,用于把当前方法的返回值作为bean对象存入spring的ioc容器中
- 属性:name,用于指定bean的id,当不写时,默认值是当前方法的名称
- 细节:当我们使用注解配置方法时,如果方法有参数,spring框架会去容器中查找有没有可用的bean对象。查找的方式和Autowried注解一样的。
config.SpringConfiguration:
package com.config;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.apache.commons.dbutils.QueryRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
@Configuration
@ComponentScan("com")
public class SpringConfiguration {
/**
* 用于创建一个QueryRunner对象
* @param dataSource
* @return
*/
@Bean(name = "runner")
@Scope("prototype")
public QueryRunner createQueryRunner(DataSource dataSource){
return new QueryRunner(dataSource);
}
/**
* 创建数据源对象
* @return
*/
@Bean(name = "dataSource")
public DataSource createDataSource(){
try {
ComboPooledDataSource ds=new ComboPooledDataSource();
ds.setDriverClass("com.mysql.jdbc.Driver");
ds.setJdbcUrl("jdbc:mysql://localhost:3306/db6");
ds.setUser("root");
ds.setPassword("root");
return ds;
}catch (Exception e){
throw new RuntimeException(e);
}
}
}
测试配置:
package test;
import com.config.SpringConfiguration;
import com.domain.Account;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import com.service.IAccountService;
import java.util.List;
//使用Junit单元测试测试配置
public class AccountServiceTest {
AnnotationConfigApplicationContext ac;
IAccountService as;
@Before
public void init(){
//获取容器
ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
//得到业务层对象
as = (IAccountService)ac.getBean("accountService");
}
@Test
public void testFindAll(){
List<Account> accounts = as.findAllAccount();
for (Account account : accounts) {
System.out.println(account);
}
}
//测试QueryRunner是否单例
@Test
public void testQueryRunner(){
QueryRunner runner = ac.getBean("runner", QueryRunner.class);
QueryRunner runner2 = ac.getBean("runner", QueryRunner.class);
System.out.println(runner==runner2); //false
}
}
写了配置类后,bean.xml就不需要了
- Import注解:用于导入其他的配置类。属性value,用于指定其他配置类的字节码。当我们使用Import注解后,有Import注解的类就是主配置/父配置类,而导入的都是子配置类
继续发现问题:配置类中的下面这部分又被写死了
- @PropertiesSource注解:用于指定properties文件的位置
- 属性value:指定文件的名称和文件的路径。关键字:classpath表示在类路径下.
1、写jdbcConfig.properties配置文件:
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/db6
jdbc.username=root
jdbc.password=root
2、改写配置类:
package com.config;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.apache.commons.dbutils.QueryRunner;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.*;
import javax.sql.DataSource;
@Configuration
@ComponentScan("com")
@PropertySource("classpath:jdbcConfig.properties")
public class SpringConfiguration {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
/**
* 用于创建一个QueryRunner对象
* @param dataSource
* @return
*/
@Bean(name = "runner")
@Scope("prototype")
public QueryRunner createQueryRunner(DataSource dataSource){
return new QueryRunner(dataSource);
}
/**
* 创建数据源对象
* @return
*/
@Bean(name = "dataSource")
public DataSource createDataSource(){
try {
ComboPooledDataSource ds=new ComboPooledDataSource();
ds.setDriverClass(driver);
ds.setJdbcUrl(url);
ds.setUser(username);
ds.setPassword(password);
return ds;
}catch (Exception e){
throw new RuntimeException(e);
}
}
}
3.4、spring整合Junit
提出问题:在MyTest中,发现了很多重复代码,而下面这些代码是测试工程师不想要看见的,他并不想知道你是怎么创建的,他只是想生成测试报告。因此,我们要怎么做才能简化呢?
ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
IAccountService as = ac.getBean("accountService", IAccountService.class);
1、导入spring整合junit的jar包(坐标)
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.4</version>
</dependency>
2、使用junit提供的一个注解把原有的main方法替换了,替换成spring提供的:@Runwith
3、告知spring的运行器,spring的ioc创建是基于xml还是注解的,并且说明位置。@ContextConfiguration。locations:指定xml文件的位置,加上classpath的关键字,表示在类路径下。classes:指定注解类所在的位置。
package test;
import com.config.SpringConfiguration;
import com.domain.Account;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import com.service.IAccountService;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.List;
//使用Junit单元测试测试配置
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
public class AccountServiceTest {
@Autowired
private IAccountService as=null;
@Test
public void testFindAll(){
List<Account> accounts = as.findAllAccount();
for (Account account : accounts) {
System.out.println(account);
}
}
@Test
public void testFindById(){
Account account = as.findAccountById(1);
System.out.println(account);
}
@Test
public void testSave(){
Account account = new Account();
account.setName("Julia");
account.setMoney(10000);
as.saveAccount(account);
}
@Test
public void testUpdate(){
Account account = new Account();
account.setId(4);
account.setMoney(20000);
as.updateAccount(account);
}
@Test
public void testDelete(){
as.deleteAccount(4);
}
}
4、转账案例
1、导入坐标:pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>spring_test4</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.4</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.4</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.23</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.7</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.5</version>
</dependency>
</dependencies>
</project>
2、dao-IAccountDao:
package com.dao;
import com.domain.Account;
import java.util.List;
public interface IAccountDao {
List<Account> findAll();
Account findById(int id);
void saveAccount(Account account);
void deleteAccount(int id);
void updateAccount(Account account);
Account findAccountByName(String name);
}
3、dao-impl-AccountDaoImpl:
package com.dao.impl;
import com.dao.IAccountDao;
import com.domain.Account;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository("accountDao")
public class AccountDao implements IAccountDao {
@Autowired
private QueryRunner runner;
public List<Account> findAll() {
try{
return runner.query("select *from account",new BeanListHandler<Account>(Account.class));
}catch (Exception e){
throw new RuntimeException(e);
}
}
public Account findById(int id) {
try {
return runner.query("select * from account where id=?",new BeanHandler<Account>(Account.class),id);
}catch (Exception e){
throw new RuntimeException(e);
}
}
public void saveAccount(Account account) {
try {
runner.update("insert into account(name,money) values(?,?)",account.getName(),account.getMoney());
}catch (Exception e){
throw new RuntimeException(e);
}
}
public void deleteAccount(int id) {
try {
runner.update("delete from account where id=?",id);
}catch (Exception e){
throw new RuntimeException(e);
}
}
public void updateAccount(Account account) {
try {
runner.update("update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
}catch (Exception e){
throw new RuntimeException(e);
}
}
/**
* 根据名称查找用户
* @param name
* @return 如果有唯一的结果就返回,如果没有则返回null,如果超过一个就抛异常
*/
public Account findAccountByName(String name) {
try {
List<Account> accounts= runner.query("select * from account where name=?",new BeanListHandler<Account> (Account.class),name);
if (accounts==null||accounts.size()==0){
return null; //没有结果
}
if (accounts.size()>1){
throw new RuntimeException("结果集不唯一,数据有问题");
}
return accounts.get(0);
}catch (Exception e){
throw new RuntimeException(e);
}
}
}
4、service-IAccountService:
package com.service;
import com.domain.Account;
import java.util.List;
public interface IAccountService {
List<Account> findAll();
Account findById(int id);
void saveAccount(Account account);
void deleteAccount(int id);
void updateAccount(Account account);
/**
* 转账
* @param sourceName 转出账户名称
* @param targetName 转入账户名称
* @param money 转账金额
*/
void tranfer(String sourceName,String targetName,Float money);
}
5、service-impl-AccountServiceImpl:
package com.service.impl;
import com.dao.IAccountDao;
import com.domain.Account;
import com.service.IAccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service("accountService")
public class AccountServiceImpl implements IAccountService {
@Autowired
private IAccountDao accountDao;
public List<Account> findAll() {
return accountDao.findAll();
}
public Account findById(int id) {
return accountDao.findById(id);
}
public void saveAccount(Account account) {
accountDao.saveAccount(account);
}
public void deleteAccount(int id) {
accountDao.deleteAccount(id);
}
public void updateAccount(Account account) {
accountDao.updateAccount(account);
}
public void tranfer(String sourceName, String targetName, Float money) {
//1、根据名称查询转出账户
Account source = accountDao.findAccountByName(sourceName);
//2、根据名称查询转入账户
Account target = accountDao.findAccountByName(targetName);
//3、转出账户减钱,转入账户加钱
source.setMoney(source.getMoney()-money);
target.setMoney(target.getMoney()+money);
//4、更新转入和转出账户
accountDao.updateAccount(source);
accountDao.updateAccount(target);
}
}
6、bean.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: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 https://www.springframework.org/schema/context/spring-context.xsd">
<!--告知spring在容器创建时要扫描的包-->
<context:component-scan base-package="com"></context:component-scan>
<!--配置QueryRunner-->
<bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
<!--注入数据-->
<constructor-arg name="ds" ref="dataSource"></constructor-arg>
</bean>
<!--配置数据源-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--注入连接数据库必备信息-->
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/db6"></property>
<property name="user" value="root"></property>
<property name="password" value="root"></property>
</bean>
</beans>
7、test:
package test;
import com.domain.Account;
import com.service.IAccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.List;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class SpringTest {
@Autowired
private IAccountService as;
@Test
public void testFindAll(){
List<Account> accounts = as.findAll();
for (Account account : accounts) {
System.out.println(account);
}
}
@Test
public void testTranfer(){
as.tranfer("aaa","bbb",100f);
}
}
发现问题:很明显的,上面的转账案例会出现事务的问题,也就是一方转出了,一方没有受到。在我们的案例中,有四个connection,也就是每一个connection是一个独立的事务,也就是一个connection成功了,提交一个事务。所以,我们要实现的是,四个要么一起成功,要么一起失败,也就是要这四个操作使用同一个connection。
解决方案:(多次操作使用同一个事务)需要使用ThreadLocal对象把Connection和当前线程绑定,从而使一个线程中只有一个能控制事务的对象。
创建两个工具类:
1、ConnectionUtils:
package com.utils;
import javax.sql.DataSource;
import java.sql.Connection;
//连接的工具类,它用于从数据源中获取一个连接,并且实现和线程的绑定
public class ConnectionUtils {
private ThreadLocal<Connection> tl=new ThreadLocal<Connection>();
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
/**
* 获取当前线程上的连接
* @return
*/
public Connection getThreadConnection(){
try {
//1、先从ThreadLocal上获取
Connection conn=tl.get();
//2、判断当前线程上是否有连接
if(conn==null){
//3、从数据源中获取一个连接并且存入ThreadLocal中
conn=dataSource.getConnection();
//4、把conn存入ThreadLocal
tl.set(conn);
}
//返回当前线程上的连接
return conn;
}catch (Exception e){
throw new RuntimeException(e);
}
}
/**
* 把连接和线程解绑
*/
public void removeConnection(){
tl.remove();
}
}
2、TransctionManger:
package com.utils;
/**
* 和事务管理相关的工具类,它包含了开启事务,提交事务,回滚事务和释放连接
*/
public class TransctionManger {
private ConnectionUtils connectionUtils;
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
/**
* 开启事务
*/
public void beginTransaction(){
try {
connectionUtils.getThreadConnection().setAutoCommit(false);
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 提交事务
*/
public void commit(){
try {
connectionUtils.getThreadConnection().commit();
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 回滚事务
*/
public void rollback(){
try {
connectionUtils.getThreadConnection().rollback();
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 释放连接
*/
public void release(){
try {
connectionUtils.getThreadConnection().close(); //还回了连接池中
connectionUtils.removeConnection(); //解绑
}catch (Exception e){
e.printStackTrace();
}
}
}
但是,使用上面的事务控制方式十分麻烦,需要在配置文件中注入很多信息,并且存在很多依赖关系,不仅是类之间的依赖,还有方法之间的依赖。因此,引出了动态代理实现事务控制。
4.1、代理模式回顾
动态代理的特点:字节码随用随创建,随用随加载
动态代理的作用:不修改源码的基础上对方法的增强
动态代理的分类:基于接口的动态代理、基于子类的动态代理
4.1.1 基于接口的动态代理
如何创建代理对象:使用Proxy类中的方法newProxyInstance方法
创建代理对象的要求:被代理类最少实现一个接口,如果没有,则不能使用
newProxyInstance方法的参数:
- ClassLoader:类加载器。他是用于加载代理对象字节码的。和被代理对象使用相同的类加载器
- Class[]:字节码数组,用于让代理对象和被代理对象有相同的方法
- InvocationHandler:用于提供增强的代码。让我们写如何代理。我们一般写该接口的实现类,通常情况下都是匿名内部类。
生产商-代理商-消费者:
IProducer:
package proxy;
public interface IProducer {
public void saleProduct(float money);
public void afterService(float money);
}
Producer:
package proxy;
//一个生产厂家必须满足IProduct接口,也就是必须有saleProduct和afterService两个方法
public class Producer implements IProducer {
public void saleProduct(float money){
System.out.println("生产商卖出商品并拿到钱:"+money);
}
public void afterService(float money){
System.out.println("生产商维修商品并拿到钱"+money);
}
}
Client:
package proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class Client {
public static void main(String[] args) {
final Producer producer = new Producer();
//没有使用动态代理的方式
/*producer.saleProduct(10000f);*/
//使用动态代理
IProducer saler=(IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(),
producer.getClass().getInterfaces(),
new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object returnValue = null;
//获取执行方法的参数
Float money = (Float) args[0];
//判断当前的方法是不是销售
if ("saleProduct".equals(method.getName())) {
returnValue = method.invoke(producer, money * 0.8f);
}
return returnValue;
}
});
saler.saleProduct(10000f);
}
}
4.1.2、基于子类的动态代理
我们会发现,基于接口的动态代理,如果被代理类没有实现任何接口,那么是无法实现动态代理的,所以我们可以使用基于子类的动态代理:(代理一个普通的java类)但是这种动态代理要求有第三方jar包的支持
如何创建代理对象:使用Enhancer类中的方法create
创建代理对象的要求:被代理类不能是最终类
create方法的参数:
- Class:字节码。用于指定被代理对象的字节码
- Callback:用于提供增强。我们一般写的都是该接口的子接口实现类:MethodIntercepter
1、导入坐标
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
2、改写Client:此时不需要IProduct
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class Client {
public static void main(String[] args) {
final Producer producer = new Producer();
//没有使用动态代理的方式
/*producer.saleProduct(10000f);*/
//使用动态代理
Producer saler=(Producer)Enhancer.create(producer.getClass(), new MethodInterceptor() {
/**
* 执行任何被代理对象的方法都会经过该方法
* @param proxy
* @param method
* @param args
* 以上三个参数和基于接口的动态代理中invoke方法的参数是一样的
* @param methodProxy 当前执行方法的代理对象
* @return
* @throws Throwable
*/
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object returnValue=null;
Float money=(Float)args[0];
//判断当前方法是不是saleProduct
if ("saleProduct".equals(method.getName())){
returnValue=method.invoke(producer,money*0.8f);
}
return returnValue;
}
});
saler.saleProduct(10000f);
}
}
5、Spring中的AOP
AOP的概念:(Aspect Oriented Programming)面向切面编程,是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各个部分间的耦合性降低,提高程序的可重用性,同时提高开发的效率。
AOP的作用:在运行期间,不修改源码对已有方法进行增强
AOP优势:减少重复代码;提高开发效率,维护方便
我们上面实现事务控制的原理,其实是让一个线程只有一个conn,并通过动态代理的方式增强了原方法并减少代码的冗余。而spring中的AOP实现事务是通过配置文件实现了上面的方法。
AOP 相关术语:
- JoinPoint(连接点):是指那些被拦截的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点。(简单来说就是业务逻辑层中的接口中的方法)
- Pointcut(切入点):是指我们要对哪些Joinpoint进行拦截的定义。(业务逻辑接口中被增强的方法)
- Advice(通知/增强):所谓通知是指拦截到JoinPoint之后要做的事情。包括前置通知,后置通知,异常通知,最终通知和环绕通知
- Introduction(引介):是一种特殊的通知,在不修改类代码的前提下,引介可以在运行期为类动态的添加一些方法或Field
- Target(目标对象):被代理对象
- Waving(织入):返回代理对象加入了事务的支持(增强了方法)的过程
- Proxy(代理):产生的代理类
- Aspect(切面):是切入点和通知的结合。(在配置文件中说明哪些方法需要增强,通知哪些方法什么时候执行)
spring在运行阶段:spring会监控切入点方法的执行,一旦监视到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。
5.1、spring基于XML的AOP配置
1、导入坐标
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>
2、IAccountService:
package service;
/**
* 账户的业务层接口
*/
public interface IAccountService {
//模拟保存账户
void saveAccount();
//模拟更新账户
void updateAccount(int i);
//模拟删除账户
int deleteAccount();
}
3、AccountServiceImpl:
package service.impl;
import service.IAccountService;
/**
* 账户的业务层实现类
*/
public class AccountServiceImpl implements IAccountService {
public void saveAccount() {
System.out.println("执行了保存账户");
}
public void updateAccount(int i) {
System.out.println("执行了更新"+i);
}
public int deleteAccount() {
System.out.println("执行了删除");
return 0;
}
}
4、main-java-utils-Loger:
package utils;
/**
* 用于记录日志的工具类,他里面提供了公共的代码
*/
public class Loger {
/**
* 用于打印日志,计划让其在切入点方法执行之前执行(切入层方法就是业务层方法)
* 我们之前的方法是创建accountService的代理对象,在执行方法之前调用这个方法
*/
public void printLog(){
System.out.println("Logger类中的printLog方法开始记录日志");
}
}
5、bean.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.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--配置spring的ioc,把service对象配置进来-->
<bean id="accountService" class="service.impl.AccountServiceImpl"></bean>
<!--spring中基于xml的AOP配置
1、把通知Bean也交给spring来管理
2、使用aop:config标签表明开始aop的配置
3、使用aop:aspect标签表明配置切面
id属性:给切面提供一个唯一标志
ref属性:指定通知类bean的id
4、在aop:aspect标签内部使用对应的标签来配置通知的类型
我们现在的事例是让printLog方法在切入点方法执行之前执行:所以是前置通知,aop:before表示配置前置通知
method属性:用于指定Loger类中哪个方法是前置通知
pointcut属性:用于指定切入点表达式,该表达式的含义指的是对业务层中哪些方法增强
切入点表达式的写法:
关键字:execution(表达式)
表达式:
访问修饰符 返回值 包名.包名.类名 方法名(参数列表)
标准的表达式写法:
public void service.impl.AccountServiceImpl.saveAccount()
访问修饰符可以省略
void service.impl.AccountServiceImpl.saveAccount()
返回值可以使用通配符表示任意返回值
* service.impl.AccountServiceImpl.saveAccount()
包名可以使用通配符表示任意包,但是有几级包就需要写几个*.
* *.*.AccountServiceImpl.saveAccount()
包名可以使用..表示当前包及其子包
* *..AccountServiceImpl.saveAccount()
类名和方法名都可以使用* 来实现通配
* *..*.*()
参数列表可以直接写数据类型,基本类型写名称;引用类型写包名.类名的方式;
也可以使用通配符表示任意类型,但是必须有参数。
可以使用..表示有无参数均可,并可以是任意类型
全通配写法:* *..*.*(..)
实际开发中切入点表达式的通常写法:
切到业务层实现类的所有方法
* service.impl.*.*(..)
-->
<!--配置Loger类-->
<bean id="logger" class="utils.Loger"></bean>
<!--配置AOP-->
<aop:config>
<!--配置切面-->
<aop:aspect id="logAdvice" ref="logger">
<!--配置通知的类型并且建立通知方法和切入点方法的关联-->
<aop:before method="printLog" pointcut="execution(public void service.impl.AccountServiceImpl.saveAccount())"></aop:before>
</aop:aspect>
</aop:config>
</beans>
6、测试AOP:
package test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import service.IAccountService;
//测试AOP的配置
public class AOPTest {
public static void main(String[] args) {
//1、获取容器
ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2、获取对象
IAccountService as = ac.getBean("accountService", IAccountService.class);
as.saveAccount();
}
}
5.1.2、四种常用的通知类型
前置通知(before):在切入点方法执行之前执行、
后置通知(after-returning):在切入点方法正常执行之后执行,他和异常通知永远只能执行一个、
异常通知(after-throwing):在切入点方法执行产生异常之后执行、
最终通知(after):无论切入点方法是否正常执行,都会在其后面执行
main-java-utils-Logger:
package utils;
public class Loger {
/**
* 前置通知
*/
public void beforePrintLog(){
System.out.println("打印前置通知...");
}
/**
* 后置通知
*/
public void afterPrintLog(){
System.out.println("打印后置通知...");
}
/**
* 异常通知
*/
public void afterThrowingPrintLog(){
System.out.println("打印异常通知...");
}
/**
* 最终通知
*/
public void finalPrintLog(){
System.out.println("打印最终通知...");
}
}
bean.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.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--配置spring的ioc,把service对象配置进来-->
<bean id="accountService" class="service.impl.AccountServiceImpl"></bean>
<!--配置Loger类-->
<bean id="logger" class="utils.Loger"></bean>
<!--配置AOP-->
<aop:config>
<!--配置切面-->
<aop:aspect id="logAdvice" ref="logger">
<!--配置通知的类型并且建立通知方法和切入点方法的关联-->
<!--配置前置通知-->
<aop:before method="beforePrintLog" pointcut="execution(* service.impl.*.*(..))"></aop:before>
<!--配置后置通知-->
<aop:after-returning method="afterPrintLog" pointcut="execution(* service.impl.*.*(..))"></aop:after-returning>
<!--配置异常通知-->
<aop:after-throwing method="afterThrowingPrintLog" pointcut="execution(* service.impl.*.*(..))"></aop:after-throwing>
<!--配置最终通知-->
<aop:after method="finalPrintLog" pointcut="execution(* service.impl.*.*(..))"></aop:after>
</aop:aspect>
</aop:config>
</beans>
5.1.3、通用化切入点表达式
我们发现上面的通用表达式太冗余了,因此可以通用化配置切入点表达式:
<!--配置AOP-->
<aop:config>
<!--配置切面-->
<aop:aspect id="logAdvice" ref="logger">
<!--配置通知的类型并且建立通知方法和切入点方法的关联-->
<!--配置前置通知-->
<aop:before method="beforePrintLog" pointcut-ref="pc"></aop:before>
<!--配置后置通知-->
<aop:after-returning method="afterPrintLog" pointcut-ref="pc"></aop:after-returning>
<!--配置异常通知-->
<aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pc"></aop:after-throwing>
<!--配置最终通知-->
<aop:after method="finalPrintLog" pointcut-ref="pc"></aop:after>
<!--配置切入点表达式,id属性用于指定表达式的唯一标识。expression属性用于指定表达式内容
此标签写在切面标签aop-aspect内部,只能当前切面使用。
他还可以写在aop:aspect的外面,此时就变成了所有切面可用-->
<aop:pointcut id="pc" expression="execution(* service.impl.*.*(..))"/>
</aop:aspect>
</aop:config>
5.1.4、环绕通知
main-java-utils-Logger:
package utils;
import org.aspectj.lang.ProceedingJoinPoint;
public class Loger {
/**
* 环绕通知
* 问题:当我们配置环绕通知后,切入点方法没有执行,而通知方法执行了
* 分析:通过对比动态代理中的环绕通知代码,发现动态代理的环绕通知有明确的切入点方法调用,而我们的代码没有
* 解决:
* spring框架为我们提供了一个接口:ProceedingJoinPoint,该接口有一个方法proceed(),此方法就相当于明确调用切入点方法
* 该接口可以作为环绕通知的方法参数,在程序执行时,spring框架会为我们提供该接口的实现类供我们使用
* spring中的环绕通知:
* 他是spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式,
*/
public Object aroundPrintLog(ProceedingJoinPoint pjp){
Object returnValue=null;
try {
//获取执行方法所需的参数
Object[] args = pjp.getArgs();
System.out.println("打印前置通知...");
returnValue=pjp.proceed(args); //明确调用业务层方法(切入点方法)
System.out.println("打印后置通知...");
return returnValue;
}catch (Throwable t){
System.out.println("打印异常通知...");
throw new RuntimeException(t);
}finally {
System.out.println("打印最终通知...");
}
}
}
bean.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.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--配置spring的ioc,把service对象配置进来-->
<bean id="accountService" class="service.impl.AccountServiceImpl"></bean>
<!--配置Loger类-->
<bean id="logger" class="utils.Loger"></bean>
<!--配置AOP-->
<aop:config>
<!--配置切入点表达式,id属性用于指定表达式的唯一标识。expression属性用于指定表达式内容
此标签写在切面标签aop-aspect内部,只能当前切面使用。
他还可以写在aop:aspect的前外面,此时就变成了所有切面可用-->
<aop:pointcut id="pc" expression="execution(* service.impl.*.*(..))"/>
<!--配置切面-->
<aop:aspect id="logAdvice" ref="logger">
<!--配置环绕通知-->
<aop:around method="aroundPrintLog" pointcut-ref="pc"></aop:around>
</aop:aspect>
</aop:config>
</beans>
5.2、spring基于注解的AOP配置
1、将bean.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"
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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 配置spring创建容器时要扫描的包-->
<context:component-scan base-package="service,utils"></context:component-scan>
<!-- 配置spring开启注解AOP的支持 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
2、AccountServiceImpl(也用注解配置的方式):
package service.impl;
import org.springframework.stereotype.Service;
import service.IAccountService;
/**
* 账户的业务层实现类
*/
@Service("accountService")
public class AccountServiceImpl implements IAccountService {
public void saveAccount() {
System.out.println("执行了保存账户");
}
public void updateAccount(int i) {
System.out.println("执行了更新"+i);
}
public int deleteAccount() {
System.out.println("执行了删除");
return 0;
}
}
3、logger:(采用注解)
package utils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* 用于记录日志的工具类,他里面提供了公共的代码
*/
@Component("logger")
@Aspect //表示当前类是一个切面类
public class Loger {
//指定切入点表达式
@Pointcut("execution(* service.impl.*.*(..))")
private void pt(){}
/**
* 前置通知
*/
@Before("pt()")
public void beforePrintLog(){
System.out.println("打印前置通知...");
}
/**
* 后置通知
*/
@AfterReturning("pt()")
public void afterPrintLog(){
System.out.println("打印后置通知...");
}
/**
* 异常通知
*/
@AfterThrowing("pt()")
public void afterThrowingPrintLog(){
System.out.println("打印异常通知...");
}
/**
* 最终通知
*/
@After("pt()")
public void finalPrintLog(){
System.out.println("打印最终通知...");
}
/**
* 环绕通知
*/
@Around("pt()")
public Object aroundPrintLog(ProceedingJoinPoint pjp){
Object returnValue=null;
try {
//获取执行方法所需的参数
Object[] args = pjp.getArgs();
System.out.println("打印前置通知...");
returnValue=pjp.proceed(args); //明确调用业务层方法(切入点方法)
System.out.println("打印后置通知...");
return returnValue;
}catch (Throwable t){
System.out.println("打印异常通知...");
throw new RuntimeException(t);
}finally {
System.out.println("打印最终通知...");
}
}
}
6、spring中的JdbcTemplate
JdbacTemplate的概述:他是spring框架中提供的一个对象,是对 原始Jdbc API对象的简单封装,spring框架为我们提供了很多的操作模板类:
操作关系型数据的:JdbcTemplate、HibernateTemplate
操作nosql数据库的:RedisTemplate
操作消息队列的:JmsTemplate
JdbcTemplate的作用:它就是用于和数据库交互的,实现对表的CRUD操作
如何创建该对象:
1、导入坐标:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>spring_jdbc1</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.4</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.5</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.3.5</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.23</version>
</dependency>
</dependencies>
</project>
2、在数据库中创建表account和创建实体类main-java-domain-Account
3、测试类JdbcTemplateDemo1:
package jdbcTemplate;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
/**
* jdbcTemplate的最基本用法
*/
public class JdbcTemplateDemo1 {
public static void main(String[] args) {
//准备数据源:spring的内置数据源
DriverManagerDataSource ds=new DriverManagerDataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/db6");
ds.setUsername("root");
ds.setPassword("root");
//创建对象
JdbcTemplate jt = new JdbcTemplate(ds);
//执行操作
jt.execute("insert into account(name,money) values ('ccc',1000f)");
}
}
发现问题:上面程序中的对象是通过new处出来的,对象之间的耦合度高,并且上面数据源的参数都是写死的,可以通过配置文件和利用ioc来改善:
1、bean.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:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<!--配置持久层-->
<bean id="accountDao" class="dao.impl.AccountDao">
<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://localhost:3306/db6"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</bean>
</beans>
2、创建dao-IAccount和dao-impl-AccountDaoImpl:
package dao.impl;
import dao.IAccountDao;
import domain.Account;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import java.util.List;
public class AccountDao implements IAccountDao {
private JdbcTemplate jdbcTemplate;
//使用xml注解配置的方式为我们注入jdbcTemplate对象
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public List<Account> findAll() {
return jdbcTemplate.query("select * from account",new BeanPropertyRowMapper<Account>(Account.class));
}
public Account findById(Integer id) {
List<Account> account = jdbcTemplate.query("select * from account where id=?", new BeanPropertyRowMapper<Account>(Account.class), id);
return account.get(0);
}
public Account findByName(String name) {
List<Account> accounts = jdbcTemplate.query("select * from account where name=?", new BeanPropertyRowMapper<Account>(Account.class), name);
if (accounts.isEmpty())
return null;
if(accounts.size()>1){
throw new RuntimeException("结果集不唯一");
}
return accounts.get(0);
}
public void updateAccount(Account account) {
jdbcTemplate.update("update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
}
public void saveAccount(Account account) {
jdbcTemplate.update("insert into account(name,money) values (?,?)",account.getName(),account.getMoney());
}
public void deleteAccount(Integer id) {
jdbcTemplate.update("delete from account where id=?",id);
}
public void countAccount() {
jdbcTemplate.queryForObject("select count(*) from account where money>?",Integer.class,1000f);
}
}
3、测试类:
package jdbcTemplate;
import dao.IAccountDao;
import domain.Account;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.util.List;
/**
* jdbcTemplate的CRUD操作
*/
public class JdbcTemplateDemo1 {
public static void main(String[] args) {
//获取容器
final ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
IAccountDao accountDao = ac.getBean("accountDao", IAccountDao.class);
List<Account> accounts = accountDao.findAll();
for (Account account : accounts) {
System.out.println(account);
}
}
}
JdbcDaoSupport的使用和Dao的两种编写方式:
由于AccountDao中存在重复代码,因此我们会想办法将重复代码减少:
1、创建main-创建java-dao-impl-JdbcDaoSupport:
package dao.impl;
import org.springframework.jdbc.core.JdbcTemplate;
import javax.sql.DataSource;
/**
* 此类用于抽取Dao中的重复代码
*/
public class JdbcDaoSupport {
private JdbcTemplate jdbcTemplate;
//使用xml注解配置的方式为我们注入jdbcTemplate对象
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public JdbcTemplate getJdbcTemplate() {
return jdbcTemplate;
}
public void setDataSource(DataSource dataSource) {
if(jdbcTemplate==null){
jdbcTemplate=createJdbcTemplate(dataSource);
}
}
private JdbcTemplate createJdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
}
2、AccountDaoImpl:
package dao.impl;
import dao.IAccountDao;
import domain.Account;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import java.util.List;
public class AccountDao extends JdbcDaoSupport implements IAccountDao {
public List<Account> findAll() {
return getJdbcTemplate().query("select * from account",new BeanPropertyRowMapper<Account>(Account.class));
}
public Account findById(Integer id) {
List<Account> account = getJdbcTemplate().query("select * from account where id=?", new BeanPropertyRowMapper<Account>(Account.class), id);
return account.get(0);
}
public Account findByName(String name) {
List<Account> accounts = getJdbcTemplate().query("select * from account where name=?", new BeanPropertyRowMapper<Account>(Account.class), name);
if (accounts.isEmpty())
return null;
if(accounts.size()>1){
throw new RuntimeException("结果集不唯一");
}
return accounts.get(0);
}
public void updateAccount(Account account) {
getJdbcTemplate().update("update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
}
public void saveAccount(Account account) {
getJdbcTemplate().update("insert into account(name,money) values (?,?)",account.getName(),account.getMoney());
}
public void deleteAccount(Integer id) {
getJdbcTemplate().update("delete from account where id=?",id);
}
public void countAccount() {
getJdbcTemplate().queryForObject("select count(*) from account where money>?",Integer.class,1000f);
}
}
3、bean.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:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<!--配置持久层-->
<bean id="accountDao" class="dao.impl.AccountDao">
<!--<property name="jdbcTemplate" ref="jdbcTemplate"></property>-->
<!--当配置了JdbcDaoSupport后,就不需要这样注入了,直接注入DataSource就可以注入jdbcTemplate了-->
<property name="dataSource" ref="dataSource"></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://localhost:3306/db6"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</bean>
</beans>
但是实际上,spring已经帮我们写好了JdbcDaoSupport了:不用自己写JdbcDaoSupport了,spring会帮我们导入。
7、AOP实现事务控制
7.1、spring基于XML的声明式事务控制
spring基于XML的声明式事务控制配置步骤:
- 配置事务管理器
- 配置事务的通知。此时我们需要导入事务的约束,tx名称空间和约束,同时也需要aop的。
- 使用tx:advice标签实现事务的实现事务的通知。属性id表示给事务通知起一个唯一标志。transaction-manager属性表示给事务通知提供一个事务管理器引用
- 使用tx:advice标签实现事务的实现事务的通知。属性id表示给事务通知起一个唯一标志。transaction-manager属性表示给事务通知提供一个事务管理器引用
<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:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.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">
</beans>
- 配置AOP中的通用切入点表达式
- 建立事务通知和切入点表达式的对应关系
- 配置事务的属性,在事务的通知tx:advice标签的内部
- isolation="":用于指定事务的隔离级别。默认值是DEFAULT,表示使用数据库的默认隔离级别
- no-rollback-for="":用于指定一个异常,当产生该异常时,事务不回滚,当产生其他异常时,事务回滚。没有默认值,表示任何异常都回滚。
- propagation="":用于指定事务的传播行为,默认值是REQUIRED,表示一定会有事务,增删改的选择,查询方法可以选择SUPPORTS
- read-only="":用于指定事务是否可读,只有查询方法可以设置为true,默认值是false表示读写。
- rollback-for="":用于指定一个异常,当产生该异常时,事务回滚。产生其他异常时,事务不会回滚,没有默认值,表示任何异常都回滚
- timeout="":用于指定事务的超时时间,默认值是-1,表示永不超时。如果指定了数值,以秒为单位
pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>tx</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.4</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.5</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.3.5</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.23</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>
</dependencies>
</project>
main-java-domain-Account:
package domain;
import java.io.Serializable;
public class Account implements Serializable {
private Integer id;
private String name;
private Float money;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Float getMoney() {
return money;
}
public void setMoney(Float money) {
this.money = money;
}
@Override
public String toString() {
return "Account{" +
"id=" + id +
", name='" + name + '\'' +
", money=" + money +
'}';
}
}
main-java-dao-IAccountDao:
package dao;
import domain.Account;
public interface IAccountDao {
Account findById(Integer id);
Account findByName(String name);
void updateAccount(Account account);
}
main-java-dao-impl-AccountDaoImpl:
package dao.impl;
import dao.IAccountDao;
import domain.Account;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import java.util.List;
public class AccountDaoImpl implements IAccountDao {
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public Account findById(Integer id) {
List<Account> accounts = jdbcTemplate.query("select * from account where id=?", new BeanPropertyRowMapper<Account>(Account.class), id);
//因为根据id查询,要么有一个,要么没有
return accounts.isEmpty()?null:accounts.get(0);
}
public Account findByName(String name) {
List<Account> accounts = jdbcTemplate.query("select * from account where name=?", new BeanPropertyRowMapper<Account>(Account.class), name);
if(accounts.isEmpty()){
return null;
}
if(accounts.size()>1){
throw new RuntimeException("结果集不唯一");
}
return accounts.get(0);
}
public void updateAccount(Account account) {
jdbcTemplate.update("update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
}
}
main-java-service-IAccountService:
package service;
import domain.Account;
public interface IAccountService {
void transfer(String sourceName,String targetName,Float money);
Account findById(Integer id);
}
main-java-service-impl-AccountServiceImpl:
package service.impl;
import dao.IAccountDao;
import domain.Account;
import service.IAccountService;
public class AccountServiceImpl implements IAccountService {
private IAccountDao accountDao;
public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}
public void transfer(String sourceName, String targetName, Float money) {
//根据姓名查找
Account source = accountDao.findByName(sourceName);
Account target = accountDao.findByName(targetName);
//修改金额
source.setMoney(source.getMoney()-money);
target.setMoney(target.getMoney()+money);
/*int a=1/0;*/ //故意制造一个错误测试事务控制
//修改数据库
accountDao.updateAccount(source);
accountDao.updateAccount(target);
}
public Account findById(Integer id) {
return accountDao.findById(id);
}
}
bean.xml:
<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:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.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">
<!--配置accountService-->
<bean id="accountService" class="service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
<!--配置AccountDao-->
<bean id="accountDao" class="dao.impl.AccountDaoImpl">
<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://localhost:3306/db6"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</bean>
<!--spring中基于xml的声明式事务控制-->
<!--配置事务管理器,里面有事务的提交,回滚等操作-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置事务的通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!--配置事务的属性-->
<tx:attributes>
<tx:method name="*" propagation="REQUIRED" read-only="false"/>
<tx:method name="find*" propagation="SUPPORTS" read-only="true"></tx:method>
</tx:attributes>
</tx:advice>
<!--配置AOP-->
<aop:config>
<!--配置AOP中的通用切入点表达式-->
<aop:pointcut id="pt1" expression="execution(* service.impl.*.*(..))"/>
<!--建立事务通知和切入点表达式的对应关系-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor>
</aop:config>
</beans>
MyTest:
package test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import service.IAccountService;
public class MyTest {
public static void main(String[] args) {
//获取IOC容器
ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
IAccountService as = ac.getBean("accountService", IAccountService.class);
as.transfer("aaa","bbb",100f);
}
}
7.2、spring基于注解的声明式事务控制
1、导入aop基于注解的声明式事务控制的约束
<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:tx="http://www.springframework.org/schema/tx"
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/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
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
</beans>
2、spring中基于注解的声明式事务控制:
- 配置事务管理器
- 开启spring对注解事务的支持
- 在需要事务的支持的地方使用@Transactional注解
bean.xml:
<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:tx="http://www.springframework.org/schema/tx"
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/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
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--配置spring创建ioc容器时要扫描的包-->
<context:component-scan base-package="service,dao"></context:component-scan>
<!--配置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://localhost:3306/db6"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</bean>
<!--spring中基于注解的声明式事务控制-->
<!--配置事务管理器,里面有事务的提交,回滚等操作-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--开启spring对注解事务的支持-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
</beans>
AccountServiceImpl:
package service.impl;
import dao.IAccountDao;
import domain.Account;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import service.IAccountService;
@Service("accountService")
@Transactional(propagation = Propagation.SUPPORTS,readOnly = true)
public class AccountServiceImpl implements IAccountService {
@Autowired
private IAccountDao accountDao;
@Transactional(propagation = Propagation.REQUIRED,readOnly = false)
public void transfer(String sourceName, String targetName, Float money) {
//根据姓名查找
Account source = accountDao.findByName(sourceName);
Account target = accountDao.findByName(targetName);
//修改金额
source.setMoney(source.getMoney()-money);
target.setMoney(target.getMoney()+money);
/*int a=1/0;*/ //故意制造一个错误测试事务控制
//修改数据库
accountDao.updateAccount(source);
accountDao.updateAccount(target);
}
public Account findById(Integer id) {
return accountDao.findById(id);
}
}