小言_互联网的博客

记Spring知识

349人阅读  评论(0)

spring介绍

Spring是分层的Java SE/EE应用 full-stack轻量级开源框架,以IoC(Inverse Of Control:反转控制)和AOP(Aspect Oriented Programming:面向切面编程)为内核,提供了展现层Spring MVC和持久层Spring JDBC以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多著名的第三方框架和类库,逐渐成为使用最多的Java EE企业应用开源框架。

spring的优势

方便解耦,简化开发
通过Spring提供的IoC容器,可以将对象间的依赖关系交由Spring进行控制,避免硬编码所造成的过度程序耦合。用户也不必再为单例模式类、属性文件解析等这些很底层的需求编写代码,可以更专注于上层的应用。
AOP编程的支持
通过Spring的AOP功能,方便进行面向切面的编程,许多不容易用传统OOP实现的功能可以通过AOP轻松应付。
声明式事务的支持
可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活的进行事务的管理,提高开发效率和质量。
方便程序的测试
可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随手可做的事情。
方便集成各种优秀框架
Spring可以降低各种框架的使用难度,提供了对各种优秀框架(Struts、Hibernate、Hessian、Quartz等)的直接支持。
降低JavaEE API的使用难度
Spring对JavaEE API(如JDBC、JavaMail、远程调用等)进行了薄薄的封装层,使这些API的使用难度大为降低。
Java源码是经典学习范例
Spring的源代码设计精妙、结构清晰、匠心独用,处处体现着大师对Java设计模式灵活运用以及对Java技术的高深造诣。它的源代码无意是Java技术的最佳实践的范例。

spring的体系结构

使用spring的IOC解决程序耦合


控制反转-Inversion Of Control
创建业务层接口和实现类:

/**
 * 客户的业务层接口
*/
public interface ICustomerService {
	/**
	 * 保存客户
	 * @param customer
	 */
	void saveCustomer();
}

/**
 * 客户的业务层实现类
 */
public class CustomerServiceImpl implements ICustomerService {
	
	private ICustomerDao customerDao = new CustomerDaoImpl();//此处有依赖关系
	
	@Override
	public void saveCustomer() {
		customerDao.saveCustomer();	
	}
}

创建持久层接口和实现类:

/**
 * 客户的持久层接口
 */
public interface ICustomerDao {
	/**
	 * 保存客户
	 */
	void saveCustomer();
}

/**
 * 客户的持久层实现类
 */
public class CustomerDaoImpl implements ICustomerDao {
	@Override
	public void saveCustomer() {
		System.out.println("保存了客户");
	}
}

基于XML的配置

创建一个bean.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<!-- 导入schema 约束的位置在:
	..\spring-framework-4.2.4.RELEASE\docs\spring-framework-reference\html\xsd-configuration.html文件中。
注意:要导入schema约束
-->
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
      					 http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>

把资源交给spring来管理,在配置文件中配置service和dao

<!-- 把资源交给spring来管理 -->
<bean id="customerDao" class="com.itheima.dao.impl.CustomerDaoImpl"/>
<bean id="customerService" class="com.itheima.service.impl.CustomerServiceImpl"/>

测试配置是否成功

/**
 * 模拟一个表现层
 */
public class Client {
	/**
      * 使用main方法获取容器测试执行
	 */
	public static void main(String[] args) {
		//1.使用ApplicationContext接口,就是在获取spring容器
		ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
		//2.根据bean的id获取对象
		ICustomerService cs = (ICustomerService) ac.getBean("customerService");
		System.out.println(cs);		
		ICustomerDao cd = (ICustomerDao) ac.getBean("customerDao");
		System.out.println(cd);
	}
}

Spring基于XML的IOC细节

spring中工厂的类结构图

BeanFactory和ApplicationContext的区别
BeanFactory才是Spring容器中的顶层接口。
ApplicationContext是它的子接口。
BeanFactory和ApplicationContext的区别:
创建对象的时间点不一样。
ApplicationContext:只要一读取配置文件,默认情况下就会创建对象。
BeanFactory:什么使用什么时候创建对象。

ApplicationContext接口的实现类

ClassPathXmlApplicationContext:
它是从类的根路径下加载配置文件 推荐使用这种
FileSystemXmlApplicationContext:
它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置。

IOC中bean标签和管理对象细节

bean标签

作用:
	用于配置对象让spring来创建的。
	默认情况下它调用的是类中的无参构造函数。如果没有无参构造函数则不能创建成功。
属性:
	id:给对象在容器中提供一个唯一标识。用于获取对象。
	class:指定类的全限定类名。用于反射创建对象。默认情况下调用无参构造函数。
	scope:指定对象的作用范围。
			* singleton	:默认值,单例的.
			* prototype	:多例的.
			* request	:WEB项目中,Spring创建一个Bean的对象,将对象存入到request域中.
			* session	:WEB项目中,Spring创建一个Bean的对象,将对象存入到session域中.
			* globalSession	:WEB项目中,应用在Portlet环境.如果没有Portlet环境那么globalSession相当于session.

	init-method:指定类中的初始化方法名称。
	destroy-method:指定类中销毁方法名称。

bean的作用范围和生命周期

单例对象:scope="singleton"
		一个应用只有一个对象的实例。它的作用范围就是整个引用。
		生命周期:
			对象出生:当应用加载,创建容器时,对象就被创建了。
			对象活着:只要容器在,对象一直活着。
			对象死亡:当应用卸载,销毁容器时,对象就被销毁了。
	多例对象:scope="prototype"
		每次访问对象时,都会重新创建对象实例。
		生命周期:
			对象出生:当使用对象时,创建新的对象实例。
			对象活着:只要对象在使用中,就一直活着。
			对象死亡:当对象长时间不用时,被java的垃圾回收器回收了。

实例化Bean的三种方式

第一种方式:使用默认无参构造函数
	<!--在默认情况下:
		它会根据默认无参构造函数来创建类对象。如果bean中没有默认无参构造函数,将会创建失败。 
	-->
<bean id="customerService" class="com.itheima.service.impl.CustomerServiceImpl"/>
第二种方式:spring管理静态工厂-使用静态工厂的方法创建对象
/**
 * 模拟一个静态工厂,创建业务层实现类
 */
public class StaticFactory {	
	public static ICustomerService createCustomerService(){
		return new CustomerServiceImpl();
	}
}
<!-- 此种方式是:
	 使用StaticFactory类中的静态方法createCustomerService创建对象,并存入spring容器
	 id属性:指定bean的id,用于从容器中获取
	 class属性:指定静态工厂的全限定类名
	 factory-method属性:指定生产对象的静态方法
 -->
<bean id="customerService" 
	  class="com.itheima.factory.StaticFactory" 
	  factory-method="createCustomerService"></bean>
第三种方式:spring管理实例工厂-使用实例工厂的方法创建对象
/**
 * 模拟一个实例工厂,创建业务层实现类
 * 此工厂创建对象,必须现有工厂实例对象,再调用方法
 */
public class InstanceFactory {	
	public ICustomerService createCustomerService(){
		return new CustomerServiceImpl();
	}
}
	<!-- 此种方式是:
		 先把工厂的创建交给spring来管理。
		然后在使用工厂的bean来调用里面的方法
		factory-bean属性:用于指定实例工厂bean的id。
		factory-method属性:用于指定实例工厂中创建对象的方法。
	-->
	<bean id="instancFactory" class="com.itheima.factory.InstanceFactory"></bean>
	<bean id="customerService" 
		  factory-bean="instancFactory" 
		  factory-method="createCustomerService"></bean>

spring的依赖注入

依赖注入的概念

它是spring框架核心ioc的具体实现方式。简单的说,就是坐等框架把对象传入,而不用我们自己去获取。

构造函数注入

就是使用类中的构造函数,给成员变量赋值。注意,赋值的操作不是我们自己做的,而是通过配置的方式,让spring框架来为我们注入。具体代码如下:

public class CustomerServiceImpl implements ICustomerService {
	
	private String name;
	private Integer age;
	private Date birthday;
		
	public CustomerServiceImpl(String name, Integer age, Date birthday) {
		this.name = name;
		this.age = age;
		this.birthday = birthday;
	}

	@Override
	public void saveCustomer() {
		System.out.println(name+","+age+","+birthday);	
	}
}
<!-- 使用构造函数的方式,给service中的属性传值
	要求:
		类中需要提供一个对应参数列表的构造函数。
	涉及的标签:
		constructor-arg
			属性:
				index:指定参数在构造函数参数列表的索引位置
				type:指定参数在构造函数中的数据类型
				name:指定参数在构造函数中的名称					用这个找给谁赋值
				
				=======上面三个都是找给谁赋值,下面两个指的是赋什么值的==============
				
				value:它能赋的值是基本数据类型和String类型
				ref:它能赋的值是其他bean类型,也就是说,必须得是在配置文件中配置过的bean
	 -->
<bean id="customerService" class="com.itheima.service.impl.CustomerServiceImpl">
	<constructor-arg name="name" value="张三"></constructor-arg>
	<constructor-arg name="age" value="18"></constructor-arg>
	<constructor-arg name="birthday" ref="now"></constructor-arg>
</bean>

<bean id="now" class="java.util.Date"></bean>

set方法注入

