-
Spring是什么
spring是分层的java SE/EE应用full_statck轻量级开源框架,以IOC (inverse of Control:控制反转)和 AOP(Aspect Oriented Programming:面向切面编程)为内核,提供了展现层Spring MVC和持久层 Spring JDBC以及业务层事务管理等
-
Spring的优势
1.方便解耦,简化开发
2.AOP编程的支持
3.声明式事务的支持
4.方便程序的测试
5.方便集成各种优秀框架
6.降低java EE API的使用难度
-
程序的耦合性
耦合性(Coupling),也叫耦合度,是对模块间关联程度的度量。耦合的强弱取决于模块间接口的复杂性、调用模块的方式以及通过界面传送数据的多少。模块间的耦合度是指模块之间的依赖关系,包括控制关系、调用关系、数据传递关系。模块间联系越多,其耦合性越强,同时表明其独立性越差( 降低耦合性,可以提高其独立性)。软件设计中通常用耦合度和内聚度作为衡量模块独立程度的标准。划分模块的一个准则就是高内聚低耦合。
一般模块之间可能的连接方式有七种,构成耦合性的七种类型。它们之间的关系为(独立性由强到弱)
1.非直接耦合(Nondirect Coupling):如果两个模块之间没有直接关系,它们之间的联系完全是通过主模块的控制和调用来实现的,这就是非直接耦合。这种耦合的模块独立性最强。
2.数据耦合(Data Coupling):如果一个模块访问另一个模块时,彼此之间是通过数据参数(不是控制参数、公共数据结构或外部变量)来交换输入、输出信息的,则称这种耦合为数据耦合。由于限制了只通过参数表传递数据,按数据耦合开发的程序界面简单、安全可靠。因此,数据耦合是松散的耦合,模块之间的独立性比较强。在软件程序结构中至少必须有这类耦合。
3.印记耦合(Stamp Coupling):如果一组模块通过参数表传递记录信息,就是标记耦合。事实上,这组模块共享了这个记录,它是某一数据结构的子结构,而不是简单变量。这要求这些模块都必须清楚该记录的结构,并按结构要求对此记录进行操作。在设计中应尽量避免这种耦合,它使在数据结构上的操作复杂化了。如果采取“信息隐蔽”的方法,把在数据结构上的操作全部集中。
4.控制耦合(Control Coupling):如果一个模块通过传送开关、标志、名字等控制信息,明显地控制选择另一模块的功能,就是控制耦合。这种耦合的实质是在单一接口上选择多功能模块中的某项功能。因此,对所控制模块的任何修改,都会影响控制模块。另外,控制耦合也意味着控制模块必须知道所控制模块内部的一些逻辑关系,这些都会降低模块的独立性。
5.外部耦合(External Coupling):一组模块都访问同一全局简单变量而不是同一全局数据结构,而且不是通过参数表传递该全局变量的信息,则称之为外部耦合。例如C语言程序中各个模块都访问被说明为extern类型的外部变量。外部耦合引起的问题类似于公共耦合,区别在于在外部耦合中不存在依赖于一个数据结构内部各项的物理安排。
6.公共耦合(Common Coupling):若一组模块都访问同一个公共数据环境,则它们之间的耦合就称为公共耦合。公共的数据环境可以是全局数据结构、共享的通信区、内存的公共覆盖区等。 这种耦合会引起下列问题:
- 所有公共耦合模块都与某一个公共数据环境内部各项的物理安排有关,若修改某个数据的大小,将会影响到所有的模块。
- 无法控制各个模块对公共数据的存取,严重影响软件模块的可靠性和适应性。
- 公共数据名的使用,明显降低了程序的可读性。
公共耦合的复杂程度随耦合模块的个数增加而显著增加。若只是两个模块之间有公共数据环境,则公共耦合有两种情况。
若一个模块只是往公共数据环境里传送数据,而另一个模块只是从公共数据环境中取数据,则这种公共耦合叫做松散公共耦合。若两个模块都从公共数据环境中取数据,又都向公共数据环境里送数据,则这种公共耦合叫做紧密公共耦合。只有在模块之间共享的数据很多,且通过参数表传递不方便时,才使用公共耦合。否则,还是使用模块独立性比较高的数据耦合好些。
7.内容耦合(Content Coupling):如果发生下列情形,两个模块之间就发生了内容耦合。
- 一个模块直接访问另一个模块的内部数据;
- 一个模块不通过正常入口转到另一模块内部;
- 两个模块有一部分程序代码重叠(只可能出现在汇编语言中);
- 一个模块有多个入口。
- 在内容耦合的情形,所访问模块的任何变更,或者用不同的编译器对它再编译,
- 都会造成程序出错。好在大多数高级程序设计语言已经设计成不允许出现内容
- 耦合。它一般出现在汇编语言程序中。这种耦合是模块独立性最弱的耦合。
总结: 耦合是影响软件复杂程度和设计质量的一个重要因素,在设计上我们采用以下原则:如果模块间必须存在耦合,就尽量使用数据耦合,少用控制耦合,限制公共耦合的范围,尽量避免使用内容耦合
-
工厂模式
工厂模式是我们最常用的实例化对象模式了,是用工厂方法代替new操作的一种模式。著名的Jive论坛 ,就大量使用了工厂模式,工厂模式在Java程序系统可以说是随处可见。因为工厂模式就相当于创建实例对象的new,我们经常要根据类Class生成实例对象,如A a=new A() 工厂模式也是用来创建实例对象的,所以以后new时就要多个心眼,是否可以考虑使用工厂模式,虽然这样做,可能多做一些工作,但会给你系统带来更大的可扩展性和尽量少的修改量。
bean.properties
ACCOUNTSERVICE=com.torodial.service.impl.AccountServiceImpl
ACCOUNTDAO=com.torodial.dao.impl.IAccountDaoImpl
public class BeanFactory {
/* 1、他可以用于读取properties文件 2、它只能用于读取,不能用于写入 3、它只能读取类路径下的,不在类路径下的读取不了
*/
private static ResourceBundle bundle = ResourceBundle.getBundle("config.bean");// 参数指的是properties文件的位置,写法是包名.类名
/**
* 用于创建bean对象的方法
*
* @param beanName 资源文件中的key,用于获取全限定类名
* @return 创建好的对象
*/
public static Object getBean(String beanName) {
try {
Object value = null;
String beanPath = bundle.getString(beanName);
value = Class.forName(beanPath).newInstance();
return value;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
// write your code here
Object accountservice = BeanFactory.getBean("ACCOUNTSERVICE");
System.out.println(accountservice);
}
/*
AccountServiceImpl创建了
com.torodial.service.impl.AccountServiceImpl@12a3a380
*/
-
单例模式
单例模式,属于创建类型的一种常用的软件设计模式。通过单例模式的方法创建的类在当前进程中只有一个实例(根据需要,也有可能一个线程中属于单例,如:仅线程上下文内使用同一个实例)

单例模式成员变量被所有线程共享,一个线程修改成员变量的值,则所有线程的值都随之改变,局部变量的值每个线程独享,互不影响。
优点:
- 1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
- 2、避免对资源的多重占用(比如写文件操作)。
缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
使用场景:
- 1、要求生产唯一序列号。
- 2、WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
- 3、创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。
注意事项:getBean() 方法中需要使用同步锁 synchronized (Singleton.class) 防止多线程同时进入造成 instance 被多次实例化。
public class AccountServiceImpl implements IAccountService {
private int i = 1;
public AccountServiceImpl() {
System.out.println("AccountServiceImpl创建了");
}
@Override
public void saveAccount() {
//accountDao.save();
System.out.println(i);
i++;
}
}
public class BeanFactory {
/**
* 1、他可以用于读取properties文件 2、它只能用于读取,不能用于写入 3、它只能读取类路径下的,不在类路径下的读取不了
*/
private static ResourceBundle bundle = ResourceBundle.getBundle("config.bean");// 参数指的是properties文件的位置,写法是包名.类名
// 定义一个容器,用于存放所有的bean对象
private static Map<String, Object> beans = new HashMap<String, Object>();
// 使用静态代码块,给容器填充内容
static {
try {
//遍历多有的key
Enumeration<String> keys = bundle.getKeys();
while(keys.hasMoreElements()) {
//取出key
String key = keys.nextElement();
//根据key获取value
String beanPath = bundle.getString(key);
//反射获取bean对象
Object value=Class.forName(beanPath).newInstance();
//把创建出来的key存入map中
beans.put(key, value);
}
} catch (Exception e) {
throw new ExceptionInInitializerError("初始化容器失败");
}
}
public static Object getBean(String beanName) {
return beans.get(beanName);
}
}
public static void main(String[] args) {
// write your code here
for (int i = 0; i < 5; i++) {
IAccountService accountservice = (IAccountService) BeanFactory.getBean("ACCOUNTSERVICE");
System.out.println(accountservice);
accountservice.saveAccount();
}
}
/*
AccountServiceImpl创建了
com.torodial.service.impl.AccountServiceImpl@12a3a380
1
com.torodial.service.impl.AccountServiceImpl@12a3a380
2
com.torodial.service.impl.AccountServiceImpl@12a3a380
3
com.torodial.service.impl.AccountServiceImpl@12a3a380
4
com.torodial.service.impl.AccountServiceImpl@12a3a380
5
*/
public class AccountServiceImpl implements IAccountService {
public AccountServiceImpl() {
System.out.println("AccountServiceImpl创建了");
}
@Override
public void saveAccount() {
//accountDao.save();
int i = 1;
System.out.println(i);
i++;
}
}
public class BeanFactory {
/**
* 1、他可以用于读取properties文件 2、它只能用于读取,不能用于写入 3、它只能读取类路径下的,不在类路径下的读取不了
*/
private static ResourceBundle bundle = ResourceBundle.getBundle("config.bean");// 参数指的是properties文件的位置,写法是包名.类名
// 定义一个容器,用于存放所有的bean对象
private static Map<String, Object> beans = new HashMap<String, Object>();
// 使用静态代码块,给容器填充内容
static {
try {
//遍历多有的key
Enumeration<String> keys = bundle.getKeys();
while(keys.hasMoreElements()) {
//取出key
String key = keys.nextElement();
//根据key获取value
String beanPath = bundle.getString(key);
//反射获取bean对象
Object value=Class.forName(beanPath).newInstance();
//把创建出来的key存入map中
beans.put(key, value);
}
} catch (Exception e) {
throw new ExceptionInInitializerError("初始化容器失败");
}
}
public static Object getBean(String beanName) {
return beans.get(beanName);
}
}
public static void main(String[] args) {
// write your code here
for (int i = 0; i < 5; i++) {
IAccountService accountservice = (IAccountService) BeanFactory.getBean("ACCOUNTSERVICE");
System.out.println(accountservice);
accountservice.saveAccount();
}
}
/*
AccountServiceImpl创建了
com.torodial.service.impl.AccountServiceImpl@12a3a380
1
com.torodial.service.impl.AccountServiceImpl@12a3a380
1
com.torodial.service.impl.AccountServiceImpl@12a3a380
1
com.torodial.service.impl.AccountServiceImpl@12a3a380
1
com.torodial.service.impl.AccountServiceImpl@12a3a380
1
*/
-
单例模式的几种实现方式
1.饿汉模式 //饿汉模式,很饿很着急,所以类加载时即创建实例对象(开发中单例模式常用的)
是否 Lazy 初始化:否
是否多线程安全:是
实现难度:易
描述:这种方式比较常用,但容易产生垃圾对象。
优点:没有加锁,执行效率会提高。
缺点:类加载时就初始化,浪费内存。
它基于 classloader 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用 getInstance 方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化 instance 显然没有达到 lazy loading 的效果。
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){} //私有化构造函数,避免再次实例化对象
public static Singleton getInstance() {
return instance;
}
}
2.懒汉模式(饱汉模式),线程安全
是否 Lazy 初始化:是
是否多线程安全:是
实现难度:易
描述:这种方式具备很好的 lazy loading,能够在多线程中很好的工作,但是,效率很低,99% 情况下不需要同步。
优点:第一次调用才初始化,避免内存浪费。
缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率。
getInstance() 的性能对应用程序不是很关键(该方法使用不太频繁)。
//饱汉模式,很饱不着急,延迟加载,啥时候用啥时候创建实例
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
3.双检锁/双重校验锁(DCL,即 double-checked locking)是饱汉模式的优化,进行双重判断,当已经创建过实例对象后就无需加锁,提高效率。也是一种推荐使用的方式。
JDK 版本:JDK1.5 起
是否 Lazy 初始化:是
是否多线程安全:是
实现难度:较复杂
描述:这种方式采用双锁机制,安全且在多线程情况下能保持高性能。
getInstance() 的性能对应用程序很关键。
public class Singleton {
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
-
Spring的入门案例
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--配置业务层对象-->
<bean id="accountService" class="com.torodial.service.impl.AccountServiceImpl"></bean>
<!--配置持久层对象-->
<bean id="accountDao" class="com.torodial.dao.impl.IAccountDaoImpl"></bean>
</beans>
/**
* 模拟一个表现层
* @author wen
*
*/
public class Client {
/**
* 获取spring的核心容器,并且根据bean的id获取对象
* @param args
* ApplicationContext: (常用)适合于单例对象
* 它是BeanFactory下的子接口的再子接口
* 特点是创建bean对象时,采用的是立即加载的策略。(当读取完xml配置文件,配置文件中所有的bean对象都已经创建完了)
* BeanFactory: 适合于多例对象
* 它是springIoC容器的顶层接口。
* 特点是创建bean对象是采用的是延迟加载的策略(当真正要从容器中获取bean对象时才会创建,读完配置文件并不创建)
*
* ApplicationContext的实现类:
* ClassPathXmlApplicationContext: (常用)
* 它是通过读取类路径下的配置文件,创建spring容器,要求配置文件必须在类路径下
* FileSystemXmlApplicationContext:
* 它是通过读取文件系统中的配置文件,创建spring容器,要求配置文件在文件系统中即可
*/
public static void main(String[] args) {
//1、获取spring的核心容器,参数为文件名
ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
//2、根据bean的id获取对象
//IAccountService iAccountService=(IAccountService) ac.getBean("accountService");
//不用自己强转的方式,等价于以上操作
IAccountService iAccountService=ac.getBean("accountService", IAccountService.class);
//不用自己强转的方式
IAccountDao iAccountDao=ac.getBean("accountDao", IAccountDao.class);
System.out.println(iAccountService);
System.out.println(iAccountDao);
/*Resource resource=new ClassPathResource("bean.xml");
BeanFactory bFactory=new XmlBeanFactory(resource);
IAccountDao iAccountDao = bFactory.getBean("accountDao", IAccountDao.class);*/
}
}
/*
...
信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@6438a396: startup date [Tue Mar 24 23:02:05 CST 2020]; root of context hierarchy
三月 24, 2020 11:02:05 下午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from class path resource [bean.xml]
AccountServiceImpl创建了
com.torodial.service.impl.AccountServiceImpl@5e3a8624
com.torodial.dao.impl.IAccountDaoImpl@5c3bd550
*/
-
bean标签
作用:用于让spring帮我们配置后创建对象
属性;
id:对象的唯一标识
class:要创建对象的全限定类名
factory-method:指定创建bean对象的方法,该方法可以使静态的,也可以不是
bean对象的三种创建方式:
1、通过调用构造函数创建bean对象 常用
在默认情况下,当我们在spring的配置文件中写了bean标签,并提供了class属性,
spring就会调用默认构造函数创建对象,如果没有默认无参构造函数,则对象创建失败。
2、通过静态工厂创建bean对象.
工厂类中提供一个静态方法,可以返回要用的bean对象
3、通过实例工厂创建bean对象
工厂类中提供一个普通方法,可以返回要用的bean对象
bean对象的作用范围:
它是可以通过配置的方式来指定的
配置的属性:bean标签的scope属性
属性的取值:
singleton:单例的,(默认值)最常用
prototype:多例的,每次都会创建一个新的对象
request:请求范围,当前对象只会在一次请求,和当前请求的转发中可用
(web项目中,spring创建一个bean的对象,讲对象存入到request域中)
session:会话范围,在一次会话中可用(web项目中,spring创建一个bean的对象,讲对象存入到session域中)
global-session:全局会话范围(web项目中,应用在portlet环境,如果没有port环境,就相当于session)
bean对象的生命周期:
单例对象:
出生:容器创建时
活着:只要容器存在,对象就一直活着
死亡:容器销毁
多例对象
出生:每次使用时容器会为我们创建对象
活着:只要对象在使用过程中,就一直活着
死亡:当对象长时间不用,并且也没有其他对象饮用时,由Java的垃圾回收器回收
<?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对象的细节
bean标签:
作用:用于让spring帮我们配置后创建对象
属性;
id:对象的唯一标识
class:要创建对象的全限定类名
factory-method:指定创建bean对象的方法,该方法可以使静态的,也可以不是
bean对象的三种创建方式:
1、通过调用构造函数创建bean对象 常用
在默认情况下,当我们在spring的配置文件中写了bean标签,并提供了class属性,
spring就会调用默认构造函数创建对象,如果没有默认无参构造函数,则对象创建失败。
2、通过静态工厂创建bean对象.
工厂类中提供一个静态方法,可以返回要用的bean对象
3、通过实例工厂创建bean对象
工厂类中提供一个普通方法,可以返回要用的bean对象
bean对象的作用范围:
它是可以通过配置的方式来指定的
配置的属性:bean标签的scope属性
属性的取值:
singleton:单例的,(默认值)最常用
prototype:多例的,每次都会创建一个新的对象
request:请求范围,当前对象只会在一次请求,和当前请求的转发中可用
(web项目中,spring创建一个bean的对象,讲对象存入到request域中)
session:会话范围,在一次会话中可用(web项目中,spring创建一个bean的对象,讲对象存入到session域中)
global-session:全局会话范围(web项目中,应用在portlet环境,如果没有port环境,就相当于session)
bean对象的生命周期:
单例对象:
出生:容器创建时
活着:只要容器存在,对象就一直活着
死亡:容器销毁
多例对象
出生:每次使用时容器会为我们创建对象
活着:只要对象在使用过程中,就一直活着
死亡:当对象长时间不用,并且也没有其他对象饮用时,由Java的垃圾回收器回收
-->
<!-- 默认构造函数创建-->
<bean id="accountService" class="com.torodial.service.impl.AccountServiceImpl"
scope="singleton" init-method="init" destroy-method="destroy"></bean>
<!-- 静态工厂创建
<bean id="staticAccountService" class="com.itheima.factory.StaticBeanFactory" factory-method="getBean"></bean>
-->
<!-- 实例工厂创建 -->
<!-- <bean id="instanceBeanFactory" class="com.itheima.factory.InstanceBeanFactory" ></bean>
接下来配置真正想用的bean对象
<bean id="InstanceAccountService" factory-bean="instanceBeanFactory" factory-method="getBean"></bean> -->
<!-- 配置持久层对象
<bean id="accountDao" class="com.itheima.dao.impl.IAccountDaoImpl"></bean> -->
</beans>
-
依赖注入
依赖注入(Dependency Injection)是Spring框架的核心之一。
当某个角色(可能是一个Java实例,调用者)需要另一个角色(另一个Java实例,被调用者)的协助时,在 传统的程序设计过程中,通常由调用者来创建被调用者的实例。但在Spring里,创建被调用者的工作不再由调用者来完成,因此称为控制反转;创建被调用者 实例的工作通常由Spring容器来完成,然后注入调用者,因此也称为依赖注入。
依赖注入有两种:设值注入、构造注入
所谓依赖注入,是指程序运行过程中,如果需要调用另一个对象协助时,无须在代码中创建被调用者,而是依赖于外部的注入。
依赖注入是控制反转中最常见的方式
控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。
注入的方式:只有三种(今天只涉及两种)
第一种方式:通过构造函数注入
第二种方式:通过set方法注入
注入的内容:
第一类:基本类型和String类型
第二类:其他的bean类型
第三类:复杂类型(集合类型)(标签里面还有各自复杂类型的子标签)
第一种:使用构造函数注入:
涉及的标签:constructor-arg (该标签是写在bean标签内部的子标签)、
标签的属性:
type:指定要注入的参数在构造函数中的类型(不到必要时不选他,因为一个类型可能不止一个元素)
index:指定要注入的参数在构造函数中的索引位置
name:指定参数在构造函数中的名称
value:指定注入的数据内容,它只能指定基本类型数据和String类型数据
ref:指定其他bean类型数据,写的是其他bean的ID,其他bean指的是存在于spring容器中的bean
<?xml version="1.0" encoding="UTF-8"?>
<!-- spring的IOC约束beans -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
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的依赖注入-
注入的方式:只有三种(今天只涉及两种)
第一种方式:通过构造函数注入
第二种方式:通过set方法注入
注入的内容:
第一类:基本类型和String类型
第二类:其他的bean类型
第三类:复杂类型(集合类型)(标签里面还有各自复杂类型的子标签)
-->
<!--
第一种:使用构造函数注入:
涉及的标签:constructor-arg (该标签是写在bean标签内部的子标签)、
标签的属性:
type:指定要注入的参数在构造函数中的类型(不到必要时不选他,因为一个类型可能不止一个元素)
index:指定要注入的参数在构造函数中的索引位置
name:指定参数在构造函数中的名称
value:指定注入的数据内容,它只能指定基本类型数据和String类型数据
ref:指定其他bean类型数据,写的是其他bean的ID,其他bean指的是存在于spring容器中的bean
-->
<!-- 定义一个日期对象 -->
<bean id="now" class="java.util.Date" ></bean>
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
<constructor-arg name="name" value="泰斯特"></constructor-arg>
<constructor-arg name="age" value="18"></constructor-arg>
<constructor-arg name="birthday" ref="now"></constructor-arg>
</bean>
</beans>
/*
* 账户的业务层实现类
*/
public class AccountServiceImpl implements IAccountService {
private String name;
private Integer age;
private Date birthday;
public AccountServiceImpl(String name, Integer age, Date birthday) {
super();
this.name = name;
this.age = age;
this.birthday = birthday;
}
@Override
public void saveAccount() {
System.out.println("调用持久层执行方法:" + name + "," + age + "," + birthday);
}
}
public class Client {
public static void main(String[] args) {
//1、获取Spring的核心容器,参数为文件名
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
// 2、根据bean的id获取对象
//IAccountService accountService = (IAccountService) ac.getBean("accountService");
// 不用自己强转的方式
IAccountService accountService = ac.getBean("accountService", IAccountService.class);
accountService.saveAccount();
}
}
/*
调用持久层执行方法:泰斯特,18,Thu Mar 26 14:52:58 CST 2020
*/
第二种:使用set方法注入(常用)
涉及的标签:property
该标签也是要写在bean标签内部的子标签
标签的属性:
name:指定的是set方法的名称,匹配的是类中set方法后边的部分(如setAge则是age)
value:指定注入的数据内容,它只能指定基本类型数据和String类型数据
ref:指定其他bean类型数据,写的是其他bean的ID,其他bean指的是存在于spring容器中的bean
<?xml version="1.0" encoding="UTF-8"?>
<!-- spring的IOC约束beans -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
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的依赖注入-
注入的方式:只有三种(今天只涉及两种)
第一种方式:通过构造函数注入
第二种方式:通过set方法注入
注入的内容:
第一类:基本类型和String类型
第二类:其他的bean类型
第三类:复杂类型(集合类型)(标签里面还有各自复杂类型的子标签)
-->
<!--
第二种:使用set方法注入(常用)
涉及的标签:property
该标签也是要写在bean标签内部的子标签
标签的属性:
name:指定的是set方法的名称,匹配的是类中set方法后边的部分(如setAge则是age)
value:指定注入的数据内容,它只能指定基本类型数据和String类型数据
ref:指定其他bean类型数据,写的是其他bean的ID,其他bean指的是存在于spring容器中的bean
-->
<!-- 定义一个日期对象 -->
<bean id="now" class="java.util.Date" ></bean>
<bean id="accountService2" class="com.itheima.service.impl.AccountServiceImpl2">
<property name="name" value="test"></property>
<property name="age" value="21"></property>
<property name="birthday" ref="now"></property>
</bean>
</beans>
/*
* 账户的业务层实现类
*/
public class AccountServiceImpl2 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;
}
@Override
public void saveAccount() {
System.out.println("调用持久层执行方法:" + name + "," + age + "," + birthday);
}
}
public class Client {
/**
* 获取spring的核心容器,并且根据bean的id获取对象
*
* @param args
*/
public static void main(String[] args) {
//1、获取Spring的核心容器,参数为文件名
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
// 2、根据bean的id获取对象
//IAccountService accountService = (IAccountService) ac.getBean("accountService");
// 不用自己强转的方式
IAccountService accountService = ac.getBean("accountService", IAccountService.class);
//使用set方法注入(常用)
accountService.saveAccount();
}
}
/*
调用持久层执行方法:test,21,Thu Mar 26 15:02:39 CST 2020
*/
使用p名称空间注入:
它的本质仍然是需要类中提供set方法,同时在配置文件中要导入p名称空间
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">..
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
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">
<!--
使用p名称空间注入:
它的本质仍然是需要类中提供set方法,同时在配置文件中要导入p名称空间
-->
<bean id="accountService3" class="com.itheima.service.impl.AccountServiceImpl3"
p:name="zhangsan" p:age="25" p:birthday-ref="now">
</bean>
</beans>
/*
* 账户的业务层实现类
*/
public class AccountServiceImpl3 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;
}
@Override
public void saveAccount() {
System.out.println("调用持久层执行方法:" + name + "," + age + "," + birthday);
}
}
public static void main(String[] args) {
// 1、获取spring的核心容器,参数为文件名
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
// 使用p名称空间注入
IAccountService iAccountService3 = (IAccountService) ac.getBean("accountService3");
iAccountService3.saveAccount();
}
/*
调用持久层执行方法:zhangsan,25,Thu Mar 26 15:19:23 CST 2020
*/
复杂类型的注入:注入方式set,注入内容集合类型
结构相同,标签可以互换,如list、str和set;map和properties
记住array&value和pros&prop
<!-- 复杂类型的注入:注入方式set,注入内容集合类型
结构相同,标签可以互换,如list、str和set;map和properties
记住array&value和pros&prop
-->
<bean id="accountService4" class="com.itheima.service.impl.AccountServiceImpl4">
<!-- 给数组注入数据 -->
<property name="myStrs">
<array>
<value>array1</value>
<value>array2</value>
<value>array3</value>
</array>
</property>
<!-- 给list注入数据 -->
<property name="myList">
<list>
<value>list1</value>
<value>list2</value>
<value>list3</value>
</list>
</property>
<!-- 给set集合注入 -->
<property name="mySet">
<set>
<value>set1</value>
<value>set2</value>
<value>set3</value>
</set>
</property>
<!-- 给map集合注入 -->
<property name="myMap">
<map>
<entry key="testA" value="map1"></entry>
<entry key="testB" value="map2"></entry>
<entry key="testC" value="map3"></entry>
</map>
</property>
<!-- 给properties注入数据 -->
<property name="myProps">
<props>
<prop key="num1">prop1</prop>
<prop key="num2">prop2</prop>
<prop key="num2">prop3</prop>
</props>
</property>
</bean>
/*
* 账户的业务层实现类
*/
public class AccountServiceImpl4 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;
}
@Override
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);
}
}
public static void main(String[] args) {
// 1、获取spring的核心容器,参数为文件名
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
// 复杂类型的注入
IAccountService iAccountService4 = (IAccountService) ac.getBean("accountService4");
iAccountService4.saveAccount();
}
/*
[array1, array2, array3]
[list1, list2, list3]
[set1, set2, set3]
{testA=map1, testB=map2, testC=map3}
{num2=prop3, num1=prop1}
*/
-
set方法注入(常用)演示
<?xml version="1.0" encoding="UTF-8"?>
<!-- spring的IOC约束beans -->
<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标签:
作用:用于让spring帮我们配置后创建对象
属性;
id:对象的唯一标识
class:要创建对象的全限定类名
-->
<!-- 配置持久层对象 -->
<bean id="accountDao" class="com.itheima.dao.impl.IAccountDaoImpl"></bean>
<!-- 配置业务层对象 -->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
</beans>
public class IAccountDaoImpl implements IAccountDao {
@Override
public void save() {
System.out.println("保存了账户");
}
}
/*
* 账户的业务层实现类
*/
public class AccountServiceImpl implements IAccountService {
//private IAccountDao accountDao=new IAccountDaoImpl();//有依赖关系
private IAccountDao accountDao = null;//(用注入的方式解决空指针异常,解除依赖)
public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}
public AccountServiceImpl() {
System.out.println("AccountServiceImpl创建了");
}
@Override
public void saveAccount() {
accountDao.save();
}
}
public class Client {
public static void main(String[] args) {
//1、获取spring的核心容器,参数为文件名
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2、根据bean的id获取对象
IAccountService accountService = ac.getBean("accountService", IAccountService.class);//不用自己强转的方式
accountService.saveAccount();
}
}
/*
AccountServiceImpl创建了
保存了账户
*/
-
pring基于注解的ioc环境搭建
第一步:导入六个必须的依赖jar包