就是在类中提供需要注入成员的set方法。具体代码如下:

public class CustomerServiceImpl implements ICustomerService {
	
	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 saveCustomer() {
		System.out.println(name+","+age+","+birthday);	
	}
}

<!-- 通过配置文件给bean中的属性传值:使用set方法的方式
	涉及的标签:
		property
		属性:
			name:找的是类中set方法后面的部分
			ref:给属性赋值是其他bean类型的
			value:给属性赋值是基本数据类型和string类型的
	实际开发中,此种方式用的较多。
-->
<bean id="customerService" class="com.itheima.service.impl.CustomerServiceImpl">
		<property name="name" value="test"></property>
		<property name="age" value="21"></property>
		<property name="birthday" ref="now"></property>
</bean>
	
<bean id="now" class="java.util.Date"></bean>

注入集合属性

就是给类中的集合成员传值,它用的也是set方法注入的方式,只不过变量的数据类型都是集合。我们这里介绍注入数组,List,Set,Map,Properties。具体代码如下:

public class CustomerServiceImpl implements ICustomerService {
	
	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 saveCustomer() {
		System.out.println(Arrays.toString(myStrs));
		System.out.println(myList);
		System.out.println(mySet);
		System.out.println(myMap);
		System.out.println(myProps);
	}
}
<!-- 注入集合数据 
	 List结构的:
		array,list,set
	Map结构的
		map,entry,props,prop
-->
<bean id="customerService" class="com.itheima.service.impl.CustomerServiceImpl">
	<!-- 在注入集合数据时,只要结构相同,标签可以互换 -->
	<!-- 给数组注入数据 -->
	<property name="myStrs">
		<set>
			<value>AAA</value>
			<value>BBB</value>
			<value>CCC</value>
		</set>
	</property>
	<!-- 注入list集合数据 -->
	<property name="myList">
		<array>
			<value>AAA</value>
			<value>BBB</value>
			<value>CCC</value>
		</array>
	</property>
	<!-- 注入set集合数据 -->
	<property name="mySet">
		<list>
			<value>AAA</value>
			<value>BBB</value>
			<value>CCC</value>
		</list>
	</property>
	<!-- 注入Map数据 -->
	<property name="myMap">
		<props>
			<prop key="testA">aaa</prop>
			<prop key="testB">bbb</prop>
		</props>
	</property>
	<!-- 注入properties数据 -->
	<property name="myProps">
		<map>
			<entry key="testA" value="aaa"></entry>
			<entry key="testB">
				<value>bbb</value>
			</entry>
		</map>
	</property>
</bean>

基于注解的IOC配置

在类的根路径下创建一个任意名称的xml文件(不能是中文)

创建一个bean.xml文件,给配置文件导入约束:

<?xml version="1.0" encoding="UTF-8"?>
<!-- 导入schema 
约束的位置在:..\spring-framework-4.2.4.RELEASE\docs\spring-framework-reference\html\xsd-configuration.html文件中。
注意:要导入schema约束
-->
<beans xmlns="http://www.springframework.org/schema/beans"
	  xmlns:context="http://www.springframework.org/schema/context"
       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
				http://www.springframework.org/schema/context
      			http://www.springframework.org/schema/context/spring-context.xsd ">
</beans>

使用@Component注解配置管理的资源

/**
 * 客户的业务层实现类
 * @author zhy
 * 
@Component(value="customerService")
public class CustomerServiceImpl implements ICustomerService {
	@Override
	public void saveCustomer() {
		System.out.println("执行了保存客户");
	}
}

在spring的配置文件中开启spring对注解ioc的支持

<!-- 告知spring框架在,读取配置文件,创建容器时,扫描注解,依据注解创建对象,并存入容器中 -->
	<context:component-scan base-package="com.itheima"></context:component-scan>

常用注解:

用于创建对象的:

相当于:<bean id="" class="">

@Component
作用:
	把资源让spring来管理。相当于在xml中配置一个bean。
属性:
	value:指定bean的id。如果不指定value属性,默认bean的id是当前类的类名。首字母小写。
@Controller @Service @Repository
他们三个注解都是针对一个的衍生注解,他们的作用及属性都是一模一样的。
他们只不过是提供了更加明确的语义化。
	@Controller:一般用于表现层的注解。
	@Service:一般用于业务层的注解。
	@Repository:一般用于持久层的注解。
细节:如果注解中有且只有一个属性要赋值时,且名称是value,value在赋值是可以不写。
用于注入数据的:

相当于:<property name="" ref=""> <property name="" value="">

@Autowired
作用:
	自动按照类型注入。当使用注解注入属性时,set方法可以省略。它只能注入其他bean类型。
	当有多个类型匹配时,使用要注入的对象变量名称作为bean的id,在spring容器查找,找到了也可以注入成功。
	找不到就报错。
@Qualifier
作用:
	在自动按照类型注入的基础之上,再按照Bean的id注入。
	它在给字段注入时不能独立使用,必须和@Autowire一起使用;但是给方法参数注入时,可以独立使用。
属性:
	value:指定bean的id。
@Resource
作用:
	直接按照Bean的id注入。它也只能注入其他bean类型。
属性:
	name:指定bean的id。
@Value
作用:
	注入基本数据类型和String类型数据的
属性:
	value:用于指定值
用于改变作用范围的:

相当于:<bean id="" class="" scope="">

@Scope
作用:
	指定bean的作用范围。
属性:
	value:指定范围的值。
		   取值:singleton  prototype request session globalsession
和生命周期相关的:(了解)

相当于:<bean id="" class="" init-method="" destroy-method="" />

@PostConstruct
作用:
	用于指定初始化方法。
@PreDestroy
作用:
	用于指定销毁方法。
代码示例
业务层代码:
/**
 * 客户的业务层接口
 */
public interface ICustomerService {	
	/**
	 * 保存客户
	 * @param customer
	 */
	void saveCustomer();	
}


/**
 * 客户的业务层实现类
 */
//作用就相当于在xml中配置了一个bean标签,该注解有value属性,含义是bean的id。
//不写的时候,默认的id是:当前类名,且首字母小写。即:customerServiceImpl
@Component(value="customerService")
@Scope(value="singleton")
public class CustomerServiceImpl implements ICustomerService {
//	@Autowired
//	自动按照数据类型注入,拿着当前变量的数据类型在spring的容器中找,找到后,给变量赋值。
//	当有多个类型匹配时,会使用当前变量名称customerDao作为bean的id,继续在容器中找。
//	找到了,也能注入成功。找不到就报错。
//	@Qualifier(value="customerDao2")//在自动按照类型注入的基础之上,再按照id注入
	@Resource(name="customerDao2")//直接按照bean的id注入
	private ICustomerDao customerDao = null;
	
	@Value("com.mysql.jdbc.Driver")//注入基本类型和String类型数据
	private String driver;

	@Override
	public void saveCustomer() {
		System.out.println(driver);
		customerDao.saveCustomer();	
	}
}


持久层代码:
/**
 * 客户的持久层接口
 */
public interface ICustomerDao {
	/**
	 * 保存客户
	 */
	void saveCustomer();
}



/**
 * 客户的持久层实现类11111111111111111111
 */
@Repository("customerDao1")
public class CustomerDaoImpl implements ICustomerDao {

	@Override
	public void saveCustomer() {
		System.out.println("保存了客户111111111111111111");
	}
}

/**
 * 客户的持久层实现类222222222222222222222222
 */
@Repository("customerDao2")
public class CustomerDaoImpl2 implements ICustomerDao {

	@Override
	public void saveCustomer() {
		System.out.println("保存了客户2222222222222222222");
	}
}

测试类代码:
public class Client {
	public static void main(String[] args) {
		//1.获取容器
		ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
		//2.根据id获取对象
		ICustomerService cs = (ICustomerService) ac.getBean("customerService");				cs.saveCustomer();
	}
}

配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<!-- 我们导入约束时,除了昨天的那部分之外,还要单独导入一个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>
关于Spring注解和XML的选择问题
注解的优势:
	配置简单,维护方便(我们找到类,就相当于找到了对应的配置)。
XML的优势:
	修改时,不用改源码。不涉及重新编译和部署。

Spring管理Bean方式的比较:

spring的纯注解配置

我们发现,之所以我们现在离不开xml配置文件,是因为我们有一句很关键的配置:

<!-- 告知spring框架在读取配置文件,创建容器时,扫描注解,依据注解创建对象,并存入容器中 -->
	<context:component-scan base-package="com.itheima"></context:component-scan>

如果他要也能用注解配置,那么我们就可以脱离xml文件了。

使用注解配置要扫描的包

工程结构,如下图:

在此图中,我们已经一点也看不到xml的身影了。那么,那句关键的配置跑哪去了呢?
在一个新的类上:

/**
 * 客户的业务层实现类
 */
@Configuration//表明当前类是一个配置类
@ComponentScan(basePackages = "com.itheima")//配置要扫描的包
public class SpringConfiguration {
}

那么新的问题又来了,我们如何获取容器呢?
public class Client {
	public static void main(String[] args) {
		//1.获取容器:由于我们已经没有了xml文件,所以再用读取xml方式就不能用了。
		//这时需要指定加载哪个类上的注解
		ApplicationContext ac = 
			new AnnotationConfigApplicationContext(SpringConfiguration.class);
		//2.根据id获取对象
		ICustomerService cs = (ICustomerService) ac.getBean("customerService");
		cs.saveCustomer();
	}
}