第二步:创建配置文件,并告知Spring创建容器时需要扫描的包
第三步:使用注解配置对象
-
注解分类
1、用于创建对象的
@Component
* 作用就相当于在spring的xml文件中写了一个bean标签。
* 属性:value用于指定bean的id,当不写时默认值是当前类名,首字母改小写,
* 例如:accountServiceImpl,同时只有value属性时可不写属性名,只写值
* 由此注解衍生的三个注解:
* @Controller(一般用于表现层)
* @Service (一般用于业务层)
* @Repository(一般用于持久层)
* 他们的作用以及属性和@Component的作用是一模一样的,
* 他们的出现是spring框架为我们提供更明确的语义化来指定不同的bean对象
<?xml version="1.0" encoding="UTF-8"?>
<!-- 基于注解的IOC配置需要导入context的名称空间 -->
<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.itheima"></context:component-scan>
</beans>
package com.itheima.service.impl;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import com.itheima.dao.IAccountDao;
import com.itheima.service.IAccountService;
/**
* 账户的业务层实现类
* @author wen
*xml的配置
*<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
* <property name="accountDao" ref="accountDao"></property>
* </bean>
*
*
*注解分类
* 1、用于创建对象的
* @Component
* 作用就相当于在spring的xml文件中写了一个bean标签。
* 属性:value用于指定bean的id,当不写时默认值是当前类名,首字母改小写,
* 例如:accountServiceImpl,同时只有value属性时可不写属性名,只写值
* 由此注解衍生的三个注解:
* @Controller(一般用于表现层)
* @Service (一般用于业务层)
* @Repository(一般用于持久层)
* 他们的作用以及属性和@Component的作用是一模一样的,
* 他们的出现是spring框架为我们提供更明确的语义化来指定不同的bean对象
* 2、用于注入数据的
* 用于注入其他bean类型的注解:
* @AutoWired
* 作用:自动按照类型注入,只要容器中有唯一的类型匹配,则可以直接注入成功,
* 1、 如果没有匹配的类型就报错。
* 2、如果有多个类型匹配时,会先按照类型找到符合条件的对象,
* 然后再用变量名称作为bean的id,从里面继续查找,如果找到,仍然可以注入成功,如果没有则报错
* 细节:当使用此注解注入时,set方法可以省略
* 属性:required:是否必须注入成功,取值为true(默认值)/false;
* 当取值为true时,没有匹配的对象就报错
* @Qualifer
* 作用:在自动按照类型注入的基础上,再按照bean的id注入。在给类成员注入时,它不能够独立使用。
* 属性:value:用于指定bean的id
* @Resource
* 作用:直接按照bean的id注入
* 属性:name:用于指定bean的id
* 以上三个注解,都只能注入其他bean类型,而不能注入基本类型和String,更不能注入集合类型
*
* 用于注入基本类型和String类型的注解:
* @Value
* 作用:用于注入基本类型和String类型的注解
* 属性:value:用于指定要注入的数据,支持使用spring的EL表达式
* spring的EL表达式写法:${表达式}
* 3、用于改变作用范围
* @Scope
* 作用:用于改变bean的作用范围,范围的取值和xml配置中的一样@Scope(singleton)
* 属性:value:用于指定范围
*
* 4、和生命周期相关的
* @PostConstruct
* 作用:用于指定初始化方法,和配置文件中init-method属性是一样的
* @PreDestroy
* 作用:用于指定销毁方法,和配置文件中的destroy-method属性是一样的
*/
@Service(value = "accountServiceImpl")
public class AccountServiceImpl implements IAccountService{
private IAccountDao accountDao;
private String driver;
@PostConstruct
public void init() {
System.out.println("对象初始化了");
}
public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public void saveAccount() {
System.out.println(driver);
accountDao.saveAccount();
}
}
public class Client {
public static void main(String[] args) {
//1、获取spring的核心容器,参数为文件名
ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
//2、根据bean的id获取对象
IAccountService iAccountService=ac.getBean("accountServiceImpl",IAccountService.class);
System.out.println(iAccountService);
}
}
/*
对象初始化了
com.itheima.service.impl.AccountServiceImpl@dbd940d
*/
2、用于注入数据的
用于注入其他bean类型的注解:
* @AutoWired
* 作用:自动按照类型注入,只要容器中有唯一的类型匹配,则可以直接注入成功,
* 1、 如果没有匹配的类型就报错。
* 2、如果有多个类型匹配时,会先按照类型找到符合条件的对象,
* 然后再用变量名称作为bean的id,从里面继续查找,如果找到,仍然可以注入成功,如果没有则报错
* 细节:当使用此注解注入时,set方法可以省略
* 属性:required:是否必须注入成功,取值为true(默认值)/false;
* 当取值为true时,没有匹配的对象就报错
* @Qualifer
* 作用:在自动按照类型注入的基础上,再按照bean的id注入。在给类成员注入时,它不能够独立使用。
* 属性:value:用于指定bean的id
* @Resource
* 作用:直接按照bean的id注入
* 属性:name:用于指定bean的id
* 以上三个注解,都只能注入其他bean类型,而不能注入基本类型和String,更不能注入集合类型
*
* 用于注入基本类型和String类型的注解:
* @Value
* 作用:用于注入基本类型和String类型的注解
* 属性:value:用于指定要注入的数据,支持使用spring的EL表达式
* spring的EL表达式写法:${表达式}
-
@AutoWired
作用:自动按照类型注入,只要容器中有唯一的类型匹配,则可以直接注入成功,
* 1、 如果没有匹配的类型就报错。
* 2、如果有多个类型匹配时,会先按照类型找到符合条件的对象,
* 然后再用变量名称作为bean的id,从里面继续查找,如果找到,仍然可以注入成功,如果没有则报错
* 细节:当使用此注解注入时,set方法可以省略
* 属性:required:是否必须注入成功,取值为true(默认值)/false;
* 当取值为true时,没有匹配的对象就报错
<?xml version="1.0" encoding="UTF-8"?>
<!-- 基于注解的IOC配置需要导入context的名称空间 -->
<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.itheima"></context:component-scan>
<!-- 告知spring properties文件的位置
<context:property-placeholder location="jdbcConfig.properties"/>
-->
</beans>
/**
* 账户的持久层实现类
* @author wen
*
*/
@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {
@Override
public void saveAccount() {
System.out.println("执行了保存账户操作");
}
}
@Service("accountServiceImpl")
public class AccountServiceImpl implements IAccountService{
@Autowired
private IAccountDao accountDao;
public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public void saveAccount() {
accountDao.saveAccount();
}
}
public class Client {
public static void main(String[] args) {
//1、获取spring的核心容器,参数为文件名
ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
//2、根据bean的id获取对象
IAccountService iAccountService=ac.getBean("accountServiceImpl",IAccountService.class);
System.out.println(iAccountService);
iAccountService.saveAccount();
}
}
/*
com.itheima.service.impl.AccountServiceImpl@1188e820
执行了保存账户操作
*/

@Qualifer
* 作用:在自动按照类型注入的基础上,再按照bean的id注入。在给类成员注入时,它不能够独立使用。
* 属性:value:用于指定bean的id
@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {
@Override
public void saveAccount() {
System.out.println("11111执行了保存账户操作");
}
}
@Repository("accountDao2")
public class AccountDaoImpl2 implements IAccountDao {
@Override
public void saveAccount() {
System.out.println("222执行了保存账户操作");
}
}
@Service("accountServiceImpl")
public class AccountServiceImpl implements IAccountService{
@Autowired
@Qualifier("accountDao")
private IAccountDao accountDao;
public void init() {
System.out.println("对象初始化了");
}
public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public void saveAccount() {
accountDao.saveAccount();
}
}
public class Client {
public static void main(String[] args) {
//1、获取spring的核心容器,参数为文件名
ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
//2、根据bean的id获取对象
IAccountService iAccountService=ac.getBean("accountServiceImpl",IAccountService.class);
System.out.println(iAccountService);
iAccountService.saveAccount();
}
}
/*
com.itheima.service.impl.AccountServiceImpl@33b37288
执行了保存账户操作
*/
@Resource
* 作用:直接按照bean的id注入
* 属性:name:用于指定bean的id
@Service("accountServiceImpl")
public class AccountServiceImpl implements IAccountService{
@Resource(name="accountDao2")
private IAccountDao accountDao;
public void init() {
System.out.println("对象初始化了");
}
public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public void saveAccount() {
accountDao.saveAccount();
}
}
public class Client {
public static void main(String[] args) {
//1、获取spring的核心容器,参数为文件名
ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
//2、根据bean的id获取对象
IAccountService iAccountService=ac.getBean("accountServiceImpl",IAccountService.class);
System.out.println(iAccountService);
iAccountService.saveAccount();
}
}
/*
com.itheima.service.impl.AccountServiceImpl@17695df3
222执行了保存账户操作
*/
以上三个注解,都只能注入其他bean类型,而不能注入基本类型和String,更不能注入集合类型
-
用于注入基本类型和String类型的注解
-
@Value
作用:用于注入基本类型和String类型的注解
* 属性:value:用于指定要注入的数据,支持使用spring的EL表达式
* spring的EL表达式写法:${表达式}
直接填写具体值的方式(没意义,还不入直接: private String driver = "com.mysql.jdbc.Driver";)
@Service("accountService")
public class AccountServiceImpl implements IAccountService{
@Resource(name="accountDao2")
private IAccountDao accountDao;
//@Value(value="${jdbc.driver}")
@Value(value="com.mysql.jdbc.Driver")
private String driver;
public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public void saveAccount() {
System.out.println(driver);
accountDao.saveAccount();
}
}
public class Client {
public static void main(String[] args) {
//1、获取spring的核心容器,参数为文件名
ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
//2、根据bean的id获取对象
IAccountService iAccountService=ac.getBean("accountServiceImpl",IAccountService.class);
System.out.println(iAccountService);
iAccountService.saveAccount();
}
}
/*
com.itheima.service.impl.AccountServiceImpl@17695df3
222执行了保存账户操作
*/
作用:用于注入基本类型和String类型的注解 * 属性:value:用于指定要注入的数据,支持使用spring的EL表达式 * spring的EL表达式写法:${表达式},将具体值怕配置到配置文件jdbcConfig.properties中
jdbcConfig.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring5
jdbc.username=root
jdbc.password=123
告知spring properties文件的位置
<?xml version="1.0" encoding="UTF-8"?>
<!-- 基于注解的IOC配置需要导入context的名称空间 -->
<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.itheima"></context:component-scan>
<!-- 告知spring properties文件的位置-->
<context:property-placeholder location="jdbcConfig.properties"/>
</beans>
@Service("accountServiceImpl")
public class AccountServiceImpl implements IAccountService{
//@Autowired
//@Qualifier("accountDao1")
@Resource(name="accountDao2")
private IAccountDao accountDao;
//@Value(value = "com.mysql.jdbc.Driver")
@Value(value = "${jdbc.driver}")
private String driver;
public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public void saveAccount() {
System.out.println(driver);
accountDao.saveAccount();
}
}
public class Client {
public static void main(String[] args) {
//1、获取spring的核心容器,参数为文件名
ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
//2、根据bean的id获取对象
IAccountService iAccountService=ac.getBean("accountServiceImpl",IAccountService.class);
System.out.println(iAccountService);
iAccountService.saveAccount();
}
}
/*
com.itheima.service.impl.AccountServiceImpl@3ffcd140
com.mysql.jdbc.Driver
222执行了保存账户操作
*/
-
3、用于改变作用范围
* @Scope
* 作用:用于改变bean的作用范围,范围的取值和xml配置中的一样@Scope(singleton)
* 属性:value:用于指定范围,默认值为 singleton
配置的属性:bean标签的scope属性
属性的取值:
singleton:单例的,(默认值)最常用
prototype:多例的,每次都会创建一个新的对象
request:请求范围,当前对象只会在一次请求,和当前请求的转发中可用
(web项目中,spring创建一个bean的对象,讲对象存入到request域中)
session:会话范围,在一次会话中可用(web项目中,spring创建一个bean的对象,讲对象存入到session域中)
global-session:全局会话范围(web项目中,应用在portlet环境,如果没有port环境,就相当于session)
@Service("accountServiceImpl")
@Scope("singleton")
public class AccountServiceImpl implements IAccountService{
...
}
public static void main(String[] args) {
//1、获取spring的核心容器,参数为文件名
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2、根据bean的id获取对象
IAccountService iAccountService = ac.getBean("accountServiceImpl", IAccountService.class);
IAccountService iAccountService1 = ac.getBean("accountServiceImpl", IAccountService.class);
System.out.println(iAccountService);
System.out.println(iAccountService1);
System.out.println(iAccountService==iAccountService1);
}
/*
com.itheima.service.impl.AccountServiceImpl@3ffcd140
com.itheima.service.impl.AccountServiceImpl@3ffcd140
true
*/
@Service("accountServiceImpl")
@Scope("prototype")
public class AccountServiceImpl implements IAccountService{
...
}
public static void main(String[] args) {
//1、获取spring的核心容器,参数为文件名
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2、根据bean的id获取对象
IAccountService iAccountService = ac.getBean("accountServiceImpl", IAccountService.class);
IAccountService iAccountService1 = ac.getBean("accountServiceImpl", IAccountService.class);
System.out.println(iAccountService);
System.out.println(iAccountService1);
System.out.println(iAccountService==iAccountService1);
}
/*
com.itheima.service.impl.AccountServiceImpl@25359ed8
com.itheima.service.impl.AccountServiceImpl@21a947fe
false
*/
-
4、和生命周期相关的
-
@PostConstruct
* @PostConstruct
* 作用:用于指定初始化方法,和配置文件中init-method属性是一样的
* @PreDestroy
* 作用:用于指定销毁方法,和配置文件中的destroy-method属性是一样的
*/
要想使@PreDestroy标记的销毁方法生效需要注意:
1.不能使用多例 prototype ,因为spring不知道多例对象什么时候用完所以不管销毁
2.容器销毁对象才会跟着销毁
@Service("accountServiceImpl")
@Scope("singleton")
public class AccountServiceImpl implements IAccountService{
//@Autowired
//@Qualifier("accountDao1")
@Resource(name="accountDao2")
private IAccountDao accountDao;
//@Value(value = "com.mysql.jdbc.Driver")
@Value(value="${jdbc.driver}")
private String driver;
@PostConstruct
public void init() {
System.out.println("对象初始化了");
}
@PreDestroy
public void destroy(){
System.out.println("对象销毁了");
}
public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public void saveAccount() {
System.out.println(driver);
accountDao.saveAccount();
}
}
public class Client {
public static void main(String[] args) {
//1、获取spring的核心容器,参数为文件名
//ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//ApplicationContext没有close()方法需要使用ClassPathXmlApplicationContext
ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2、根据bean的id获取对象
IAccountService iAccountService = ac.getBean("accountServiceImpl", IAccountService.class);
//ac.close()销毁容器(对象才会跟着销毁)
ac.close();
}
}
/*
信息: Loading XML bean definitions from class path resource [bean.xml]
对象初始化了
三月 26, 2020 10:47:15 下午 org.springframework.context.support.ClassPathXmlApplicationContext doClose
信息: Closing org.springframework.context.support.ClassPathXmlApplicationContext@6477463f: startup date [Thu Mar 26 22:47:15 CST 2020]; root of context hierarchy
对象销毁了
*/
-
基于xml的springIoC开发示例
<?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="com.itheima.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
<!-- 配置dao层 -->
<bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
<property name="dbAssit" ref="dbAssit"></property>
</bean>
<!-- 配置数据库操作助手 -->
<bean id="dbAssit" class="com.wangyu.dbassit.DBAssit">
<property name="dataSource" ref="dataSource"></property>
</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/test"></property>
<property name="user" value="root"></property>
<property name="password" value="123456"></property>
</bean>
</beans>
业务层实现类
/**
* 账户的业务层实现类
* @author wen
*
*/
public class AccountServiceImpl implements IAccountService {
private IAccountDao accountDao;
public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public void saveAccount(Account account) {
accountDao.save(account);
}
@Override
public void updateAccount(Account account) {
accountDao.update(account);
}
@Override
public void deleteAccount(Integer accountId) {
accountDao.delete(accountId);
}
@Override
public Account findAccountById(Integer accountId) {
return accountDao.findById(accountId);
}
@Override
public List<Account> findAllAccount() {
return accountDao.findAll();
}
}
持久层实现类
/**
* 账户的持久层实现类
* @author wen
*
*/
public class AccountDaoImpl implements IAccountDao {
private DBAssit dbAssit;
public void setDbAssit(DBAssit dbAssit) {
this.dbAssit = dbAssit;
}
@Override
public void save(Account account) {
dbAssit.update("insert into account(name,money) values(?,?)", account.getName(),account.getMoney());
}
@Override
public void update(Account account) {
dbAssit.update("update account set name=?,money=? where id=?", account.getName(),account.getMoney(),account.getId());
}
@Override
public void delete(Integer accountId) {
dbAssit.update("delete from account where id=?", accountId);
}
@Override
public Account findById(Integer accountId) {
return (Account) dbAssit.query("select *from account where id=?", new BeanHandler<Account>(Account.class), accountId);
}
@SuppressWarnings("unchecked")
@Override
public List<Account> findAll() {
return (List<Account>) dbAssit.query("select *from account", new BeanListHandler<Account>(Account.class));
}
}