新注解说明:

@Configuration
作用:
	用于指定当前类是一个spring配置类,当创建容器时会从该类上加载注解。获取容器时需要使用AnnotationApplicationContext(@Configuration注解的类.class)。
属性:
	value:用于指定配置类的字节码

示例代码:

/**
 * 用于初始化spring容器的配置类
 */
@Configuration
public class SpringConfiguration{
}
@ComponentScan
作用:
	用于指定spring在初始化容器时要扫描的包。作用和在spring的xml配置文件中的:
<context:component-scan base-package="com.itheima"/>是一样的。
属性:
	basePackages:用于指定要扫描的包。和该注解中的value属性作用一样。
@PropertySource
作用:
	用于加载.properties文件中的配置。例如我们配置数据源时,可以把连接数据库的信息写到properties配置文件中,就可以使用此注解指定properties配置文件的位置。
属性:
	value[]:用于指定properties文件位置。如果是在类路径下,需要写上classpath:

示例代码:

配置:
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(name="dataSource")
	public DataSource getDataSource(){
		BasicDataSource ds = new BasicDataSource();
		ds.setDriverClassName(driver);
		ds.setUrl(url);
		ds.setUsername(username);
		ds.setPassword(password);
		return ds;
	}	
}
jdbc.properties文件:
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/day44_ee247_spring
jdbc.username=root
jdbc.password=1234

注意:
	我们目前上课使用的版本是4.2.4,在spring4.3以前都需要提供一个占位符配置器:
		PropertySourcesPlaceholderConfigurer
	而在spring4.3以后,则不需要提供。
	提供的方式如下:(在SpringConfiguration或JdbcConfig中配置均可)
	@Bean
    	public static PropertySourcesPlaceholderConfigurer 
								propertySourcesPlaceholderConfigurer() {
		return new PropertySourcesPlaceholderConfigurer();
    }
@Import
作用:
	用于导入其他配置类,在引入其他配置类时,可以不用再写@Configuration注解。当然,写上也没问题。
属性:
	value[]:用于指定其他配置类的字节码。

示例代码:

@Configuration
@ComponentScan(basePackages = "cn.itcast.spring")
@Import({ Configuration_B.class})
public class Configuration_A {
}

@Configuration
@PropertySource("classpath:info.properties")
public class Configuration_B {

}
@Bean
作用:
	该注解只能写在方法上,表明使用此方法创建一个对象,并且放入spring容器。它就相当于我们之前在xml配置中介绍的factory-bean和factory-method。
属性:
	name:给当前@Bean注解方法创建的对象指定一个名称(即bean的id)。

示例代码:

@Bean(name = "datasource2")
	public DataSource createDS() throws Exception {
		ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
		comboPooledDataSource.setUser("root");
		comboPooledDataSource.setPassword("1234");
		comboPooledDataSource.setDriverClass("com.mysql.jdbc.Driver");
		comboPooledDataSource.setJdbcUrl("jdbc:mysql:///spring_ioc");
		return comboPooledDataSource;
	}

Spring整合Junit

创建业务层接口实现类

/**
 * 客户的业务层接口
 */
public interface ICustomerService {

	/**
	 * 查询所有客户
	 * @return
	 */
	List<Customer> findAllCustomer();
	
	/**
	 * 保存客户
	 * @param customer
	 */
	void saveCustomer(Customer customer);
}

/**
 * 客户的业务层实现类
 */
public class CustomerServiceImpl implements ICustomerService {

	private ICustomerDao customerDao;
	
	public void setCustomerDao(ICustomerDao customerDao) {
		this.customerDao = customerDao;
	}

	@Override
	public List<Customer> findAllCustomer() {
		return customerDao.findAllCustomer();
	}

	@Override
	public void saveCustomer(Customer customer) {
		customerDao.save(customer);
	}

}

创建持久层接口实现类

/**
 * 客户的持久层接口
 */
public interface ICustomerDao {
	
	/**
	 * 查询所有客户
	 * @return
	 */
	List<Customer> findAllCustomer();

	/**
	 * 保存客户
	 * @param customer
	 */
	void save(Customer customer);
}

/**
 * 客户的持久层实现类
 */
public class CustomerDaoImpl implements ICustomerDao {

	@Override
	public List<Customer> findAllCustomer() {
		System.out.println("查询了所有客户");
		return null;
	}

	@Override
	public void save(Customer customer) {
		System.out.println("保存了客户");
	}
}

导入junit的jar包

编写测试类

/**
 * 测试客户的业务层和持久层
 */
public class CustomerServiceTest {

	private ICustomerService customerService;
	
	@Test
	public void testFindAll(){
		customerService.findAllCustomer();
	}
	
	@Test
	public void testSave(){
		Customer c = new Customer();
		c.setCustName("传智学院 ");	
		customerService.saveCustomer(c);
	}
}

使用xml配置步骤

一、 xml文件中的配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
       		   http://www.springframework.org/schema/beans/spring-beans.xsd">
	<!-- 把资源交给spring来管理 -->
	<bean id="customerDao" class="com.itheima.dao.impl.CustomerDaoImpl"></bean>
	
	<bean id="customerService" class="com.itheima.service.impl.CustomerServiceImpl">
		<property name="customerDao" ref="customerDao"></property>
	</bean>
</beans>
二、 拷贝整合junit的必备jar包到lib目录

此处需要注意的是,导入jar包时,需要导入一个spring中aop的jar包。

三、 使用@RunWith注解替换原有运行器
@RunWith(SpringJUnit4ClassRunner.class)
public class CustomerServiceTest {
	
	private ICustomerService customerService;
	
	@Test
	public void testFindAll(){
		customerService.findAllCustomer();
	}
	
	@Test
	public void testSave(){
		Customer c = new Customer();
		c.setCustName("井冈山大学 ");	
		customerService.saveCustomer(c);
	}
}
四、 用@ContextConfiguration指定spring配置文件的位置
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"classpath:bean.xml"})
public class CustomerServiceTest {

	private ICustomerService customerService;
	
	@Test
	public void testFindAll(){
		customerService.findAllCustomer();
	}
	
	@Test
	public void testSave(){
		Customer c = new Customer();
		c.setCustName("井冈山大学 ");	
		customerService.saveCustomer(c);
	}
}
五、 使用@Autowired给测试类中的变量注入数据
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"classpath:bean.xml"})
public class CustomerServiceTest {

	@Autowired
	private ICustomerService customerService;
	
	@Test
	public void testFindAll(){
		customerService.findAllCustomer();
	}
	
	@Test
	public void testSave(){
		Customer c = new Customer();
		c.setCustName("井冈山大学 ");	
		customerService.saveCustomer(c);
	}
}

使用纯注解配置步骤

一:拷贝整合junit的必备jar包到lib目录

此处需要注意的是,导入jar包时,需要导入一个spring中aop的jar包。

二:把资源都用注解管理
@Service("customerService")
public class CustomerServiceImpl implements ICustomerService {

	@Autowired
	private ICustomerDao customerDao;

	@Override
	public List<Customer> findAllCustomer() {
		return customerDao.findAllCustomer();
	}

	@Override
	public void saveCustomer(Customer customer) {
		customerDao.save(customer);
	}
}

/**
 * 客户的持久层实现类
 */
@Repository("customerDao")
public class CustomerDaoImpl implements ICustomerDao {

	@Override
	public List<Customer> findAllCustomer() {
		System.out.println("查询了所有客户");
		return null;
	}

	@Override
	public void save(Customer customer) {
		System.out.println("保存了客户");
	}
}
三:使用注解配置方式创建spring容器
@Configuration
@ComponentScan(basePackages={"com.itheima"})
public class CustomerServiceTest {

	@Autowired
	private ICustomerService customerService;
	
	@Test
	public void testFindAll(){
		customerService.findAllCustomer();
	}
	
	@Test
	public void testSave(){
		Customer c = new Customer();
		c.setCustName("井冈山大学 ");	
		customerService.saveCustomer(c);
	}
}
四:使用RunWith注解和ContextConfiguration注解配置
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={CustomerServiceTest.class})
@Configuration
@ComponentScan(basePackages={"com.itheima"})
public class CustomerServiceTest {

	@Autowired
	private ICustomerService customerService;
	
	@Test
	public void testFindAll(){
		customerService.findAllCustomer();
	}
	
	@Test
	public void testSave(){
		Customer c = new Customer();
		c.setCustName("井冈山大学");	
		customerService.saveCustomer(c);
	}
}

AOP的相关概念

什么是AOP

AOP:全称是Aspect Oriented Programming即:面向切面编程。

简单的说它就是把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的基础上,对我们的已有方法进行增强

AOP的作用及优势

作用:
	在程序运行期间,不修改源码对已有方法进行增强。
优势:
		减少重复代码		 提高开发效率 		维护方便

AOP的实现方式

使用动态代理技术

动态代理

动态代理的特点
字节码随用随创建,随用随加载。
	它与静态代理的区别也在于此。因为静态代理是字节码一上来就创建好,并完成加载。
	装饰者模式就是静态代理的一种体现。