测试类
/**
* 账户的测试类
* @author wen
*
*/
public class AccountServiceTest {
@Test
public void testSaveAccount() {
Account account=new Account();
account.setName("springIocAccount");
account.setMoney(2000f);
ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
IAccountService accountService=ac.getBean("accountService", IAccountService.class);
accountService.saveAccount(account);
}
@Test
public void testUpdateAccount() {
ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
IAccountService accountService=ac.getBean("accountService", IAccountService.class);
Account account=accountService.findAccountById(1);
account.setMoney(6666f);
accountService.updateAccount(account);
}
@Test
public void testDeleteAccount() {
ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
IAccountService accountService=ac.getBean("accountService", IAccountService.class);
accountService.deleteAccount(1);
}
@Test
public void testFindAccountById() {
ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
IAccountService accountService=ac.getBean("accountService", IAccountService.class);
Account account=accountService.findAccountById(1);
System.out.println(account);
}
@Test
public void testFindAllAccount() {
ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
IAccountService accountService=ac.getBean("accountService", IAccountService.class);
List<Account> accounts = accountService.findAllAccount();
System.out.println(accounts);
}
}
- 分析测试类中产生的问题
代码冗余
ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
IAccountService accountService=ac.getBean("accountService", IAccountService.class);
以上代码出现多次
提取公共部分
/**
* 账户的测试类
* @author wen
*
*/
public class AccountServiceTest {
private ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
private IAccountService accountService=ac.getBean("accountService", IAccountService.class);
@Test
public void testSaveAccount() {
Account account=new Account();
account.setName("springIocAccount");
account.setMoney(2000f);
// ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
// IAccountService accountService=ac.getBean("accountService", IAccountService.class);
accountService.saveAccount(account);
}
@Test
public void testUpdateAccount() {
// ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
// IAccountService accountService=ac.getBean("accountService", IAccountService.class);
Account account=accountService.findAccountById(1);
account.setMoney(6666f);
accountService.updateAccount(account);
}
@Test
public void testDeleteAccount() {
// ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
// IAccountService accountService=ac.getBean("accountService", IAccountService.class);
accountService.deleteAccount(1);
}
@Test
public void testFindAccountById() {
// ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
// IAccountService accountService=ac.getBean("accountService", IAccountService.class);
Account account=accountService.findAccountById(1);
System.out.println(account);
}
@Test
public void testFindAllAccount() {
// ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
// IAccountService accountService=ac.getBean("accountService", IAccountService.class);
List<Account> accounts = accountService.findAllAccount();
System.out.println(accounts);
}
}
-
改造案例使用基于注解的ioc实现
使用注解,删除set方法
@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {
@Resource(name = "dbAssit")
private DBAssit dbAssit;
...
}
-
纯注解配置待改造的问题(舍弃配置文件)
<?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.itheima"></context:component-scan>
<!-- 配置数据库操作助手 -->
<bean id="dbAssit" class="com.wangyu.dbassit.DBAssit">
<property name="dataSource" ref="dataSource"></property>
</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/test"></property>
<property name="user" value="root"></property>
<property name="password" value="123456"></property>
</bean>
</beans>
-
@Configuration//相当于表明当前类是spring的配置类
package config;
@Configuration//相当于表明当前类是spring的配置类,
public class SpringConfiguration {
...
}
-
@Import(JdbcConfig.class)//用于导入其他的配置类
package config;
@Configuration//相当于表明当前类是spring的配置类,
@Import(JdbcConfig.class)//用于导入其他的配置类
public class SpringConfiguration {
...
}
-
@PropertySource("config/jdbcConfig.properties")
package config;
@Configuration//相当于表明当前类是spring的配置类,
@Import(JdbcConfig.class)//用于导入其他的配置类
@PropertySource("config/jdbcConfig.properties")
public class SpringConfiguration {
...
}
-
纯注解配置待改造
/**
* 账户的持久层实现类
* @author wen
*
*/
@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {
@Autowired
private DBAssit dbAssit;
@Override
public void save(Account account) {
dbAssit.update("insert into account(name,money) values(?,?)", account.getName(),account.getMoney());
}
@Override
public void update(Account account) {
dbAssit.update("update account set name=?,money=? where id=?", account.getName(),account.getMoney(),account.getId());
}
@Override
public void delete(Integer accountId) {
dbAssit.update("delete from account where id=?", accountId);
}
@Override
public Account findById(Integer accountId) {
return (Account) dbAssit.query("select *from account where id=?", new BeanHandler<Account>(Account.class), accountId);
}
@SuppressWarnings("unchecked")
@Override
public List<Account> findAll() {
return (List<Account>) dbAssit.query("select *from account", new BeanListHandler<Account>(Account.class));
}
}
/**
* 账户的业务层实现类
* @author wen
*
*/
@Service("accountService")
public class AccountServiceImpl implements IAccountService {
@Autowired
private IAccountDao accountDao;
@Override
public void saveAccount(Account account) {
accountDao.save(account);
}
@Override
public void updateAccount(Account account) {
accountDao.update(account);
}
@Override
public void deleteAccount(Integer accountId) {
accountDao.delete(accountId);
}
@Override
public Account findAccountById(Integer accountId) {
return accountDao.findById(accountId);
}
@Override
public List<Account> findAllAccount() {
return accountDao.findAll();
}
}
SpringConfiguration
/**
* spring的配置类,作用相当于bean.xml
* @author wen
*
*/
@Configuration//相当于表明当前类是spring的配置类,
//如果只是写到AnnotationConfigurationContext的构造函数的字节码中,可以不写
//如果是加载要扫描的包时,需要读到此类的配置,同时又没把此类的字节码提供给AnnotationConfigurationContext构造函数,则必须写
//@ComponentScan({"com.itheima","config"})//指定创建容器时要扫描的包
@ComponentScan("com.itheima")
@Import(JdbcConfig.class)//用于导入其他的配置类
@PropertySource("config/jdbcConfig.properties")
public class SpringConfiguration {
}
JdbcConfig
/**
* 和jdbc相关的配置类
* @author wen
*
*/
//@Configuration
public class JdbcConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
/**
* @bean注解:
* 作用:把当前方法的返回值作为bean对象存入spring容器中
* 属性:name:用于指定bean的id.如果没写该属性的话,默认值是当前的方法名
*spring框架在给带有bean注解的方法创建对象时,如果方法有参数,会用方法参数的数据类型前往容器中查找
*如果能找到唯一的一个类型匹配,则直接给方法的参数注入。
*如果找不到,就报错
*如果找到多个,就需要借助@Qualifier注解,此时他可以独立使用
*
* @return
*/
@Bean(name="dbAssit")
public DBAssit createDBAssit(@Qualifier("dataSource")DataSource dataSource) {
return new DBAssit(dataSource);
}
/**
* 创建数据源
* @return
*/
@Bean(name="dataSource")
public DataSource createDatasource1() {
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);
}
}
/*@Bean(name="ds1")
public DataSource createDatasource1() {
try {
ComboPooledDataSource ds=new ComboPooledDataSource();
ds.setDriverClass("com.mysql.jdbc.Driver");
ds.setJdbcUrl("jdbc:mysql://localhost:3306/spring5");
ds.setUser("root");
ds.setPassword("123");
return ds;
} catch (Exception e) {
throw new RuntimeException(e);
}
}*/
}
jdbcConfig.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test
jdbc.username=root
jdbc.password=123456
/**
* 账户的测试类
* @author wen
*
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=SpringConfiguration.class)
public class AccountServiceTest {
//private ApplicationContext ac=new AnnotationConfigApplicationContext(SpringConfiguration.class,JdbcConfig.class);
//private ApplicationContext ac=new AnnotationConfigApplicationContext(SpringConfiguration.class);
//private IAccountService accountService=ac.getBean("accountService", IAccountService.class);
@Autowired
private IAccountService accountService;
@Test
public void testFindAccountById() {
Account account=accountService.findAccountById(1);
System.out.println(account);
}
}
/*
Account [id=1, name=springIocAccount, money=6666.0]
*/
测试类的是不会配置在bean.xml配置文件中的,因为测试类知识用来测试,配置在xml配置文件中会是每次启动容器都加载不必要的内容

-
AOP(面向切面编程)
在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

-
动态代理的具体应用



-
账户的业务层接口
/**
* 账户的业务层接口
* @author wen
*
*/
public interface IAccountService {
/**
* 保存账户
* @param account
*/
void saveAccount(Account account);
/**
* 更新账户
* @param account
*/
void updateAccount(Account account);
/**
* 删除账户
* @param accountId
*/
void deleteAccount(Integer accountId);
/**
* 根据id查询账户
* @param accountId
*/
Account findAccountById(Integer accountId);
/**
* 查询所有账户
* @return
*/
List<Account> findAllAccount();
/**
* 转账
* @param sourceName转出账户
* @param targetName转入账户
* @param money钱
*/
void transfer(String sourceName,String targetName,Float money);
}
-
账户的业务层实现类
/**
* 账户的业务层实现类
*
* @author wen 在javaee分层开发中,事务一般处于业务层
*/
public class AccountServiceImpl implements IAccountService {
private IAccountDao accountDao = new AccountDaoImpl();
public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public void saveAccount(Account account) {
accountDao.save(account);
}
@Override
public void updateAccount(Account account) {
accountDao.update(account);
}
@Override
public void deleteAccount(Integer accountId) {
accountDao.delete(accountId);
}
@Override
public Account findAccountById(Integer accountId) {
return accountDao.findById(accountId);
}
@Override
public List<Account> findAllAccount() {
return accountDao.findAll();
}
@Override
public void transfer(String sourceName, String targetName, Float money) {
// 1、根据名称查询转出账户
Account source = accountDao.findByName(sourceName);
// 2、根据名称查询转入账户
Account target = accountDao.findByName(targetName);
// 3、转出账户减钱
source.setMoney(source.getMoney() - money);
// 4、转入账户加钱
target.setMoney(target.getMoney() + money);
// 5、更新转出账户
accountDao.update(source);
//int i = 3 / 0;
// 6、更新转入账户
accountDao.update(target);
}
}
-
用于生产业务层代理对象的工厂类
/**
* 用于生产业务层代理对象的工厂类
* @author wen
*
*/
public class BeanFactory {
/**
* 用于创建代理对象
* @return
*/
public static IAccountService getProxyAccountService() {
final IAccountService accountService=new AccountServiceImpl();
IAccountService iAccountService=(IAccountService) Proxy.newProxyInstance(accountService.getClass().getClassLoader(), accountService.getClass().getInterfaces(), new InvocationHandler() {
/**
* 添加事务的支持
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object rtVaule=null;
try {
//1、开启事务
TransactionManger.beginTransaction();
//2、执行调用业务层方法
rtVaule=method.invoke(accountService, args);
//3、提交事务
TransactionManger.commit();
}catch(Exception e) {
//4、回滚事务
TransactionManger.rollback();
System.out.println(e);
}finally {
//5、释放资源
TransactionManger.release();
}
return rtVaule;
}
});
return iAccountService;
}
}
测试类测试转钱,在转钱的引发异常
@Test
public void testTransfer() {
accountService.transfer("aaa", "bbb", 100f);
}
- Aop相关术语
所有的切入点都是连接点,但不是有的连接点都是切入点
例如 账户的业务层接口 如果新增test方法,不需要连接数据库,就不需要提交事务等操作




-
spring基于XML的AOP
全局配方式:
public void com.itheima.service.impl.AccountServiceImpl.saveAccount()
spring基于XML的AOP配置步骤:
1、前期准备:
1、拷贝AOP的jar包
2、在配置文件中导入AOP的约束
2、配置步骤
1、把通知bean也交给spring来管理
2、使用aop名称空间下的aop:config标签开始aop的配置
3、使用aop:aspect标签。开始配置切面。
aop:aspect属性:
id属性:用于给切面提供一个唯一标志。
ref属性是用于引用通知bean的id
4、使用aop:before标签配置前置通知.
method属性用于指定通知类中的哪个方法是前置通知.
pointcut属性用于指定切入点表达式。
切入点表达式:
关键字:execution(表达式)
表达式的写法:
访问修饰符 返回值 包名.包名....类名.方法名(参数列表)
全局配方式:
public void com.itheima.service.impl.AccountServiceImpl.saveAccount()
访问修饰符可以省略:
void com.itheima.service.impl.AccountServiceImpl.saveAccount()
返回值可以使用*,表示任意返回值类型。
* com.itheima.service.impl.AccountServiceImpl.saveAccount()
包名可以使用*,表示任意包,但是有几级包,需要写几个*。
* *.*.*.*.AccountServiceImpl.saveAccount()
包名可以使用(..)表示当前包及其子包
* com.itheima..AccountServiceImpl.saveAccount()
类名可以使用*,表示任意类
* *.*..*.saveAccount()
方法名可以使用*表示任意方法
参数列表可以指定具体类型:
基本类型直接写类型名称
* com.itheima..*.*(int)
引用类型必须是包名.类名的方式
* com.itheima..*.*(java.lang.String)
参数列表可以使用*号,表示任意参数类型,但是必须有参数
* com.itheima..*.*(*)
参数列表还可以使用..表示有无参数均可
* com.itheima..*.*(..)
全通配:* *..*.*(..)
在实际开发中,切入表达式的使用是根据需求决定的,我们一般是对业务层的方法进行增强
所以在写的时候:
* com.itheima.service.impl.*.*(..)
<?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">
<!-- 配置业务层 -->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
<!-- spring基于XML的AOP配置步骤:
1、前期准备:
1、拷贝AOP的jar包
2、在配置文件中导入AOP的约束
2、配置步骤
1、把通知bean也交给spring来管理
2、使用aop名称空间下的aop:config标签开始aop的配置
3、使用aop:aspect标签。开始配置切面。
aop:aspect属性:
id属性:用于给切面提供一个唯一标志。
ref属性是用于引用通知bean的id
4、使用aop:before标签配置前置通知.
method属性用于指定通知类中的哪个方法是前置通知.
pointcut属性用于指定切入点表达式。
切入点表达式:
关键字:execution(表达式)
表达式的写法:
访问修饰符 返回值 包名.包名....类名.方法名(参数列表)
全局配方式:
public void com.itheima.service.impl.AccountServiceImpl.saveAccount()
访问修饰符可以省略:
void com.itheima.service.impl.AccountServiceImpl.saveAccount()
返回值可以使用*,表示任意返回值类型。
* com.itheima.service.impl.AccountServiceImpl.saveAccount()
包名可以使用*,表示任意包,但是有几级包,需要写几个*。
* *.*.*.*.AccountServiceImpl.saveAccount()
包名可以使用(..)表示当前包及其子包
* com.itheima..AccountServiceImpl.saveAccount()
类名可以使用*,表示任意类
* *.*..*.saveAccount()
方法名可以使用*表示任意方法
参数列表可以指定具体类型:
基本类型直接写类型名称
* com.itheima..*.*(int)
引用类型必须是包名.类名的方式
* com.itheima..*.*(java.lang.String)
参数列表可以使用*号,表示任意参数类型,但是必须有参数
* com.itheima..*.*(*)
参数列表还可以使用..表示有无参数均可
* com.itheima..*.*(..)
全通配:* *..*.*(..)
在实际开发中,切入表达式的使用是根据需求决定的,我们一般是对业务层的方法进行增强
所以在写的时候:
* com.itheima.service.impl.*.*(..)
-->
<!-- 通知bean类 -->
<bean id="logger" class="com.itheima.utils.Logger"></bean>
<aop:config>
<aop:aspect id="logAdvice" ref="logger">
<aop:before method="beforePrintLog" pointcut="execution(public void com.itheima.service.impl.AccountServiceImpl.saveAccount())"></aop:before>
</aop:aspect>
</aop:config>
</beans>
/**
* 账户的业务层接口
* @author wen
*
*/
public interface IAccountService {
/**
* 模拟保存方法,无返回值无参
*/
void saveAccount();
/**
* 模拟更新方法,无返回值有参
*
* @param i
*/
void updateAccount(int i);
/**
* 模拟删除方法,有返回值,无参
* @return
*/
int deleteAccount();
}
/**
* 模拟账户的业务层实现类
* @author wen
*
*/
public class AccountServiceImpl implements IAccountService{
@Override
public void saveAccount() {
System.out.println("保存了账户!");
}
@Override
public void updateAccount(int i) {
System.out.println("更新了账户!" + i);
}
@Override
public int deleteAccount() {
System.out.println("删除了账户!");
return 0;
}
}
/**
* 一个通知类,用于对业务层方法进行增强,模拟记录日志
* @author wen
*
*/
public class Logger {
/**
* 计划让其在切入点方法执行之前执行
*/
public void beforePrintLog() {
System.out.println("Logger类中的beforePrintLog开始记录日志了");
}
}
/**
* 模拟一个表现层,调用业务层
*
* @author wen
*/
public class Client {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
IAccountService accountService = ac.getBean("accountService", IAccountService.class);
accountService.saveAccount();
}
}
/*
...
信息: Loading XML bean definitions from class path resource [bean.xml]
Logger类中的beforePrintLog开始记录日志了
保存了账户!
*/
-
多个方法测试
/**
* 模拟一个表现层,调用业务层
*
* @author wen
*/
public class Client {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
IAccountService accountService = ac.getBean("accountService", IAccountService.class);
accountService.saveAccount();
accountService.updateAccount(1);
accountService.deleteAccount();
}
}
/*
...
信息: Loading XML bean definitions from class path resource [bean.xml]
Logger类中的beforePrintLog开始记录日志了
保存了账户!
更新了账户!1
删除了账户!
*/
只增强了全匹配的方法,
-
全通配
<?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">
<!-- 配置业务层 -->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
<!-- spring基于XML的AOP配置步骤:
1、前期准备:
1、拷贝AOP的jar包
2、在配置文件中导入AOP的约束
2、配置步骤
1、把通知bean也交给spring来管理
2、使用aop名称空间下的aop:config标签开始aop的配置
3、使用aop:aspect标签。开始配置切面。
aop:aspect属性:
id属性:用于给切面提供一个唯一标志。
ref属性是用于引用通知bean的id
4、使用aop:before标签配置前置通知.
method属性用于指定通知类中的哪个方法是前置通知.
pointcut属性用于指定切入点表达式。
切入点表达式:
关键字:execution(表达式)
表达式的写法:
访问修饰符 返回值 包名.包名....类名.方法名(参数列表)
全局配方式:
public void com.itheima.service.impl.AccountServiceImpl.saveAccount()
访问修饰符可以省略:
void com.itheima.service.impl.AccountServiceImpl.saveAccount()
返回值可以使用*,表示任意返回值类型。
* com.itheima.service.impl.AccountServiceImpl.saveAccount()
包名可以使用*,表示任意包,但是有几级包,需要写几个*。
* *.*.*.*.AccountServiceImpl.saveAccount()
包名可以使用(..)表示当前包及其子包
* com.itheima..AccountServiceImpl.saveAccount()
类名可以使用*,表示任意类
* *.*..*.saveAccount()
方法名可以使用*表示任意方法
参数列表可以指定具体类型:
基本类型直接写类型名称
* com.itheima..*.*(int)
引用类型必须是包名.类名的方式
* com.itheima..*.*(java.lang.String)
参数列表可以使用*号,表示任意参数类型,但是必须有参数
* com.itheima..*.*(*)
参数列表还可以使用..表示有无参数均可
* com.itheima..*.*(..)
全通配:* *..*.*(..)
在实际开发中,切入表达式的使用是根据需求决定的,我们一般是对业务层的方法进行增强
所以在写的时候:
* com.itheima.service.impl.*.*(..)
-->
<!-- 通知bean类 -->
<bean id="logger" class="com.itheima.utils.Logger"></bean>
<aop:config>
<aop:aspect id="logAdvice" ref="logger">
<aop:before method="beforePrintLog" pointcut="execution(* com.itheima.service.impl.AccountServiceImpl.*(..))"></aop:before>
</aop:aspect>
</aop:config>
</beans>
/**
* 模拟一个表现层,调用业务层
*
* @author wen
*/
public class Client {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
IAccountService accountService = ac.getBean("accountService", IAccountService.class);
accountService.saveAccount();
accountService.updateAccount(1);
accountService.deleteAccount();
}
}
/*
...
信息: Loading XML bean definitions from class path resource [bean.xml]
Logger类中的beforePrintLog开始记录日志了
保存了账户!
Logger类中的beforePrintLog开始记录日志了
更新了账户!1
Logger类中的beforePrintLog开始记录日志了
删除了账户!
*/
-
常见的四种通知类型
-
后置通知
/**
* 模拟账户的业务层实现类
* @author wen
*
*/
public class AccountServiceImpl implements IAccountService{
@Override
public void saveAccount() {
System.out.println("保存了账户!");
}
/**
* 一个通知类,用于对业务层方法进行增强,模拟记录日志
* @author wen
*
*/
public class Logger {
/**
* 前置通知
*/
public void beforePrintLog() {
System.out.println("Logger类中的beforePrintLog开始记录日志了...前置通知");
}
/**
* 后置通知
*/
public void afterReturningPrintLog() {
System.out.println("Logger类中的afterReturningPrintLog开始记录日志了...后置通知");
}
/**
* 异常通知
*/
public void afterThrowingPrintLog() {
System.out.println("Logger类中的afterThrowingPrintLog开始记录日志了...异常通知");
}
/**
* 最终通知
*/
public void afterPrintLog() {
System.out.println("Logger类中的afterPrintLog开始记录日志了...最终通知");
}
}
/**
* 模拟一个表现层,调用业务层
*
* @author wen
*/
public class Client {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
IAccountService accountService = ac.getBean("accountService", IAccountService.class);
accountService.saveAccount();
/* accountService.updateAccount(1);
accountService.deleteAccount();*/
}
}
/*
...
信息: Loading XML bean definitions from class path resource [bean.xml]
Logger类中的beforePrintLog开始记录日志了...前置通知
保存了账户!
Logger类中的afterReturningPrintLog开始记录日志了...后置通知
Logger类中的afterPrintLog开始记录日志了...最终通知
*/
-
异常通知
/**
* 模拟账户的业务层实现类
* @author wen
*
*/
public class AccountServiceImpl implements IAccountService{
@Override
public void saveAccount() {
System.out.println("保存了账户!");
int i = 3 / 0;
}
}
/**
* 模拟一个表现层,调用业务层
*
* @author wen
*/
public class Client {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
IAccountService accountService = ac.getBean("accountService", IAccountService.class);
accountService.saveAccount();
/* accountService.updateAccount(1);
accountService.deleteAccount();*/
}
}
/*
...
信息: Loading XML bean definitions from class path resource [bean.xml]
Logger类中的beforePrintLog开始记录日志了...前置通知
保存了账户!
Logger类中的afterThrowingPrintLog开始记录日志了...异常通知
Logger类中的afterPrintLog开始记录日志了...最终通知
Exception in thread "main" java.lang.ArithmeticException: / by zero
*/
- 环绕通知
问题:
当我们配置了环绕通知之后,执行切入点方法时,最终的结果是环绕通知的代码执行了,而切入点方法却没有执行
分析:
根据动态代理的代码分析,可以看到invoke方法中有一个明确调用切入点方法的代码,而spring中的环绕通知目前没有调用切入点方法
解决办法:
思路:我们也需要在环绕通知中明确调用一下切入点方法
spring框架为我们提供了一个接口ProceedingJoinPoint,该接口可以作为环绕通知的方法参数来使用,
在程序运行时,spring框架会为我们注入该接口的实现类供我们使用
该接口有个方法:proceed()方法,他就相当于明确调用切入点方法
环绕通知是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: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">
<!-- 配置业务层 -->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
<!-- 通知bean类 -->
<bean id="logger" class="com.itheima.utils.Logger"></bean>
<aop:config>
<aop:aspect id="logAdvice" ref="logger">
<!-- 配置前置通知:它永远都会在切入点方法执行之前执行
<aop:before method="beforePrintLog" pointcut="execution(* com.itheima.service.impl.AccountServiceImpl.*(..))"></aop:before>-->
<!-- 配置后置通知:当切入点方法正常执行之后,后置通知执行。它和异常通知只能有一个执行
<aop:after-returning method="afterReturningPrintLog" pointcut="execution(* com.itheima.service.impl.AccountServiceImpl.*(..))"></aop:after-returning>-->
<!-- 配置异常通知:当切入点方法执行产生异常后执行
<aop:after-throwing method="afterThrowingPrintLog" pointcut="execution(* com.itheima.service.impl.AccountServiceImpl.*(..))"></aop:after-throwing>-->
<!-- 配置最终通知:无论切入点方式是否正常执行,它都会在其后执行
<aop:after method="afterPrintLog" pointcut="execution(* com.itheima.service.impl.AccountServiceImpl.*(..))"></aop:after>-->
<!-- 配置环绕通知 :详细注释在logger类中-->
<aop:around method="aroundPrintLog" pointcut="execution(* com.itheima.service.impl.AccountServiceImpl.*(..))"></aop:around>
</aop:aspect>
</aop:config>
</beans>
/**
* 模拟账户的业务层实现类
* @author wen
*
*/
public class AccountServiceImpl implements IAccountService{
@Override
public void saveAccount() {
System.out.println("保存了账户!");
int i = 3 / 0;
}
...
}
/**
* 一个通知类,用于对业务层方法进行增强,模拟记录日志
* @author wen
*
*/
public class Logger {
...
/**
* 环绕通知
* 问题:
* 当我们配置了环绕通知之后,执行切入点方法时,最终的结果是环绕通知的代码执行了,而切入点方法却没有执行
* 分析:
* 根据动态代理的代码分析,可以看到invoke方法中有一个明确调用切入点方法的代码,而spring中的环绕通知目前没有调用切入点方法
* 解决办法:
* 思路:我们也需要在环绕通知中明确调用一下切入点方法
* spring框架为我们提供了一个接口ProceedingJoinPoint,该接口可以作为环绕通知的方法参数来使用,
* 在程序运行时,spring框架会为我们注入该接口的实现类供我们使用
* 该接口有个方法:proceed()方法,他就相当于明确调用切入点方法
* 环绕通知是spring提供的一种可以在代码中手动控制通知何时执行的方式方法
*
*/
public Object aroundPrintLog(ProceedingJoinPoint pjp) {
Object rtValue=null;
try {
System.out.println("Logger类中的arroundPrintLog开始记录日志了--前置");
//先获取方法所需的参数
Object[] args = pjp.getArgs();
rtValue=pjp.proceed(args);
System.out.println("Logger类中的arroundPrintLog开始记录日志了--后置");
} catch (Throwable e) {
System.out.println("Logger类中的arroundPrintLog开始记录日志了--异常");
e.printStackTrace();
}finally {
System.out.println("Logger类中的arroundPrintLog开始记录日志了--最终");
}
return rtValue;
}
}
public class Client {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
IAccountService accountService = ac.getBean("accountService", IAccountService.class);
accountService.saveAccount();
/* accountService.updateAccount(1);
accountService.deleteAccount();*/
}
}
/*
信息: Loading XML bean definitions from class path resource [bean.xml]
Logger类中的arroundPrintLog开始记录日志了--前置
保存了账户!
Logger类中的arroundPrintLog开始记录日志了--异常
java.lang.ArithmeticException: / by zero
...
at com.itheima.ui.Client.main(Client.java:18)
Logger类中的arroundPrintLog开始记录日志了--最终
*/
- 动态代理环绕通知对比Spring xml环绕通知