动态代理常用的有两种方式
基于接口的动态代理
		提供者:JDK官方的Proxy类。
		要求:被代理类最少实现一个接口。
	基于子类的动态代理
		提供者:第三方的CGLib,如果报asmxxxx异常,需要导入asm.jar。
		要求:被代理类不能用final修饰的类(最终类)。
使用JDK官方的Proxy类创建代理对象

此处我们使用的是一个演员的例子:

/**
 * 一个经纪公司的要求:
 * 		能做基本的表演和危险的表演
*/
public interface IActor {
	/**
	 * 基本演出
	 * @param money
	 */
	public void basicAct(float money);
	/**
	 * 危险演出
	 * @param money
	 */
	public void dangerAct(float money);
}

/**
 * 一个演员
 */
//实现了接口,就表示具有接口中的方法实现。即:符合经纪公司的要求
public class Actor implements IActor{
	
	public void basicAct(float money){
		System.out.println("拿到钱,开始基本的表演:"+money);
	}
	
	public void dangerAct(float money){
		System.out.println("拿到钱,开始危险的表演:"+money);
	}
}

public class Client {
	
	public static void main(String[] args) {
		//一个剧组找演员:
		final Actor actor = new Actor();//直接
		
		/**
		 * 代理:
		 * 	间接。
		 * 获取代理对象:
		 * 	要求:
		 * 	 被代理类最少实现一个接口
		 * 创建的方式
		 *   Proxy.newProxyInstance(三个参数)
		 * 参数含义:
		 * 	ClassLoader:和被代理对象使用相同的类加载器。
		 *  Interfaces:和被代理对象具有相同的行为。实现相同的接口。
		 *  InvocationHandler:如何代理。
		 *  		策略模式:使用场景是:
		 *  					数据有了,目的明确。
		 *  					如何达成目标,就是策略。
		 *  			
		 */
		IActor proxyActor = (IActor) Proxy.newProxyInstance(
										actor.getClass().getClassLoader(), 
										actor.getClass().getInterfaces(), 
										new InvocationHandler() {
				/**
				 * 执行被代理对象的任何方法,都会经过该方法。
				 * 此方法有拦截的功能。
				 * 
				 * 参数:
				 * 	proxy:代理对象的引用。不一定每次都用得到
				 * 	method:当前执行的方法对象
				 * 	args:执行方法所需的参数
				 * 返回值:
				 * 	当前执行方法的返回值
				 */
				@Override
				public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
					String name = method.getName();
					Float money = (Float) args[0];
					Object rtValue = null;
					//每个经纪公司对不同演出收费不一样,此处开始判断
					if("basicAct".equals(name)){
						//基本演出,没有2000不演
						if(money > 2000){
							//看上去剧组是给了8000,实际到演员手里只有4000
							//这就是我们没有修改原来basicAct方法源码,对方法进行了增强
							rtValue = method.invoke(actor, money/2);
						}
					}
					if("dangerAct".equals(name)){
						//危险演出,没有5000不演
						if(money > 5000){
							//看上去剧组是给了50000,实际到演员手里只有25000
							//这就是我们没有修改原来dangerAct方法源码,对方法进行了增强
							rtValue = method.invoke(actor, money/2);
						}
					}
					return rtValue;
				}
		});
		//没有经纪公司的时候,直接找演员。
//		actor.basicAct(1000f);
//		actor.dangerAct(5000f);
		
		//剧组无法直接联系演员,而是由经纪公司找的演员
		proxyActor.basicAct(8000f);
		proxyActor.dangerAct(50000f);
	}
}
使用CGLib的Enhancer类创建代理对象

还是那个演员的例子,只不过不让他实现接口。

/**
 * 一个演员
*/
public class Actor{//没有实现任何接口
	
	public void basicAct(float money){
		System.out.println("拿到钱,开始基本的表演:"+money);
	}
	
	public void dangerAct(float money){
		System.out.println("拿到钱,开始危险的表演:"+money);
	}
}

public class Client {
	/**
	 * 基于子类的动态代理
	 * 	要求:
	 * 		被代理对象不能是最终类
	 * 	用到的类:
	 * 		Enhancer
	 * 	用到的方法:
	 * 		create(Class, Callback)
	 * 	方法的参数:
	 * 		Class:被代理对象的字节码
	 * 		Callback:如何代理
	 * @param args
	 */
	public static void  main(String[] args) {
		final Actor actor = new Actor();
		
		Actor cglibActor = (Actor) Enhancer.create(actor.getClass(),
							new MethodInterceptor() {
			/**
			 * 执行被代理对象的任何方法,都会经过该方法。在此方法内部就可以对被代理对象的任何方法进行增强。
			 * 
			 * 参数:
			 * 	前三个和基于接口的动态代理是一样的。
			 * 	MethodProxy:当前执行方法的代理对象。
			 * 返回值:
			 * 	当前执行方法的返回值
			 */
			@Override
			public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
				String name = method.getName();
				Float money = (Float) args[0];
				Object rtValue = null;
				if("basicAct".equals(name)){
					//基本演出
					if(money > 2000){
						rtValue = method.invoke(actor, money/2);
					}
				}
				if("dangerAct".equals(name)){
					//危险演出
					if(money > 5000){
						rtValue = method.invoke(actor, money/2);
					}
				}
				return rtValue;
			}
		});		
		cglibActor.basicAct(10000);
		cglibActor.dangerAct(100000);
	}
}

Spring中的AOP

在spring中,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式。

AOP相关术语

Joinpoint(连接点):
		所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点。
Pointcut(切入点):
		所谓切入点是指我们要对哪些Joinpoint进行拦截的定义。
Advice(通知/增强):
		所谓通知是指拦截到Joinpoint之后所要做的事情就是通知。
		通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。
Introduction(引介):
		引介是一种特殊的通知在不修改类代码的前提下, Introduction可以在运行期为类动态地添加一些方法或Field。
Target(目标对象):
		代理的目标对象。
Weaving(织入):
		是指把增强应用到目标对象来创建新的代理对象的过程。
		spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。
Proxy(代理):
		一个类被AOP织入增强后,就产生一个结果代理类。
Aspect(切面):
		是切入点和通知(引介)的结合。

学习spring中的AOP要明确的事

a、开发阶段(我们做的)
	编写核心业务代码(开发主线):大部分程序员来做,要求熟悉业务需求。
	把公用代码抽取出来,制作成通知。(开发阶段最后再做):AOP编程人员来做。
	在配置文件中,声明切入点与通知间的关系,即切面。:AOP编程人员来做。
b、运行阶段(Spring框架完成的)
	Spring框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。

基于XML的AOP配置

环境搭建

第一步:准备客户的业务层和接口(需要增强的类)

/**
 * 客户的业务层接口
*/
public interface ICustomerService {
	
	/**
	 * 保存客户
	 */
	void saveCustomer();
	
	/**
	 * 修改客户
	 * @param i
	 */
	void updateCustomer(int i);
}

/**
 * 客户的业务层实现类
 */
public class CustomerServiceImpl implements ICustomerService {

	@Override
	public void saveCustomer() {
		System.out.println("调用持久层,执行保存客户");
	}

	@Override
	public void updateCustomer(int i) {
		System.out.println("调用持久层,执行修改客户");
	}
}

第二步:拷贝必备的jar包到工程的lib目录

第三步:创建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">

</beans>

第四步:把客户的业务层配置到spring容器中

<!-- 把资源交给spring来管理 -->
<bean id="customerService" class="com.itheima.service.impl.CustomerServiceImpl"/>

第五步:制作通知(增强的类)

/**
 * 一个记录日志的工具类
*/
public class Logger {
	/**
	 * 期望:此方法在业务核心方法执行之前,就记录日志
	 */
	public void beforePrintLog(){
		System.out.println("Logger类中的printLog方法开始记录日志了。。。。");
	}
}

配置步骤

第一步:把通知类用bean标签配置起来

<!-- 把有公共代码的类也让spring来管理(把通知类也交给spring来管理) -->
<bean id="logger" class="com.itheima.util.Logger"></bean>

第二步:使用aop:config声明aop配置

<!-- aop的配置 -->
<aop:config>
	<!-- 配置的代码都写在此处 -->	
</aop:config>

第三步:使用aop:aspect配置切面

<!-- 配置切面 :此标签要出现在aop:config内部
	id:给切面提供一个唯一标识
	ref:引用的是通知类的bean的id
-->
<aop:aspect id="logAdvice" ref="logger">
		<!--配置通知的类型要写在此处-->
</aop:aspect>

第四步:使用aop:before配置前置通知

<!-- 用于配置前置通知:指定增强的方法在切入点方法之前执行 
		method:用于指定通知类中的增强方法名称
		ponitcut-ref:用于指定切入点的表达式的引用	
-->
<aop:before method="beforePrintLog" pointcut-ref="pt1"/>

第五步:使用aop:pointcut配置切入点表达式

<aop:pointcut expression="execution(public void com.itheima.service.impl.CustomerServiceImpl.saveCustomer())" 
id="pt1"/>

切入点表达式说明

execution:
		匹配方法的执行(常用)		
		execution(表达式)
表达式语法:execution([修饰符] 返回值类型 包名.类名.方法名(参数))
写法说明:
	全匹配方式:
		public void com.itheima.service.impl.CustomerServiceImpl.saveCustomer()
	访问修饰符可以省略	
		void com.itheima.service.impl.CustomerServiceImpl.saveCustomer()
	返回值可以使用*号,表示任意返回值
		* com.itheima.service.impl.CustomerServiceImpl.saveCustomer()
	包名可以使用*号,表示任意包,但是有几级包,需要写几个*
		* *.*.*.*.CustomerServiceImpl.saveCustomer()
	使用..来表示当前包,及其子包
		* com..CustomerServiceImpl.saveCustomer()
	类名可以使用*号,表示任意类
		* com..*.saveCustomer()
	方法名可以使用*号,表示任意方法
		* com..*.*()
	参数列表可以使用*,表示参数可以是任意数据类型,但是必须有参数
		* com..*.*(*)
	参数列表可以使用..表示有无参数均可,有参数可以是任意类型
		* com..*.*(..)
	全通配方式:
		* *..*.*(..)

常用标签

<aop:config>

作用:
	用于声明开始aop的配置

<aop:pointcut>

作用:
	用于配置切入点表达式
属性:
	expression:用于定义切入点表达式。
	id:用于给切入点表达式提供一个唯一标识。

<aop:before>

作用:
	用于配置前置通知
属性:
	method:指定通知中方法的名称。
	pointct:定义切入点表达式
	pointcut-ref:指定切入点表达式的引用

<aop:after-returning>

作用:
	用于配置后置通知
属性:
	method:指定通知中方法的名称。
	pointct:定义切入点表达式
	pointcut-ref:指定切入点表达式的引用

<aop:after-throwing>

作用:
	用于配置异常通知
属性:
	method:指定通知中方法的名称。
	pointct:定义切入点表达式
	pointcut-ref:指定切入点表达式的引用

<aop:after>

作用:
	用于配置最终通知
属性:
	method:指定通知中方法的名称。
	pointct:定义切入点表达式
	pointcut-ref:指定切入点表达式的引用

<aop:around>

作用:
	用于配置环绕通知
属性:
	method:指定通知中方法的名称。
	pointct:定义切入点表达式
	pointcut-ref:指定切入点表达式的引用

通知的类型

类型说明

<!-- 配置通知的类型
	aop:before:
		用于配置前置通知。前置通知的执行时间点:切入点方法执行之前执行
	aop:after-returning:
		用于配置后置通知。后置通知的执行时间点:切入点方法正常执行之后。它和异常通知只能有一个执行
	aop:after-throwing
		用于配置异常通知。异常通知的执行时间点:切入点方法执行产生异常后执行。它和后置通知只能执行一个。
	aop:after
		用于配置最终通知。最终通知的执行时间点:无论切入点方法执行时是否有异常,它都会在其后面执行。
	aop:around
		用于配置环绕通知。他和前面四个不一样,他不是用于指定通知方法何时执行的。
-->			
<aop:before method="beforePrintLog" pointcut-ref="pt1"/>
<aop:after-returning method="afterReturningPrintLog"  pointcut-ref="pt1"/>
<aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"/>
<aop:after method="afterPrintLog" pointcut-ref="pt1"/>
<aop:around method="aroundPringLog" pointcut-ref="pt1"/>

环绕通知的特殊说明

/**
	 * 环绕通知
	 * 	它是spring框架为我们提供的一种可以在代码中手动控制增强部分什么时候执行的方式。
	 * 问题:
	 * 	当我们配置了环绕通知之后,增强的代码执行了,业务核心方法没有执行。
	 * 分析:
	 * 	通过动态代理我们知道在invoke方法中,有明确调用业务核心方法:method.invoke()。
	 * 	我们配置的环绕通知中,没有明确调用业务核心方法。
	 * 解决:
	 * 	spring框架为我们提供了一个接口:ProceedingJoinPoint,它可以作为环绕通知的方法参数
	 * 	在环绕通知执行时,spring框架会为我们提供该接口的实现类对象,我们直接使用就行。
	 * 	该接口中有一个方法proceed(),此方法就相当于method.invoke()
	 */
	public void aroundPringLog(ProceedingJoinPoint pjp){
		try {
			System.out.println("前置通知:Logger类的aroundPringLog方法记录日志");
			pjp.proceed();
			System.out.println("后置通知:Logger类的aroundPringLog方法记录日志");
		} catch (Throwable e) {
			System.out.println("异常通知:Logger类的aroundPringLog方法记录日志");
			e.printStackTrace();
		}finally{
			System.out.println("最终通知:Logger类的aroundPringLog方法记录日志");
		}
	}

基于注解的AOP配置

环境搭建

第一步:准备客户的业务层和接口并用注解配置(需要增强的类)

/**
 * 客户的业务层接口
*/
public interface ICustomerService {
	
	/**
	 * 保存客户
	 */
	void saveCustomer();
	
	/**
	 * 修改客户
	 * @param i
	 */
	void updateCustomer(int i);
}

/**
 * 客户的业务层实现类
 */
public class CustomerServiceImpl implements ICustomerService {

	@Override
	public void saveCustomer() {
		System.out.println("调用持久层,执行保存客户");
	}

	@Override
	public void updateCustomer(int i) {
		System.out.println("调用持久层,执行修改客户");
	}
}

第二步:拷贝必备的jar包到工程的lib目录

第三步:创建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: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">
  
</beans>

第四步:把资源使用注解让spring来管理

/**
 * 客户的业务层实现类
 */
@Service("customerService")
public class CustomerServiceImpl implements ICustomerService {

	@Override
	public void saveCustomer() {
		System.out.println("调用持久层,执行保存客户");
	}
	@Override
	public void updateCustomer(int i) {
		System.out.println("调用持久层,执行修改客户");
	}
}

第五步:在配置文件中指定spring要扫描的包

<!-- 告知spring,在创建容器时要扫描的包 -->
<context:component-scan base-package="com.itheima"></context:component-scan> 

配置步骤

第一步:把通知类也使用注解配置

/**
 * 一个记录日志的工具类
 */
@Component("logger")
public class Logger {
	/**
	 * 期望:此方法在业务核心方法执行之前,就记录日志
	 * 前置通知
	 */
	public void beforePrintLog(){
		System.out.println("前置通知:Logger类中的printLog方法开始记录日志了");
	}
}

第二步:在通知类上使用@Aspect注解声明为切面

/**
 * 一个记录日志的工具类
 */
@Component("logger")
@Aspect//表明当前类是一个切面类
public class Logger {
	/**
	 * 期望:此方法在业务核心方法执行之前,就记录日志
	 * 前置通知
	 */
	public void beforePrintLog(){
		System.out.println("前置通知:Logger类中的printLog方法开始记录日志了");
	}
}

第三步:在增强的方法上使用@Before注解配置前置通知

/**
	 * 期望:此方法在业务核心方法执行之前,就记录日志
	 * 前置通知
	 */
	@Before("execution(* com.itheima.service.impl.*.*(..))")//表示前置通知
	public void beforePrintLog(){
		System.out.println("前置通知:Logger类中的printLog方法开始记录日志了");
	}

第四步:在spring配置文件中开启spring对注解AOP的支持

<!-- 开启spring对注解AOP的支持 -->
<aop:aspectj-autoproxy/>

常用注解

@Aspect:

作用:
	把当前类声明为切面类。

@Before:

作用:
	把当前方法看成是前置通知。
属性:
	value:用于指定切入点表达式,还可以指定切入点表达式的引用。

@AfterReturning

作用:
	把当前方法看成是后置通知。
属性:
	value:用于指定切入点表达式,还可以指定切入点表达式的引用。

@AfterThrowing

作用:
	把当前方法看成是异常通知。
属性:
	value:用于指定切入点表达式,还可以指定切入点表达式的引用。

@After

作用:
	把当前方法看成是最终通知。
属性:
	value:用于指定切入点表达式,还可以指定切入点表达式的引用。

@Around

作用:
	把当前方法看成是环绕通知。
属性:
	value:用于指定切入点表达式,还可以指定切入点表达式的引用。

@Pointcut

作用:
	指定切入点表达式
属性:
	value:指定表达式的内容

不使用XML的配置方式

@Configuration
@ComponentScan(basePackages="com.itheima")
@EnableAspectJAutoProxy
public class SpringConfiguration {
}

Spring中的JdbcTemplate

JdbcTemplate概述

它是spring框架中提供的一个对象,是对原始Jdbc API对象的简单封装。spring框架为我们提供了很多的操作模板类,入下图所示:

我们今天的主角在spring-jdbc-4.24.RELEASE.jar中,我们在导包的时候,除了要导入这个jar包外,还需要导入一个spring-tx-4.2.4.RELEASE.jar(它是和事务相关的)。

JdbcTemplate对象的创建

我们可以参考它的源码,来一探究竟:

public JdbcTemplate() {
	}

	public JdbcTemplate(DataSource dataSource) {
		setDataSource(dataSource);
		afterPropertiesSet();
	}

	public JdbcTemplate(DataSource dataSource, boolean lazyInit) {
		setDataSource(dataSource);
		setLazyInit(lazyInit);
		afterPropertiesSet();
	}

除了默认构造函数之外,都需要提供一个数据源。既然有set方法,依据我们之前学过的依赖注入,我们可以在配置文件中配置。

spring中配置数据源