-
通用化切入点表达式
使用aop:pointcut标签可以配置通用切入点表达式写在切面内部只能当前切面aop:aspect使用 。如果要所有切面都使用,要写在aop:aspect外面
注意:aop:pointcut必须写在要使用的aop:aspect的上面否则会报错
<?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">
<!-- 配置业务层 -->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
<!-- 通知bean类 -->
<bean id="logger" class="com.itheima.utils.Logger"></bean>
<aop:config>
<!-- 使用aop:pointcut标签可以配置通用切入点表达式 写在切面内部只能当前切面aop:aspect使用 。如果要所有切面都使用,要写在aop:aspect外面-->
<aop:pointcut expression="execution(* com.itheima..*.*(..))" id="pt1"/>
<aop:aspect id="logAdvice" ref="logger">
<!-- 配置前置通知:它永远都会在切入点方法执行之前执行
<aop:before method="beforePrintLog" pointcut="execution(* com.itheima.service.impl.AccountServiceImpl.*(..))"></aop:before>-->
<!-- 配置后置通知:当切入点方法正常执行之后,后置通知执行。它和异常通知只能有一个执行
<aop:after-returning method="afterReturningPrintLog" pointcut="execution(* com.itheima.service.impl.AccountServiceImpl.*(..))"></aop:after-returning>-->
<!-- 配置异常通知:当切入点方法执行产生异常后执行
<aop:after-throwing method="afterThrowingPrintLog" pointcut="execution(* com.itheima.service.impl.AccountServiceImpl.*(..))"></aop:after-throwing>-->
<!-- 配置最终通知:无论切入点方式是否正常执行,它都会在其后执行
<aop:after method="afterPrintLog" pointcut="execution(* com.itheima.service.impl.AccountServiceImpl.*(..))"></aop:after>-->
<!-- 使用aop:pointcut标签可以配置通用切入点表达式 写在切面内部只能当前切面aop:aspect使用 。如果要所有切面都使用,要写在aop:aspect外面
<aop:pointcut expression="execution(* com.itheima..*.*(..))" id="pt1"/>-->
<!-- 配置环绕通知 :详细注释在logger类中
<aop:around method="aroundPrintLog" pointcut="execution(* com.itheima.service.impl.AccountServiceImpl.*(..))"></aop:around>-->
<aop:around method="aroundPrintLog" pointcut-ref="pt1"></aop:around>
</aop:aspect>
</aop:config>
</beans>
/**
* 模拟账户的业务层实现类
* @author wen
*
*/
public class AccountServiceImpl implements IAccountService{
@Override
public void saveAccount() {
System.out.println("保存了账户!");
int i = 3 / 0;
}
...
}
/**
* 一个通知类,用于对业务层方法进行增强,模拟记录日志
* @author wen
*
*/
public class Logger {
...
/**
* 环绕通知
* 问题:
* 当我们配置了环绕通知之后,执行切入点方法时,最终的结果是环绕通知的代码执行了,而切入点方法却没有执行
* 分析:
* 根据动态代理的代码分析,可以看到invoke方法中有一个明确调用切入点方法的代码,而spring中的环绕通知目前没有调用切入点方法
* 解决办法:
* 思路:我们也需要在环绕通知中明确调用一下切入点方法
* spring框架为我们提供了一个接口ProceedingJoinPoint,该接口可以作为环绕通知的方法参数来使用,
* 在程序运行时,spring框架会为我们注入该接口的实现类供我们使用
* 该接口有个方法:proceed()方法,他就相当于明确调用切入点方法
* 环绕通知是spring提供的一种可以在代码中手动控制通知何时执行的方式方法
*
*/
public Object aroundPrintLog(ProceedingJoinPoint pjp) {
Object rtValue=null;
try {
System.out.println("Logger类中的arroundPrintLog开始记录日志了--前置");
//先获取方法所需的参数
Object[] args = pjp.getArgs();
rtValue=pjp.proceed(args);
System.out.println("Logger类中的arroundPrintLog开始记录日志了--后置");
} catch (Throwable e) {
System.out.println("Logger类中的arroundPrintLog开始记录日志了--异常");
e.printStackTrace();
}finally {
System.out.println("Logger类中的arroundPrintLog开始记录日志了--最终");
}
return rtValue;
}
}
public class Client {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
IAccountService accountService = ac.getBean("accountService", IAccountService.class);
accountService.saveAccount();
/* accountService.updateAccount(1);
accountService.deleteAccount();*/
}
}
/*
信息: Loading XML bean definitions from class path resource [bean.xml]
Logger类中的arroundPrintLog开始记录日志了--前置
保存了账户!
Logger类中的arroundPrintLog开始记录日志了--异常
java.lang.ArithmeticException: / by zero
...
at com.itheima.ui.Client.main(Client.java:18)
Logger类中的arroundPrintLog开始记录日志了--最终
*/
-
Spring AOP注解
注意:Spring AOP注解中通知顺序有bug,最终通知的执行顺序有问题,本来应该最后一个执行变成了第二执行
<?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="com.itheima"></context:component-scan>
<!-- 开启Spring对注解AOP的支持 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy> <!-- 该标签写上就是支持不写就是不支持 -->
</beans>
/**
* 一个通知类,用于对业务层方法进行增强,模拟记录日志
*
* @author wen
*/
@Component("logger")
@Aspect //把当前类配置成一个切面
public class Logger {
@Pointcut("execution(* com.itheima..*.*(..))")
private void pt1() { }
/**
* 前置通知
*/
@Before("pt1()") //报错是因为缺少切入点表达式
public void beforePrintLog() {
System.out.println("Logger类中的beforePrintLog开始记录日志了...前置通知");
}
/**
* 后置通知
*/
@AfterReturning("pt1()")
public void afterReturningPrintLog() {
System.out.println("Logger类中的afterReturningPrintLog开始记录日志了...后置通知");
}
/**
* 异常通知
*/
@AfterThrowing("pt1()")
public void afterThrowingPrintLog() {
System.out.println("Logger类中的afterThrowingPrintLog开始记录日志了...异常通知");
}
/**
* 最终通知
*/
@After("pt1()")
public void afterPrintLog() {
System.out.println("Logger类中的afterPrintLog开始记录日志了...最终通知");
}
}
public class Client {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
IAccountService accountService = ac.getBean("accountService", IAccountService.class);
accountService.saveAccount();
/* accountService.updateAccount(1);
accountService.deleteAccount();*/
}
}
/*
信息: Loading XML bean definitions from class path resource [bean.xml]
Logger类中的beforePrintLog开始记录日志了...前置通知
保存了账户!
Logger类中的afterPrintLog开始记录日志了...最终通知
Logger类中的afterReturningPrintLog开始记录日志了...后置通知
*/
/*
信息: Loading XML bean definitions from class path resource [bean.xml]
Logger类中的beforePrintLog开始记录日志了...前置通知
保存了账户!
Logger类中的afterPrintLog开始记录日志了...最终通知
Logger类中的afterThrowingPrintLog开始记录日志了...异常通知
Exception in thread "main" java.lang.ArithmeticException: / by zero
*/
- 解决方案
改为环绕通知,环绕通知的执行顺序是根据方法内部的执行顺序执行的
@Configuration
-
Spring AOP纯注解配置
package config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
/**
* Spring的配置类,相当于bean.xml
* @author Toroidal
* @date 2020/3/30 21:00
*/
@Configuration //表明当前类是spring的配置类
@ComponentScan("com.itheima") //指定要扫描的包
@EnableAspectJAutoProxy //开启Spring对注解AOP的支持
public class SpringConfiguration {
}
/**
* 模拟账户的业务层实现类
* @author Toroidal
* @date 2020/3/30 22:31
*/
@Service("accountService")
public class AccountServiceImpl implements IAccountService{
@Override
public void saveAccount() {
System.out.println("保存了账户!");
//int i = 3 / 0;
}
@Override
public void updateAccount(int i) {
System.out.println("更新了账户!" + i);
}
@Override
public int deleteAccount() {
System.out.println("删除了账户!");
return 0;
}
}
/**
* 一个通知类,用于对业务层方法进行增强,模拟记录日志
*
* @author wen
*/
@Component("logger")
@Aspect //把当前类配置成一个切面
public class Logger {
@Pointcut("execution(* com.itheima..*.*(..))")
private void pt1() { }
/**
* 环绕通知
* 问题:
* 当我们配置了环绕通知之后,执行切入点方法时,最终的结果是环绕通知的代码执行了,而切入点方法却没有执行
* 分析:
* 根据动态代理的代码分析,可以看到invoke方法中有一个明确调用切入点方法的代码,而spring中的环绕通知目前没有调用切入点方法
* 解决办法:
* 思路:我们也需要在环绕通知中明确调用一下切入点方法
* spring框架为我们提供了一个接口ProceedingJoinPoint,该接口可以作为环绕通知的方法参数来使用,
* 在程序运行时,spring框架会为我们注入该接口的实现类供我们使用
* 该接口有个方法:proceed()方法,他就相当于明确调用切入点方法
* 环绕通知是spring提供的一种可以在代码中手动控制通知何时执行的方式方法
*/
//@Around("pt1()")m
@Around("execution(* com.itheima..*.*(..))")
public Object aroundPrintLog(ProceedingJoinPoint pjp) {
Object rtValue = null;
try {
System.out.println("Logger类中的arroundPrintLog开始记录日志了--前置");
//先获取方法所需的参数
Object[] args = pjp.getArgs();
rtValue = pjp.proceed(args);
System.out.println("Logger类中的arroundPrintLog开始记录日志了--后置");
} catch (Throwable e) {
System.out.println("Logger类中的arroundPrintLog开始记录日志了--异常");
e.printStackTrace();
} finally {
System.out.println("Logger类中的arroundPrintLog开始记录日志了--最终");
}
return rtValue;
}
}
public static void main(String[] args) {
ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
IAccountService accountService = ac.getBean("accountService", IAccountService.class);
accountService.saveAccount();
/* accountService.updateAccount(1);
accountService.deleteAccount();*/
}
/*
信息: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@6477463f: startup date [Mon Mar 30 22:30:37 CST 2020]; root of context hierarchy
Logger类中的arroundPrintLog开始记录日志了--前置
保存了账户!
Logger类中的arroundPrintLog开始记录日志了--后置
Logger类中的arroundPrintLog开始记录日志了--最终
*/
-
Spring基于Xml的IOC和AOP使用
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
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">
<!--IOC的配置-->
<!--配置业务层对象-->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
<!--配置set方式注入,在AccountServiceImpl类中注入AccountDaoImpl的对象-->
<property name="accountDao" ref="accountDao"></property>
</bean>
<!--配置持久层对象-->
<bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
<!--配置set方式注入,在AccountDaoImpl类中注入DBAssit的对象-->
<property name="dbAssit" ref="dbAssit"></property>
</bean>
<!--配置操作数据库的对象-->
<bean id="dbAssit" class="com.wangyu.dbassit.DBAssit">
<property name="dataSource" ref="dataSource"></property>
<property name="useCurrentConnection" value="true"></property>
</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/test"></property>
<property name="user" value="root"></property>
<property name="password" value="123456"></property>
</bean>
<!--把通知Bean也交给spring来管理-->
<bean id="transactionManager" class="com.itheima.util.TransactionManger">
<property name="dbAssit" ref="dbAssit"></property>
</bean>
<!--AOP的配置-->
<aop:config>
<!--配置通用的切入点表达式-->
<aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"/>
<!--配置切面-->
<aop:aspect id="txAdvice" ref="transactionManager">
<!--开启事务的通知-->
<aop:before method="beginTransaction" pointcut-ref="pt1"></aop:before>
<!--提交事务的通知-->
<aop:after-returning method="commit" pointcut-ref="pt1"></aop:after-returning>
<!--回滚事务的通知-->
<aop:after-throwing method="rollback" pointcut-ref="pt1"></aop:after-throwing>
<!--释放资源的通知-->
<aop:after method="release" pointcut-ref="pt1"></aop:after>
</aop:aspect>
</aop:config>
</beans>
/**
* @author Toroidal
* @date 2020/4/1 12:48
*/
public class AccountDaoImpl implements IAccountDao {
//private DBAssit dbAssit=new DBAssit(C3P0Utils.getDataSource(),true);
private DBAssit dbAssit; //需要把连接和线程绑定
public void setDbAssit(DBAssit dbAssit) {
this.dbAssit = dbAssit;
}
@Override
public void save(Account account) {
dbAssit.update("insert into account(name,money) values(?,?)", account.getName(), account.getMoney());
}
@Override
public void update(Account account) {
dbAssit.update("update account set name=?,money=? where id=?", account.getName(), account.getMoney(), account.getId());
}
@Override
public void delete(Integer accountId) {
dbAssit.update("delete from account where id=?", accountId);
}
@Override
public Account findById(Integer accountId) {
return (Account) dbAssit.query("select *from account where id=?", new BeanHandler<Account>(Account.class), accountId);
}
@SuppressWarnings("unchecked")
@Override
public List<Account> findAll() {
return (List<Account>) dbAssit.query("select *from account", new BeanListHandler<Account>(Account.class));
}
@SuppressWarnings("all")
@Override
public Account findByName(String accountName) {
List<Account> accounts = (List<Account>) dbAssit.query("select *from account where name=?", new BeanListHandler<Account>(Account.class), accountName);
if (accounts.isEmpty()) {
return null;
}
/*if(accounts.size()>1) {
throw new RuntimeException("结果不唯一,程序逻辑有问题");
}*/
//System.out.println(accounts.size());
return accounts.get(0);
}
}
/**
* 账户的业务层实现类
*
* @author wen 在javaee分层开发中,事务一般处于业务层
*/
public class AccountServiceImpl implements IAccountService {
private IAccountDao accountDao ;
public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}
...
@Override
public void transfer(String sourceName, String targetName, Float money) {
// 1、根据名称查询转出账户
Account source = accountDao.findByName(sourceName);
// 2、根据名称查询转入账户
Account target = accountDao.findByName(targetName);
// 3、转出账户减钱
source.setMoney(source.getMoney() - money);
// 4、转入账户加钱
target.setMoney(target.getMoney() + money);
// 5、更新转出账户
accountDao.update(source);
//int i = 3 / 0;
// 6、更新转入账户
accountDao.update(target);
}
}
/**
* 和事务管理相关的工具类
*
* @author wen
*/
public class TransactionManger {
private DBAssit dbAssit;
public void setDbAssit(DBAssit dbAssit) {
this.dbAssit = dbAssit;
}
/**
* 开启事务(把自动提交关了)
*/
public void beginTransaction() {
try {
dbAssit.getCurrentConnection().setAutoCommit(false);
System.out.println("开启事务");
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* 提交事务
*/
public void commit() {
try {
dbAssit.getCurrentConnection().commit();
System.out.println("提交事务");
;
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* 回滚事务
*/
public void rollback() {
try {
dbAssit.getCurrentConnection().rollback();
System.out.println("回滚事务");
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* 释放资源
*/
public void release() {
try {
dbAssit.releaseConnection();
System.out.println("释放资源");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
/**
* 测试类
* @author Toroidal
* @date 2020/4/1 12:50
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountServiceTest {
@Autowired
private IAccountService accountService;
@Test
public void testTransfer() {
accountService.transfer("aaa", "bbb", 100f);
}
}
/*
...
开启事务
提交事务
释放资源
*/


-
Spring基于注解的IOC和AOP使用(半注解)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
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">
<!--IOC的配置-->
<!--告知Spring创建容器时要扫描的包-->
<context:component-scan base-package="com.itheima"></context:component-scan>
<!--IOC的配置-->
<!--配置业务层对象
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
<!-配置set方式注入,在AccountServiceImpl类中注入AccountDaoImpl的对象
<property name="accountDao" ref="accountDao"></property>
</bean>
<!-配置持久层对象
<bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
<!-配置set方式注入,在AccountDaoImpl类中注入DBAssit的对象
<property name="dbAssit" ref="dbAssit"></property>
</bean>
-->
<!--配置操作数据库的对象-->
<bean id="dbAssit" class="com.wangyu.dbassit.DBAssit">
<property name="dataSource" ref="dataSource"></property>
<property name="useCurrentConnection" value="true"></property>
</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/test"></property>
<property name="user" value="root"></property>
<property name="password" value="123456"></property>
</bean>
<!--开启Spring对注解AOP的支持-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<!--把通知Bean也交给spring来管理
<bean id="transactionManager" class="com.itheima.util.TransactionManger">
<property name="dbAssit" ref="dbAssit"></property>
</bean>
-->
<!--AOP的配置
<aop:config>
<!-配置通用的切入点表达式->
<aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"/>
<!-配置切面->
<aop:aspect id="txAdvice" ref="transactionManager">
<!-开启事务的通知->
<aop:before method="beginTransaction" pointcut-ref="pt1"></aop:before>
<!-提交事务的通知->
<aop:after-returning method="commit" pointcut-ref="pt1"></aop:after-returning>
<!-回滚事务的通知->
<aop:after-throwing method="rollback" pointcut-ref="pt1"></aop:after-throwing>
<!-释放资源的通知->
<aop:after method="release" pointcut-ref="pt1"></aop:after>
</aop:aspect>
</aop:config> -->
</beans>
/**
* @author Toroidal
* @date 2020/4/1 12:48
*/
@Repository(value = "accountDao")
public class AccountDaoImpl implements IAccountDao {
//private DBAssit dbAssit=new DBAssit(C3P0Utils.getDataSource(),true);
@Resource(name = "dbAssit")
private DBAssit dbAssit; //需要把连接和线程绑定
@Override
public void save(Account account) {
dbAssit.update("insert into account(name,money) values(?,?)", account.getName(), account.getMoney());
}
@Override
public void update(Account account) {
dbAssit.update("update account set name=?,money=? where id=?", account.getName(), account.getMoney(), account.getId());
}
@Override
public void delete(Integer accountId) {
dbAssit.update("delete from account where id=?", accountId);
}
@Override
public Account findById(Integer accountId) {
return (Account) dbAssit.query("select *from account where id=?", new BeanHandler<Account>(Account.class), accountId);
}
@SuppressWarnings("unchecked")
@Override
public List<Account> findAll() {
return (List<Account>) dbAssit.query("select *from account", new BeanListHandler<Account>(Account.class));
}
@SuppressWarnings("all")
@Override
public Account findByName(String accountName) {
List<Account> accounts = (List<Account>) dbAssit.query("select *from account where name=?", new BeanListHandler<Account>(Account.class), accountName);
if (accounts.isEmpty()) {
return null;
}
/*if(accounts.size()>1) {
throw new RuntimeException("结果不唯一,程序逻辑有问题");
}*/
//System.out.println(accounts.size());
return accounts.get(0);
}
}
/**
* 账户的业务层实现类
*
* @author Toroidal 在javaee分层开发中,事务一般处于业务层
*/
@Service("accountService")
public class AccountServiceImpl implements IAccountService {
@Autowired
private IAccountDao accountDao ;
...
@Override
public void transfer(String sourceName, String targetName, Float money) {
// 1、根据名称查询转出账户
Account source = accountDao.findByName(sourceName);
// 2、根据名称查询转入账户
Account target = accountDao.findByName(targetName);
// 3、转出账户减钱
source.setMoney(source.getMoney() - money);
// 4、转入账户加钱
target.setMoney(target.getMoney() + money);
// 5、更新转出账户
accountDao.update(source);
int i = 3 / 0;
// 6、更新转入账户
accountDao.update(target);
}
}
/**
* 和事务管理相关的工具类
*
* @author Toroidal
*/
@Component(value = "transactionManager")
@Aspect //切面注解
public class TransactionManger {
@Resource(name = "dbAssit")
private DBAssit dbAssit;
//public void setDbAssit(DBAssit dbAssit) {this.dbAssit = dbAssit; }
@Pointcut("execution(* com.itheima.service.*.*(..))")
private void pt1() { }
/**
* 开启事务(把自动提交关了)
*/
@Before("pt1()")
public void beginTransaction() {
try {
dbAssit.getCurrentConnection().setAutoCommit(false);
System.out.println("开启事务");
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* 提交事务
*/
@AfterReturning("pt1()")
public void commit() {
try {
dbAssit.getCurrentConnection().commit();
System.out.println("提交事务");
;
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* 回滚事务
*/
@AfterThrowing("pt1()")
public void rollback() {
try {
dbAssit.getCurrentConnection().rollback();
System.out.println("回滚事务");
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* 释放资源
*/
@After("pt1()")
public void release() {
try {
dbAssit.releaseConnection();
System.out.println("释放资源");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* 测试类
* @author Toroidal
* @date 2020/4/1 12:50
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountServiceTest {
@Autowired
private IAccountService accountService;
@Test
public void testTransfer() {
accountService.transfer("aaa", "bbb", 100f);
}
}
/*
开启事务
释放资源
com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: Can't call rollback when autocommit=true
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at com.mysql.jdbc.Util.handleNewInstance(Util.java:404)
*/
事务控制住了,数据库并没有被修改
-
Spring基于注解的IOC和AOP使用(纯注解)
-
Jdbc Template
-
Spring中的Jdbc Template

Spring为传统的jdbc API进行封装,简化持久层操作,虽然jdbcTemplate很灵活,但和ORM框架相比jdbcTemplate功能就显得力不从心了,学习jdbcTemplate是为学习ORM框架做铺垫
ORM:对象关系映射 O:对象 R:关系 M:映射

Jdbc Template入门案例
/**
* jdbctemplate的最基本使用
* @author Toroidal
* @date 2020/4/1 15:38
*
*/
public class JdbcTemplateDemo1 {
public static void main(String[] args) throws Exception{
ComboPooledDataSource ds=new ComboPooledDataSource();
ds.setDriverClass("com.mysql.jdbc.Driver");
ds.setJdbcUrl("jdbc:mysql://localhost:3306/test");
ds.setUser("root");
ds.setPassword("123456");
//1、获取jdbcTemplate对象
JdbcTemplate jt=new JdbcTemplate();
//给jdbctemplate设置数据源
jt.setDataSource(ds);
//2、调用方法
jt.execute("insert into account(name,money) values('jdbc',1000)");
}
}
/*
...
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
Process finished with exit code 0
*/

-
Spring容器配置Jdbc Template
<?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">
<!--配置jdbc操作模板-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</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/test"></property>
<property name="user" value="root"></property>
<property name="password" value="123456"></property>
</bean>
</beans>
/**
* jdbctemplate的最基本使用
*
* @author Toroidal
* @date 2020/4/1 15:58
*/
public class JdbcTemplateDemo2 {
public static void main(String[] args) throws Exception {
//1、获取容器
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2、获取对象
JdbcTemplate jt = ac.getBean("jdbcTemplate", JdbcTemplate.class);
//3、执行操作
jt.execute("insert into account(name, money) values('tony', 10000)");
}
}

public class JdbcTemplateDemo3 {
public static void main(String[] args) throws Exception {
// 1、获取容器
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
// 2、获取对象
JdbcTemplate jt = ac.getBean("jdbcTemplate", JdbcTemplate.class);
// 3、执行操作
// 保存
//jt.update("insert into account(name,money) values(?,?)","dbaa",1221);
// 更新
//jt.update("update account set name=?,money=? where id=?","taige",222,28);
// 删除
//jt.update("delete from account where id=?",29);
// 查询所有
// List<Account> list=jt.query("select *from account where money>?", new AccountRowMapper(), 1000);
// for (Account account : list) {
// System.out.println(account);
// }
// 查询一个
// List<Account> list=jt.query("select *from account where id=?", new AccountRowMapper(), 1);
// System.out.println(list.isEmpty()?"没有符合条件的数据":list.get(0));
Account account=jt.query("select *from account where id=?",new AccountResultSetExtractor(),1);
System.out.println(account);
// 查询返回一行一列:当我们的sql语句使用了聚合函数,并且没有group by子句时,返回的都是一行一列的结果
int count1=jt.queryForObject("select count(*) from account where money >?", Integer.class,1000);
System.out.println(count1);
}
}
//查询一个的参数
class AccountResultSetExtractor implements ResultSetExtractor<Account>{
@Override
public Account extractData(ResultSet rs) throws SQLException, DataAccessException {
Account account=null;
if(rs.next()) {
account=new Account();
account.setId(rs.getInt("id"));
account.setName(rs.getString("name"));
account.setMoney(rs.getFloat("money"));
}
return account;
}
}
//查询所有的参数
class AccountRowMapper implements RowMapper<Account>{
@Override
public Account mapRow(ResultSet rs, int rowNum) throws SQLException {
Account account=new Account();
account.setId(rs.getInt("id"));
account.setName(rs.getString("name"));
account.setMoney(rs.getFloat("money"));
return account;
}
}
/*
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
Account [id=1, name=springIocAccount, money=6666.0]
4
*/
-
Jdbc Template持久层实现
/**
* 账户的持久层实习类
* @author Toroidal
* @date 2020/4/1 18:09
*
*/
public class AccountDaoImpl implements IAccountDao{
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Override
public Account findAccountById(Integer accountID) {
List<Account> accounts=jdbcTemplate.query("select *from account where id=?", new BeanPropertyRowMapper<Account>(Account.class),accountID);
return accounts.isEmpty()?null:accounts.get(0);
}
@Override
public Account findAccountByName(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()>0) {
throw new RuntimeException("账户名称不唯一。程序逻辑有问题");
}
return accounts.get(0);
}
@Override
public void updateAccount(Account account) {
jdbcTemplate.update("update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
}
}
public class JdbcTemplateDemo4 {
public static void main(String[] args) throws Exception {
//1、获取容器
ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
//2、获取dao对象
IAccountDao accountDao=ac.getBean("accountDao2",IAccountDao.class);
//3、执行操作
Account account=accountDao.findAccountById(1);
System.out.println(account);
account.setMoney(7888f);
accountDao.updateAccount(account);
}
}
/*
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
Account [id=1, name=springIocAccount, money=6666.0]
*/
/*
*/

-
分析Dao中潜在的问题并通过抽取解决
dao层如果有多个类会出现重复的代码来获取jdbcTemplate对象,将代码抽取出来,让所有的需要使用JdbcTemplate对象的类来继承他
JdbcDaoSupport
/**
* 抽取dao中的重复代码
*
* @author wen
*/
public class JdbcDaoSupport {
private JdbcTemplate jdbcTemplate;
public JdbcTemplate getJdbcTemplate() {
return jdbcTemplate;
}
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
}
/**
* 账户的持久层实习类
* @author wen
*
*/
public class AccountDaoImpl2 extends JdbcDaoSupport implements IAccountDao{
@Override
public Account findAccountById(Integer accountID) {
List<Account> accounts=getJdbcTemplate().query("select *from account where id=?", new BeanPropertyRowMapper<Account>(Account.class),accountID);
return accounts.isEmpty()?null:accounts.get(0);
}
@Override
public Account findAccountByName(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()>0) {
throw new RuntimeException("账户名称不唯一。程序逻辑有问题");
}
return accounts.get(0);
}
@Override
public void updateAccount(Account account) {
getJdbcTemplate().update("update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
}
}
<?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">
<!-- 配置dao 不继承jdbcDaoSuppport-->
<bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate"></property>
</bean>
<!--配置jdbc操作模板 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置dao 继承jdbcDaoSuppport-->
<bean id="accountDao2" class="com.itheima.dao.impl.AccountDaoImpl2">
<property name="jdbcTemplate" ref="jdbcTemplate"></property>
</bean>
<!-- spring内置数据源 -->
<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/test"></property>
<property name="username" value="root"></property>
<property name="password" value="123456"></property>
</bean>
</beans>
/**
* jdbctemplate在dao中的使用
* @author wen
*
*/
public class JdbcTemplateDemo4 {
public static void main(String[] args) throws Exception {
//1、获取容器
ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
//2、获取dao对象
IAccountDao accountDao=ac.getBean("accountDao2",IAccountDao.class);
//3、执行操作
Account account=accountDao.findAccountById(1);
System.out.println(account);
account.setMoney(7888f);
accountDao.updateAccount(account);
}
}
/*
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
Account [id=1, name=springIocAccount, money=7888.0]
*/

-
Spring支持的数据源配置
- 配置数据源c3p0
<!-- 配置数据源c3p0-->
<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/spring5"></property>
<property name="user" value="root"></property>
<property name="password" value="123"></property>
</bean>
- spring内置数据源
<!-- spring内置数据源 -->
<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/test"></property>
<property name="username" value="root"></property>
<property name="password" value="123456"></property>
</bean>
-
BasicDataSource数据源

-
Spring中的事务控制

例如:专账异常
-
获取jdbcTemplate中的connection
/**
* 获取jdbcTemplate中的connection
* @author wen
*
*/
public class GetConnectionTest {
public static void main(String[] args) {
//1、获取容器
ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
//2、获取数据源
DataSource source = ac.getBean("dataSource",DataSource.class);
//3、使用spring提供的工具类,来根据数据源获取Connection
Connection conn1= DataSourceUtils.getConnection(source);
Connection conn2 = DataSourceUtils.getConnection(source);
System.out.println(conn1);
System.out.println(conn2);
System.out.println(conn1==conn2);
}
}
/*
com.mysql.jdbc.JDBC4Connection@df27fae
com.mysql.jdbc.JDBC4Connection@24a35978
false
*/
存在问的问题:要想控制事务,转账扣钱和加钱事务必须使用同一个连接,使用spring提供的工具类,来根据数据源获取Connection每次获取的连接都不一样,此时需要使用spring提供的事务管理器对象实现把connection和线程绑定
/**
* 获取jdbcTemplate中的connection
* @author wen
*
*/
public class GetConnectionTest {
public static void main(String[] args) {
//1、获取容器
ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
//2、获取数据源
DataSource source = ac.getBean("dataSource",DataSource.class);
//3、使用spring提供的事务管理器对象实现把connection和线程绑定
TransactionSynchronizationManager.initSynchronization();
//4、使用spring提供的工具类,来根据数据源获取Connection
Connection conn1= DataSourceUtils.getConnection(source);
Connection conn2 = DataSourceUtils.getConnection(source);
System.out.println(conn1);
System.out.println(conn2);
System.out.println(conn1==conn2);
}
}
/*
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
com.mysql.jdbc.JDBC4Connection@10d59286
com.mysql.jdbc.JDBC4Connection@10d59286
true
*/
存在的第二个问题:如果是多线程的时候把connection和线程绑定是否连接也是同一个呢
/**
* 获取jdbcTemplate中的connection
* @author wen
*
*/
public class GetConnectionTest {
public static void main(String[] args) {
//1、获取容器
ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
//2、获取数据源
DataSource source = ac.getBean("dataSource",DataSource.class);
//3、使用spring提供的事务管理器对象实现把connection和线程绑定
TransactionSynchronizationManager.initSynchronization();
//4、使用spring提供的工具类,来根据数据源获取Connection
Connection conn1= DataSourceUtils.getConnection(source);
Connection conn2 = DataSourceUtils.getConnection(source);
System.out.println(conn1);
System.out.println(conn2);
System.out.println(conn1==conn2);
Thread thl = new Thread(new Runnable() {
@Override
public void run() {
Connection conn3= DataSourceUtils.getConnection(source);
System.out.println(conn3);
}
});
thl.start();
}
}
/*
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
com.mysql.jdbc.JDBC4Connection@10d59286
com.mysql.jdbc.JDBC4Connection@10d59286
true
com.mysql.jdbc.JDBC4Connection@4f4d1dad
*/
- 事务控制测试
<?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="com.itheima.service.impl.AccountServiceimpl">
<property name="accountDao" ref="accountDao"></property>
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置dao 继承jdbcDaoSuppport-->
<bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- spring内置数据源 -->
<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/test"></property>
<property name="username" value="root"></property>
<property name="password" value="123456"></property>
</bean>
</beans>
/**
* 测试转账
* @author wen
*
*/
public class Client {
public static void main(String[] args) {
ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
AccountServiceimpl accountService = ac.getBean("accountService",AccountServiceimpl.class);
//执行方法
// Account account = accountService.findAccountById(1);
// System.out.println(account);
accountService.transfer("aaa", "bbb", 100f);
}
}
-
无异常测试
/**
* 账户的业务层实现类
* @author wen
*
*/
public class AccountServiceimpl implements IAccountService {
private IAccountDao accountDao;
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public void transfer(String sourceName, String targetname, Float money) {
Connection conn = null;
try {
//3、使用spring提供的事务管理器对象实现把connection和线程绑定
TransactionSynchronizationManager.initSynchronization();
//4、使用spring提供的工具类,来根据数据源获取Connection
conn = DataSourceUtils.getConnection(dataSource);
//改变Connection事务提交策略为手动提交
conn.setAutoCommit(false);
//1、根据账户名称,查询两个账户
Account source=accountDao.findAccountByName(sourceName);
Account target=accountDao.findAccountByName(targetname);
//2、转出账户减钱、转入账户加钱
source.setMoney(source.getMoney()-money);
target.setMoney(target.getMoney()+money);
//3、更新两个账户
accountDao.updateAccount(source);
//模拟转账异常
//int i=1/0;
accountDao.updateAccount(target);
//提交事务
conn.commit();
} catch (SQLException e) {
//回滚事务
try {
conn.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
e.printStackTrace();
}
}
}


转账成功
-
异常测试
/**
* 账户的业务层实现类
* @author wen
*
*/
public class AccountServiceimpl implements IAccountService {
private IAccountDao accountDao;
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public void transfer(String sourceName, String targetname, Float money) {
Connection conn = null;
try {
//3、使用spring提供的事务管理器对象实现把connection和线程绑定
TransactionSynchronizationManager.initSynchronization();
//4、使用spring提供的工具类,来根据数据源获取Connection
conn = DataSourceUtils.getConnection(dataSource);
//改变Connection事务提交策略为手动提交
conn.setAutoCommit(false);
//1、根据账户名称,查询两个账户
Account source=accountDao.findAccountByName(sourceName);
Account target=accountDao.findAccountByName(targetname);
//2、转出账户减钱、转入账户加钱
source.setMoney(source.getMoney()-money);
target.setMoney(target.getMoney()+money);
//3、更新两个账户
accountDao.updateAccount(source);
//模拟转账异常
int i=1/0;
accountDao.updateAccount(target);
//提交事务
conn.commit();
} catch (SQLException e) {
//回滚事务
try {
conn.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
e.printStackTrace();
}
}
}
/*
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
Process finished with exit code 0
*/


事务控制住了
-
事务控制代码问题
-
业务层应该只关心业务逻辑



-
使用Spring提供的API来实现事务控制
<?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="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置service -->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceimpl">
<property name="accountDao" ref="accountDao"></property>
<property name="ptm" ref="transactionManager"></property>
</bean>
<!-- 配置dao 继承jdbcDaoSuppport-->
<bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- spring内置数据源 -->
<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/test"></property>
<property name="username" value="root"></property>
<property name="password" value="123456"></property>
</bean>
</beans>
/**
* 账户的业务层实现类
*
* @author wen
*/
public class AccountServiceimpl implements IAccountService {
private IAccountDao accountDao;
private PlatformTransactionManager ptm;
public void setPtm(PlatformTransactionManager ptm) {
this.ptm = ptm;
}
public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public void transfer(String sourceName, String targetname, Float money) {
TransactionStatus status = null;
try{
DefaultTransactionDefinition definition=new DefaultTransactionDefinition();
//设置定义信息:隔离级别&传播行为&过期时间&是否只读
definition.setTimeout(-1);
definition.setReadOnly(false);
definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
status=ptm.getTransaction(definition);
//1、根据账户名称,查询两个账户
Account source = accountDao.findAccountByName(sourceName);
Account target = accountDao.findAccountByName(targetname);
//2、转出账户减钱、转入账户加钱
source.setMoney(source.getMoney() - money);
target.setMoney(target.getMoney() + money);
//3、更新两个账户
accountDao.updateAccount(source);
//模拟转账异常
int i = 1 / 0;
accountDao.updateAccount(target);
//事务的提交
ptm.commit(status);
}catch (Exception e){
//事务回滚
ptm.rollback(status);
e.printStackTrace();
}
}
}
/**
* 测试转账
* @author wen
*
*/
public class Client {
public static void main(String[] args) {
ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
AccountServiceimpl accountService = ac.getBean("accountService",AccountServiceimpl.class);
//执行方法
// Account account = accountService.findAccountById(1);
// System.out.println(account);
accountService.transfer("aaa", "bbb", 100f);
}
}
/*
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
java.lang.ArithmeticException: / by zero
at com.itheima.service.impl.AccountServiceimpl.transfer(AccountServiceimpl.java:57)
at com.itheima.ui.Client.main(Client.java:22)
*/
-
转账未成功,事务被成功控制住了

-
spring的事务控制模板(自带回滚提交方法)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置事务控制模板-->
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager"></property>
</bean>
<!-- 配置service -->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceimpl">
<property name="accountDao" ref="accountDao"></property>
<property name="transactionTemplate" ref="transactionTemplate"></property>
</bean>
<!-- 配置dao 继承jdbcDaoSuppport-->
<bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- spring内置数据源 -->
<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/test"></property>
<property name="username" value="root"></property>
<property name="password" value="123456"></property>
</bean>
</beans>
/**
* 账户的业务层实现类
*
* @author wen
*/
public class AccountServiceimpl implements IAccountService {
private IAccountDao accountDao;
private TransactionTemplate transactionTemplate;
public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
this.transactionTemplate = transactionTemplate;
}
public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public void transfer(String sourceName, String targetname, Float money) {
transactionTemplate.execute(new TransactionCallback<Object>() {
@Override
public Object doInTransaction(TransactionStatus status){
//1、根据账户名称,查询两个账户
Account source = accountDao.findAccountByName(sourceName);
Account target = accountDao.findAccountByName(targetname);
//2、转出账户减钱、转入账户加钱
source.setMoney(source.getMoney() - money);
target.setMoney(target.getMoney() + money);
//3、更新两个账户
accountDao.updateAccount(source);
//模拟转账异常
int i = 1 / 0;
accountDao.updateAccount(target);
return null;
}
});
}
}
/**
* 测试转账
* @author wen
*
*/
public class Client {
public static void main(String[] args) {
ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
AccountServiceimpl accountService = ac.getBean("accountService",AccountServiceimpl.class);
//执行方法
// Account account = accountService.findAccountById(1);
// System.out.println(account);
accountService.transfer("aaa", "bbb", 100f);
}
}
/*
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
Exception in thread "main" java.lang.ArithmeticException: / by zero
at com.itheima.service.impl.AccountServiceimpl$1.doInTransaction(AccountServiceimpl.java:52)
*/

-
spring基于XML的声明式事务控制
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置事务控制模板-->
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager"></property>
</bean>
<!-- 配置service -->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceimpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
<!-- 配置dao 继承jdbcDaoSuppport-->
<bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- spring内置数据源 -->
<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/test"></property>
<property name="username" value="root"></property>
<property name="password" value="123456"></property>
</bean>
<!-- 使用tx名称下的标签,配置事务的通知 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!-- 配置事务的属性
isolation="DEFAULT" no-rollback-for="" propagation="REQUIRED"
read-only="false" rollback-for="" timeout="-1"
-->
<tx:attributes>
<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="pt1"/>
<!-- 建立切入点表达式和事务通知之间的关系 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"/>
</aop:config>
</beans>
/**
* 账户的业务层实现类
*
* @author wen
*/
public class AccountServiceimpl implements IAccountService {
private IAccountDao accountDao;
public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public void transfer(String sourceName, String targetname, Float money) {
//1、根据账户名称,查询两个账户
Account source = accountDao.findAccountByName(sourceName);
Account target = accountDao.findAccountByName(targetname);
//2、转出账户减钱、转入账户加钱
source.setMoney(source.getMoney() - money);
target.setMoney(target.getMoney() + money);
//3、更新两个账户
accountDao.updateAccount(source);
//模拟转账异常
//int i = 1 / 0;
accountDao.updateAccount(target);
}
@Override
public Account findAccountById(Integer accountId) {
// TODO Auto-generated method stub
return accountDao.findAccountById(accountId);
}
}
/**
* 测试转账
* @author wen
*
*/
public class Client {
public static void main(String[] args) {
ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
AccountServiceimpl accountService = ac.getBean("accountService",AccountServiceimpl.class);
//执行方法
// Account account = accountService.findAccountById(1);
// System.out.println(account);
accountService.transfer("aaa", "bbb", 100f);
}
}
-
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: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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 配置spring创建容器时要扫描的包 -->
<context:component-scan base-package="com.itheima"></context:component-scan>
<!-- 配置jdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- spring内置数据源 -->
<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/test"></property>
<property name="username" value="root"></property>
<property name="password" value="123456"></property>
</bean>
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 开启spring对注解事务的支持,并引用事务管理器 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
/**
* 账户的业务层实现类
* @author wen
*
*/
@Service("accountService")
@Transactional
public class AccountServiceimpl implements IAccountService {
@Autowired
private IAccountDao accountDao;
@Override
public void transfer(String sourceName, String targetname, Float money) {
//1、根据账户名称,查询两个账户
Account source=accountDao.findAccountByName(sourceName);
Account target=accountDao.findAccountByName(targetname);
//2、转出账户减钱、转入账户加钱
source.setMoney(source.getMoney()-money);
target.setMoney(target.getMoney()+money);
//3、更新两个账户
accountDao.updateAccount(source);
//模拟转账异常
int i=1/0;
accountDao.updateAccount(target);
}
@Override
public Account findAccountById(Integer accountId) {
// TODO Auto-generated method stub
return accountDao.findAccountById(accountId);
}
}
/**
* 测试转账
* @author wen
*
*/
public class Client {
public static void main(String[] args) {
ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
AccountServiceimpl accountService = ac.getBean("accountService",AccountServiceimpl.class);
//执行方法
// Account account = accountService.findAccountById(1);
// System.out.println(account);
accountService.transfer("aaa", "bbb", 100f);
}
}
-
Transactional注解的使用以及注解配置和XML配置的区别
619

被折叠的 条评论
为什么被折叠?