环境搭建

编写spring的配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    	xsi:schemaLocation="http://www.springframework.org/schema/beans 
    	http://www.springframework.org/schema/beans/spring-beans.xsd">
	
</beans>

配置数据源

我们之前已经接触过了两个数据源,一个是C3P0,一个是DBCP。要想使用这两数据源都需要导入对应的jar包。

配置C3P0数据源

导入c3p0的jar包到工程的lib目录。在spring的配置文件中配置:

<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
	<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
	<property name="jdbcUrl" value="jdbc:mysql:///spring_day04"></property>
	<property name="user" value="root"></property>
	<property name="password" value="1234"></property>
</bean>

配置DBCP数据源

导入到工程的lib目录。在spring的配置文件中配置:

<!-- 配置数据源 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
	<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
	<property name="url" value="jdbc:mysql:// /spring_day04"></property>
	<property name="username" value="root"></property>
	<property name="password" value="1234"></property>
</bean>

配置spring内置数据源

spring框架也提供了一个内置数据源,我们也可以使用spring的内置数据源,它就在spring-jdbc-4.2.4.REEASE.jar包中:

<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
	<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
	<property name="url" value="jdbc:mysql:///spring_day04"></property>
	<property name="username" value="root"></property>
	<property name="password" value="1234"></property>
</bean>

将数据库连接的信息配置到属性文件中:

定义db.properties属性文件:

jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql:///spring_day02
jdbc.username=root
jdbc.password=123

引入外部的属性文件:

一种方式:
    <!-- 引入外部属性文件: -->
    <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    	<property name="location" value="classpath:jdbc.properties"/>
    </bean>

二种方式:
<context:property-placeholder location="classpath:jdbc.properties"/>

JdbcTemplate的增删改查操作

在spring配置文件中配置JdbcTemplate

<?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">

	<!-- 配置一个数据库的操作模板:JdbcTemplate -->
	<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
		<property name="dataSource" ref="dataSource"></property>
	</bean>
	
	<!-- 配置数据源 -->
	<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
	<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
	<property name="url" value="jdbc:mysql:///spring_day04"></property>
	<property name="username" value="root"></property>
	<property name="password" value="1234"></property>
</bean>
</beans>

最基本使用

public class JdbcTemplateDemo2 {
	public static void main(String[] args) {
		//1.获取Spring容器
		ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
		//2.根据id获取bean对象
		JdbcTemplate jt = (JdbcTemplate) ac.getBean("jdbcTemplate");
		//3.执行操作
		jt.execute("insert into account(name,money)values('eee',500)");
	}
}

保存操作

public class JdbcTemplateDemo3 {
	public static void main(String[] args) {

		//1.获取Spring容器
		ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
		//2.根据id获取bean对象
		JdbcTemplate jt = (JdbcTemplate) ac.getBean("jdbcTemplate");
		//3.执行操作
		//保存
		jt.update("insert into account(name,money)values(?,?)","fff",5000);
	}
}

更新操作

public class JdbcTemplateDemo3 {
	public static void main(String[] args) {

		//1.获取Spring容器
		ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
		//2.根据id获取bean对象
		JdbcTemplate jt = (JdbcTemplate) ac.getBean("jdbcTemplate");
		//3.执行操作
		//修改
		jt.update("update account set money = money-? where id = ?",300,6);
	}
}

删除操作

public class JdbcTemplateDemo3 {
	public static void main(String[] args) {

		//1.获取Spring容器
		ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
		//2.根据id获取bean对象
		JdbcTemplate jt = (JdbcTemplate) ac.getBean("jdbcTemplate");
		//3.执行操作
		//删除
		jt.update("delete from account where id = ?",6);
	}
}

查询所有操作

public class JdbcTemplateDemo3 {
	public static void main(String[] args) {

		//1.获取Spring容器
		ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
		//2.根据id获取bean对象
		JdbcTemplate jt = (JdbcTemplate) ac.getBean("jdbcTemplate");
		//3.执行操作
		//查询所有
		List<Account> accounts = jt.query("select * from account where money > ? ", 
											new AccountRowMapper(), 500);
		for(Account o : accounts){
			System.out.println(o);
		}
	}
}

public 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;
	}
	
}

查询返回一行一列操作

public class JdbcTemplateDemo3 {
	public static void main(String[] args) {

		//1.获取Spring容器
		ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
		//2.根据id获取bean对象
		JdbcTemplate jt = (JdbcTemplate) ac.getBean("jdbcTemplate");
		//3.执行操作
		//查询返回一行一列:使用聚合函数,在不使用group by字句时,都是返回一行一列。最长用的就是分页中获取总记录条数
		Integer total = jt.queryForObject("select count(*) from account where money > ? ",Integer.class,500);
		System.out.println(total);
	}
}

在dao中使用JdbcTemplate

准备实体类

/**
 * 账户的实体
 */
public class Account implements Serializable {

	private Integer id;
	private String name;
	private Float money;
	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public Float getMoney() {
		return money;
	}
	public void setMoney(Float money) {
		this.money = money;
	}
	@Override
	public String toString() {
		return "Account [id=" + id + ", name=" + name + ", money=" + money + "]";
	}
}

第一种方式:在dao中定义JdbcTemplate

/**
 * 账户的接口
 */
public interface IAccountDao {
	
	/**
	 * 根据id查询账户信息
	 * @param id
	 * @return
	 */
	Account findAccountById(Integer id);

	/**
	 * 根据名称查询账户信息
	 * @return
	 */
	Account findAccountByName(String name);
	
	/**
	 * 更新账户信息
	 * @param account
	 */
	void updateAccount(Account account);
}

/**
 * 账户的持久层实现类
 * 此版本的dao,需要给dao注入JdbcTemplate
 */
public class AccountDaoImpl implements IAccountDao {

	private JdbcTemplate jdbcTemplate;
	
	public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
		this.jdbcTemplate = jdbcTemplate;
	}

	@Override
	public Account findAccountById(Integer id) {
		List<Account> list =  jdbcTemplate.query("select * from account where id = ? ",new AccountRowMapper(),id);
		return list.isEmpty()?null:list.get(0);
	}

	@Override
	public Account findAccountByName(String name) {
		List<Account> list =  jdbcTemplate.query("select * from account where name = ? ",new AccountRowMapper(),name);
		if(list.isEmpty()){
			return null;
		}
		if(list.size()>1){
			throw new RuntimeException("结果集不唯一,不是只有一个账户对象");
		}
		return list.get(0);
	}

	@Override
	public void updateAccount(Account account) {
		jdbcTemplate.update("update account set money = ? where id = ? ",account.getMoney(),account.getId());
	}

}

配置文件
<?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 -->
	<bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
		<!-- 注入jdbcTemplate -->
		<property name="jdbcTemplate" ref="jdbcTemplate"></property>
	</bean>
	
	<!-- 配置一个数据库的操作模板:JdbcTemplate -->
	<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
		<property name="dataSource" ref="dataSource"></property>
	</bean>
	
	<!-- 配置数据源 -->
	<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
		<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
		<property name="url" value="jdbc:mysql:///spring_day04"></property>
		<property name="username" value="root"></property>
		<property name="password" value="1234"></property>
	</bean>
</beans>

思考:
	此种方式有什么问题吗?
答案:
	有个小问题。就是我们的dao有很多时,每个dao都有一些重复性的代码。下面就是重复代码:
	private JdbcTemplate jdbcTemplate;
	
	public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
		this.jdbcTemplate = jdbcTemplate;
	}
	能不能把它抽取出来呢?
	请看下一小节。

第二种方式:让dao继承JdbcDaoSupport

JdbcDaoSupport是spring框架为我们提供的一个类,该类中定义了一个JdbcTemplate对象,我们可以直接获取使用,但是要想创建该对象,需要为其提供一个数据源:具体源码如下:

public abstract class JdbcDaoSupport extends DaoSupport {
	//定义对象
	private JdbcTemplate jdbcTemplate; 
	//set方法注入数据源,判断是否注入了,注入了就创建JdbcTemplate
	public final void setDataSource(DataSource dataSource) {
	if (this.jdbcTemplate == null || dataSource != this.jdbcTemplate.getDataSource()) {				//如果提供了数据源就创建JdbcTemplate
			this.jdbcTemplate = createJdbcTemplate(dataSource);
			initTemplateConfig();
		}
	}
	//使用数据源创建JdcbTemplate
	protected JdbcTemplate createJdbcTemplate(DataSource dataSource) {
		return new JdbcTemplate(dataSource);
	}
	
	//当然,我们也可以通过注入JdbcTemplate对象
	public final void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
		this.jdbcTemplate = jdbcTemplate;
		initTemplateConfig();
	}
	//使用getJdbcTmeplate方法获取操作模板对象
	public final JdbcTemplate getJdbcTemplate() {
	  return this.jdbcTemplate;
	}
/**
 * 账户的接口
 */
public interface IAccountDao {
	
	/**
	 * 根据id查询账户信息
	 * @param id
	 * @return
	 */
	Account findAccountById(Integer id);

	/**
	 * 根据名称查询账户信息
	 * @return
	 */
	Account findAccountByName(String name);
	
	/**
	 * 更新账户信息
	 * @param account
	 */
	void updateAccount(Account account);
}

/**
 * 账户的持久层实现类
 * 此版本dao,只需要给它的父类注入一个数据源
 */
public class AccountDaoImpl2 extends JdbcDaoSupport implements IAccountDao {

	@Override
	public Account findAccountById(Integer id) {
		//getJdbcTemplate()方法是从父类上继承下来的。
		List<Account> list = getJdbcTemplate().query("select * from account where id = ? ",new AccountRowMapper(),id);
		return list.isEmpty()?null:list.get(0);
	}

	@Override
	public Account findAccountByName(String name) {
		//getJdbcTemplate()方法是从父类上继承下来的。
		List<Account> list =  getJdbcTemplate().query("select * from account where name = ? ",new AccountRowMapper(),name);
		if(list.isEmpty()){
			return null;
		}
		if(list.size()>1){
			throw new RuntimeException("结果集不唯一,不是只有一个账户对象");
		}
		return list.get(0);
	}

	@Override
	public void updateAccount(Account account) {
		//getJdbcTemplate()方法是从父类上继承下来的。
		getJdbcTemplate().update("update account set money = ? where id = ? ",account.getMoney(),account.getId());
	}
}
配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    	xsi:schemaLocation="http://www.springframework.org/schema/beans 
    	http://www.springframework.org/schema/beans/spring-beans.xsd">
	
<!-- 配置dao2 -->
<bean id="accountDao2" class="com.itheima.dao.impl.AccountDaoImpl2">
	<!-- 注入dataSource -->
	<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置数据源 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
	<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
	<property name="url" value="jdbc:mysql:///spring_day04"></property>
		<property name="username" value="root"></property>
		<property name="password" value="1234"></property>
	</bean>
</beans>
思考:
	两版Dao有什么区别呢?
答案:
	第一种在Dao类中定义JdbcTemplate的方式,适用于所有配置方式(xml和注解都可以)。
	第二种让Dao继承JdbcDaoSupport的方式,只能用于基于XML的方式,注解用不了。

Spring中的事务控制

Spring事务控制我们要明确的

第一:
JavaEE体系进行分层开发,事务处理位于业务层,Spring提供了分层设计业务层的事务处理解决方案。
第二:
spring框架为我们提供了一组事务控制的接口。具体在后面的第二小节介绍。这组接口是在spring-tx-4.2.4.RELEASE.jar中。
第三:
spring的事务控制都是基于AOP的,它既可以使用编程的方式实现,也可以使用配置的方式实现。我们学习的重点是使用配置的方式实现。

Spring中事务控制的API介绍

PlatformTransactionManager

此接口是spring的事务管理器,它里面提供了我们常用的操作事务的方法,如下图:

我们在开发中都是使用它的实现类,如下图:

真正管理事务的对象
org.springframework.jdbc.datasource.DataSourceTransactionManager	
使用Spring JDBC或iBatis 进行持久化数据时使用
org.springframework.orm.hibernate3.HibernateTransactionManager		
使用Hibernate版本进行持久化数据时使用

TransactionDefinition

它是事务的定义信息对象,里面有如下方法:

事务的隔离级别

事务的传播行为

REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。一般的选择(默认值)
SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行(没有事务)
MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常
REQUERS_NEW:新建事务,如果当前在事务中,把当前事务挂起。
NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
NEVER:以非事务方式运行,如果当前存在事务,抛出异常
NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行REQUIRED类似的操作。

超时时间

默认值是-1,没有超时限制。如果有,以秒为单位进行设置。

是否是只读事务

建议查询时设置为只读。

TransactionStatus

此接口提供的是事务具体的运行状态,方法介绍如下图:

基于XML的声明式事务控制(配置方式)重点

环境搭建

第一步:拷贝必要的jar包到工程的lib目录

第二步:创建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"
    	xsi:schemaLocation="http://www.springframework.org/schema/beans 
    			http://www.springframework.org/schema/beans/spring-beans.xsd
    			http://www.springframework.org/schema/tx 
    			http://www.springframework.org/schema/tx/spring-tx.xsd
        		http://www.springframework.org/schema/aop 
        		http://www.springframework.org/schema/aop/spring-aop.xsd">	
</beans>

第三步:准备数据库表和实体类

创建数据库:
create database spring_day04;
use spring_day04;
创建表:
create table account(
	id int primary key auto_increment,
	name varchar(40),
	money float
)character set utf8 collate utf8_general_ci;
/**
 * 账户的实体
 */
public class Account implements Serializable {

	private Integer id;
	private String name;
	private Float money;
	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public Float getMoney() {
		return money;
	}
	public void setMoney(Float money) {
		this.money = money;
	}
	@Override
	public String toString() {
		return "Account [id=" + id + ", name=" + name + ", money=" + money + "]";
	}
}

编写业务层接口和实现类

/**
 * 账户的业务层接口
 */
public interface IAccountService {
	
	/**
	 * 根据id查询账户信息
	 * @param id
	 * @return
	 */
	Account findAccountById(Integer id);//查
	
	/**
	 * 转账
	 * @param sourceName	转出账户名称
	 * @param targeName		转入账户名称
	 * @param money			转账金额
	 */
	void transfer(String sourceName,String targeName,Float money);//增删改
}

/**
 * 账户的业务层实现类
 */
public class AccountServiceImpl implements IAccountService {
	
	private IAccountDao accountDao;
	
	public void setAccountDao(IAccountDao accountDao) {
		this.accountDao = accountDao;
	}

	@Override
	public Account findAccountById(Integer id) {
		return accountDao.findAccountById(id);
	}

	@Override
	public void transfer(String sourceName, String targeName, Float money) {
		//1.根据名称查询两个账户
		Account source = accountDao.findAccountByName(sourceName);
		Account target = accountDao.findAccountByName(targeName);
		//2.修改两个账户的金额
		source.setMoney(source.getMoney()-money);//转出账户减钱
		target.setMoney(target.getMoney()+money);//转入账户加钱
		//3.更新两个账户
		accountDao.updateAccount(source);
		int i=1/0;
		accountDao.updateAccount(target);
	}
}

编写Dao接口和实现类

/**
 * 账户的持久层接口
 */
public interface IAccountDao {
	
	/**
	 * 根据id查询账户信息
	 * @param id
	 * @return
	 */
	Account findAccountById(Integer id);

	/**
	 * 根据名称查询账户信息
	 * @return
	 */
	Account findAccountByName(String name);
	
	/**
	 * 更新账户信息
	 * @param account
	 */
	void updateAccount(Account account);
}
/**
 * 账户的持久层实现类
 * 此版本dao,只需要给它的父类注入一个数据源
 */
public class AccountDaoImpl extends JdbcDaoSupport implements IAccountDao {

	@Override
	public Account findAccountById(Integer id) {
		List<Account> list = getJdbcTemplate().query("select * from account where id = ? ",new AccountRowMapper(),id);
		return list.isEmpty()?null:list.get(0);
	}

	@Override
	public Account findAccountByName(String name) {
		List<Account> list =  getJdbcTemplate().query("select * from account where name = ? ",new AccountRowMapper(),name);
		if(list.isEmpty()){
			return null;
		}
		if(list.size()>1){
			throw new RuntimeException("结果集不唯一,不是只有一个账户对象");
		}
		return list.get(0);
	}

	@Override
	public void updateAccount(Account account) {
		getJdbcTemplate().update("update account set money = ? where id = ? ",account.getMoney(),account.getId());
	}
}

/**
 * 账户的封装类RowMapper的实现类
 */
public 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;
	}
}

在配置文件中配置业务层和持久层

<!-- 配置service -->
<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">
	<!-- 注入dataSource -->
	<property name="dataSource" ref="dataSource"></property>
</bean>
	
<!-- 配置数据源 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
	<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
	<property name="url" value="jdbc:mysql:///spring_day04"></property>
	<property name="username" value="root"></property>
	<property name="password" value="1234"></property>
</bean>

配置步骤

第一步:配置事务管理器

<!-- 配置一个事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
	<!-- 注入DataSource -->
	<property name="dataSource" ref="dataSource"></property>
</bean>

第二步:配置事务的通知引用事务管理器

<!-- 事务的配置 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
</tx:advice>

第三步:配置事务的属性

<!--在tx:advice标签内部 配置事务的属性 -->
<tx:attributes>
<!-- 指定方法名称:是业务核心方法 
	read-only:是否是只读事务。默认false,不只读。
	isolation:指定事务的隔离级别。默认值是使用数据库的默认隔离级别。 
	propagation:指定事务的传播行为。
	timeout:指定超时时间。默认值为:-1。永不超时。
	rollback-for:用于指定一个异常,当执行产生该异常时,事务回滚。产生其他异常,事务不回滚。没有默认值,任何异常都回滚。
	no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时,事务回滚。没有默认值,任何异常都回滚。
	-->
	<tx:method name="*" read-only="false" propagation="REQUIRED"/>
	<tx:method name="find*" read-only="true" propagation="SUPPORTS"/>
</tx:attributes>

第四步:配置AOP-切入点表达式

<!-- 配置aop -->
<aop:config>
	<!-- 配置切入点表达式 -->
	<aop:pointcut expression="execution(* com.itheima.service.impl.*.*(..))" id="pt1"/>
</aop:config>

第五步:配置切入点表达式和事务通知的对应关系

<!-- 在aop:config标签内部:建立事务的通知和切入点表达式的关系 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"/>

基于XML和注解组合使用的整合方式

环境搭建

第一步:拷贝必备的jar包到工程的lib目录

第二步:创建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/tx 
    				http://www.springframework.org/schema/tx/spring-tx.xsd
        			http://www.springframework.org/schema/aop 
        			http://www.springframework.org/schema/aop/spring-aop.xsd
        			http://www.springframework.org/schema/context
        		http://www.springframework.org/schema/context/spring-context.xsd">
	<!-- 配置spring要扫描的包 -->
    <context:component-scan base-package="com.itheima"></context:component-scan>
</beans>

第三步:创建业务层接口和实现类并使用注解让spring管理

业务层接口和基于xml配置的时候相同。略

/**
 * 账户的业务层实现类
 */
@Service("accountService")
public class AccountServiceImpl implements IAccountService {
	@Autowired
	private IAccountDao accountDao;

	@Override
	public Account findAccountById(Integer id) {
		return accountDao.findAccountById(id);
	}

	@Override
	public void transfer(String sourceName, String targeName, Float money) {
		//1.根据名称查询两个账户
		Account source = accountDao.findAccountByName(sourceName);
		Account target = accountDao.findAccountByName(targeName);
		//2.修改两个账户的金额
		source.setMoney(source.getMoney()-money);//转出账户减钱
		target.setMoney(target.getMoney()+money);//转入账户加钱
		//3.更新两个账户
		accountDao.updateAccount(source);
		int i=1/0;
		accountDao.updateAccount(target);
	}
}

第四步:创建Dao接口和实现类并使用注解让spring管理

Dao层接口和AccountRowMapper与基于xml配置的时候相同。略

@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {

	@Autowired
	private JdbcTemplate jdbcTemplate;
	
	@Override
	public Account findAccountById(Integer id) {
		List<Account> list = jdbcTemplate.query("select * from account where id = ? ",new AccountRowMapper(),id);
		return list.isEmpty()?null:list.get(0);
	}

	@Override
	public Account findAccountByName(String name) {
		List<Account> list =  jdbcTemplate.query("select * from account where name = ? ",new AccountRowMapper(),name);
		if(list.isEmpty()){
			return null;
		}
		if(list.size()>1){
			throw new RuntimeException("结果集不唯一,不是只有一个账户对象");
		}
		return list.get(0);
	}

	@Override
	public void updateAccount(Account account) {
		jdbcTemplate.update("update account set money = ? where id = ? ",account.getMoney(),account.getId());
	}
}

配置步骤

第一步:配置数据源和JdbcTemplate

<!-- 配置数据源 -->
<bean id="dataSource" 
			class="org.springframework.jdbc.datasource.DriverManagerDataSource">
	<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
	<property name="url" value="jdbc:mysql:///spring_day04"></property>
	<property name="username" value="root"></property>
	<property name="password" value="1234"></property>
</bean>

第二步:配置事务管理器并注入数据源

<!-- 配置JdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
	<property name="dataSource" ref="dataSource"></property>
</bean>

第三步:在业务层使用@Transactional注解

@Service("accountService")
@Transactional(readOnly=true,propagation=Propagation.SUPPORTS)
public class AccountServiceImpl implements IAccountService {
	
	@Autowired
	private IAccountDao accountDao;

	@Override
	public Account findAccountById(Integer id) {
		return accountDao.findAccountById(id);
	}

	@Override
	@Transactional(readOnly=false,propagation=Propagation.REQUIRED)
	public void transfer(String sourceName, String targeName, Float money) {
		//1.根据名称查询两个账户
		Account source = accountDao.findAccountByName(sourceName);
		Account target = accountDao.findAccountByName(targeName);
		//2.修改两个账户的金额
		source.setMoney(source.getMoney()-money);//转出账户减钱
		target.setMoney(target.getMoney()+money);//转入账户加钱
		//3.更新两个账户
		accountDao.updateAccount(source);
		//int i=1/0;
		accountDao.updateAccount(target);
	}
}

该注解的属性和xml中的属性含义一致。该注解可以出现在接口上,类上和方法上。
出现接口上,表示该接口的所有实现类都有事务支持。
出现在类上,表示类中所有方法有事务支持
出现在方法上,表示方法有事务支持。
以上三个位置的优先级:方法>>接口

第四步:在配置文件中开启spring对注解事务的支持

<!-- 开启spring对注解事务的支持 -->
<tx:annotation-driven transaction-manager="transactionManager"/> 

基于纯注解的声明式事务控制(配置方式)重点

环境搭建

第一步:拷贝必备的jar包到工程的lib目录

第二步:创建一个类用于加载spring的配置并指定要扫描的包

/**
 * 用于初始化spring容器的配置类
 */
@Configuration
@ComponentScan(basePackages="com.itheima")
public class SpringConfiguration {

}

第三步:创建业务层接口和实现类并使用注解让spring管理

业务层接口和基于xml配置的时候相同。略

/**
 * 账户的业务层实现类
 */
@Service("accountService")
public class AccountServiceImpl implements IAccountService {
	@Autowired
	private IAccountDao accountDao;

	@Override
	public Account findAccountById(Integer id) {
		return accountDao.findAccountById(id);
	}

	@Override
	public void transfer(String sourceName, String targeName, Float money) {
		//1.根据名称查询两个账户
		Account source = accountDao.findAccountByName(sourceName);
		Account target = accountDao.findAccountByName(targeName);
		//2.修改两个账户的金额
		source.setMoney(source.getMoney()-money);//转出账户减钱
		target.setMoney(target.getMoney()+money);//转入账户加钱
		//3.更新两个账户
		accountDao.updateAccount(source);
		int i=1/0;
		accountDao.updateAccount(target);
	}
}

第四步:创建Dao接口和实现类并使用注解让spring管理

Dao层接口和AccountRowMapper与基于xml配置的时候相同。略

@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {

	@Autowired
	private JdbcTemplate jdbcTemplate;
	
	@Override
	public Account findAccountById(Integer id) {
		List<Account> list = jdbcTemplate.query("select * from account where id = ? ",new AccountRowMapper(),id);
		return list.isEmpty()?null:list.get(0);
	}

	@Override
	public Account findAccountByName(String name) {
		List<Account> list =  jdbcTemplate.query("select * from account where name = ? ",new AccountRowMapper(),name);
		if(list.isEmpty()){
			return null;
		}
		if(list.size()>1){
			throw new RuntimeException("结果集不唯一,不是只有一个账户对象");
		}
		return list.get(0);
	}

	@Override
	public void updateAccount(Account account) {
		jdbcTemplate.update("update account set money = ? where id = ? ",account.getMoney(),account.getId());
	}
}

配置步骤

第一步:使用@Bean注解配置数据源

@Bean(name = "dataSource")
	public DataSource createDS() throws Exception {
		DriverManagerDataSource dataSource = new DriverManagerDataSource();
		dataSource.setUsername("root");
		dataSource.setPassword("123");
		dataSource.setDriverClassName("com.mysql.jdbc.Driver");
		dataSource.setUrl("jdbc:mysql:///spring3_day04");
		return dataSource;
	}

第二步:使用@Bean注解配置配置事务管理器

@Bean
public PlatformTransactionManager 
		createTransactionManager(@Qualifier("dataSource") DataSource dataSource) {
	return new DataSourceTransactionManager(dataSource);
}

第三步:使用@Bean注解配置JdbcTemplate

@Bean
public JdbcTemplate createTemplate(@Qualifier("dataSource") DataSource dataSource) 
{
	return new JdbcTemplate(dataSource);
}

第四步:在需要控制事务的业务层实现类上使用@Transactional注解

@Service("accountService")
@Transactional(readOnly=true,propagation=Propagation.SUPPORTS)
public class AccountServiceImpl implements IAccountService {
	
	@Autowired
	private IAccountDao accountDao;

	@Override
	public Account findAccountById(Integer id) {
		return accountDao.findAccountById(id);
	}

	@Override
	@Transactional(readOnly=false,propagation=Propagation.REQUIRED)
	public void transfer(String sourceName, String targeName, Float money) {
		//1.根据名称查询两个账户
		Account source = accountDao.findAccountByName(sourceName);
		Account target = accountDao.findAccountByName(targeName);
		//2.修改两个账户的金额
		source.setMoney(source.getMoney()-money);//转出账户减钱
		target.setMoney(target.getMoney()+money);//转入账户加钱
		//3.更新两个账户
		accountDao.updateAccount(source);
		//int i=1/0;
		accountDao.updateAccount(target);
	}
}

该注解的属性和xml中的属性含义一致。该注解可以出现在接口上,类上和方法上。
出现接口上,表示该接口的所有实现类都有事务支持。
出现在类上,表示类中所有方法有事务支持
出现在方法上,表示方法有事务支持。
以上三个位置的优先级:方法>>接口。

第五步:使用@EnableTransactionManagement开启spring对注解事务的的支持

@Configuration
@EnableTransactionManagement
public class SpringTxConfiguration {
	//里面配置数据源,配置JdbcTemplate,配置事务管理器。在之前的步骤已经写过了。
}

转载:https://blog.csdn.net/fffvdgjvbsfkb123456/article/details/106459428
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场