Spring入门

Spring
⦁ Spring的体系结构 Spring框架是一个分层架构,它包含一系列的功能要素并被分为大约20个模块。
这些模块分为Core Container、Data Access/Integration、Web、AOP(Aspect Oriented Programming)、Instrumentation和测试部分,如图:
Spring的体系结构
核心容器(Core Container) 包括Core、Beans、Context、EL模块。
1:Core和Beans模块提供了Spring最基础的功能,提供IoC和依赖注入特性。这里的基础概念是BeanFactory,它提供对Factory模式的经典实现来消除对程序性单例模式的需要,并真正地允许你从程序逻辑中分离出依赖关系和配置。
2:Context模块基于Core和Beans来构建,它提供了用一种框架风格的方式来访问对象,有些像JNDI注册表。Context封装包继承了beans包的功能,还增加了国际化(I18N),事件传播,资源装载,以及透明创建上下文,例如通过servlet容器,以及对大量JavaEE特性的支持,如EJB、JMX。核心接口是ApplicationContext。
3:Expression Language,表达式语言模块,提供了在运行期间查询和操作对象图的强大能力。支持访问和修改属性值,方法调用,支持访问及修改数组、容器和索引器,命名变量,支持算数和逻辑运算,支持从Spring 容器获取Bean,它也支持列表投影、选择和一般的列表聚合等。
数据访问/集成部分(Data Access/Integration)
1:JDBC模块,提供对JDBC的抽象,它可消除冗长的JDBC编码和解析数据库厂商特有的错误代码。
2:ORM模块,提供了常用的"对象/关系"映射APIs的集成层。 其中包括JPA、JDO、Hibernate 和 iBatis 。利用ORM封装包,可以混合使用所有Spring提供的特性进行"对象/关系"映射,如简单声明事务管理 。
3:OXM模块,提供一个支持Object和XML进行映射的抽象层,其中包括JAXB、Castor、XMLBeans、JiBX和XStream。
4:JMS模块,提供一套"消息生产者、消费者"模板用于更加简单的使用JMS,JMS用于用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。
5:Transaction模块,支持程序通过简单声明性 事务管理,只要是Spring管理对象都能得到Spring管理事务的好处,即使是POJO,也可以为他们提供事务。
Web
1:Web模块,提供了基础的web功能。例如多文件上传、集成IoC容器、远程过程访问、以及Web Service支持,并提供一个RestTemplate类来提供方便的Restful services访问
2:Web-Servlet模块,提供了Web应用的Model-View-Controller(MVC)实现。Spring MVC框架提供了基于注解的请求资源注入、更简单的数据绑定、数据验证等及一套非常易用的JSP标签,完全无缝与Spring其他技术协作。
3:Web-Portlet模块,提供了在Portlet环境下的MVC实现
AOP
1:AOP模块,提供了符合AOP 联盟规范的面向方面的编程实现,让你可以定义如方法拦截器和切入点,从逻辑上讲,可以减弱代码的功能耦合,清晰的被分离开。而且,利用源码级的元数据功能,还可以将各种行为信息合并到你的代码中 。
2:Aspects模块,提供了对AspectJ的集成。
3:Instrumentation模块, 提供一些类级的工具支持和ClassLoader级的实现,可以在一些特定的应用服务器中使用。
Test
1:Test模块,提供对使用JUnit和TestNG来测试Spring组件的支持,它提供一致的ApplicationContexts并缓存这些上下文,它还能提供一些mock对象,使得你可以独立的测试代码。

⦁ 创建spring工程

第一步:新建工程Spring5_day01,

第二步:导入jar包

1.Spring项目的核心容器的最基本Jar包(4个):

Pom.xml:

4.0.0

cn.itcast.parent
itcast-parent
0.0.1-SNAPSHOT

cn.itcsat.spring
spring4_day01

<dependencies>
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-context</artifactId>
	</dependency>

	
</dependencies>

添加日志:

org.slf4j
slf4j-log4j12

添加log4j.properties文件放置到src/reources下。

log4j.rootLogger=INFO,A1
log4j.logger.org.apache=INFO
log4j.appender.A1.Target=System.err
log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss,SSS} [%t] [%c]-[%p] %m%n

⦁ 传统方式业务代码编写(业务层、数据持久层)
采用的示例业务是模拟用户登录操作。
第一步:创建包cn.itcast.spring.a_quickstart

第二步:创建dao
1:创建接口IUserDao
package cn.itcast.spring.a_quickstart;
//用户的dao层
public interface IUserDao {
//向数据查询数据,根据用户名和密码
public void findByUsernameAndPassword();
}
2:创建IUserDao接口的实现类UserDaoImpl
package cn.itcast.spring.a_quickstart;
//dao的实现类
public class UserDaoImpl implements IUserDao {
@Override
public void findByUsernameAndPassword() {
System.out.println(“UserDaoImpl-dao层被调用了”);
}
}
第三步:创建service
1:创建接口IUserService
package cn.itcast.spring.a_quickstart;
//业务层
public interface IUserService {
//登录
public void login();
}

2:创建IUserService接口的实现类UserServiceImpl
package cn.itcast.spring.a_quickstart;

//业务层实现
public class UserServiceImpl implements IUserService{

public void login() {
	System.out.println("UserServiceImpl-service层被调用了。。。");
	//实例化dao层
	//传统方式
	IUserDao userDao = new UserDaoImpl();
	userDao.findByUsernameAndPassword();
}

}

第四步:测试
创建SpringTest类进行测试:
public class SpringTest {
//测试
@Test
public void test(){
//创建service的示例
IUserService userService = new UserServiceImpl();
userService.login();

}

}

控制台:

【思考分析】
存在问题:代码过于耦合,上层代码过度依赖于下一层代码的实现:
例如:UserDao userDao = new UserDaoImpl();
如果要更换实现类,或者实现类换一个名字,此时代码会报错,必须要修改原来的业务代码!
传统方式:

怎么解决类与类之间如此密切的耦合问题呢?
⦁ IoC控制反转的实现
采用IoC(Inverse of Control,控制反转)的思想解决代码耦合问题。
简单的说就是引入工厂(第三者),将原来在程序中手动创建管理的依赖的UserDaoImpl对象,交给工厂来创建管理。
IoC方式:
步骤一:提供userDAO实例对象的工厂
UserDAOFactory.java:
public class UserDAOFactory {

//提供获取对象的方法
public UserDAOImpl getUserDAO(){
	//返回实例对象
	return new  UserDAOImpl ();
}

}

步骤二: 修改UserServiceImpl中获得对象的方式
UserServiceImpl.java:
public class UserServiceImpl implements IUserService{

public void login() {
	 System.out.println("UserServiceImpl-service层方法调用了");
	 
	 //调用dao层的方法

// IUserDAO userDAO = new UserDAOImpl();
// userDAO.findUserByUsernameAndPassword();

	 //ioc方式:
	 //创建工厂,利用工厂提供依赖的对象
	 UserDAOFactory userDAOFactory = new UserDAOFactory();
	 UserDAOImpl userDAO = userDAOFactory.getUserDAO();
	 userDAO.findUserByUsernameAndPassword();
	
}

}
发现问题:工厂方法仍然需要返回具体类型的实例对象,存在代码耦合
解决方案:使用反射技术传入具体类型的类字符串生产对象的实例:
UserDAOFactory.java:
//利用反射技术生产具体类型的实例对象
public Object getBean(){
try {
return Class.forName(“cn.itcast.spring.a_quickstart.UserDaoImpl”).newInstance();
} catch (Exception e) {
e.printStackTrace();
}

return null;
}

UserServiceImpl.java:
//使用反射方法获取对象
IUserDAO userDAO = (IUserDAO) userDAOFactory.getBean();
userDAO.findUserByUsernameAndPassword();
发现问题:类字符串是硬编码的,怎么动态的传入不同的类字符串呢?
解决方案: 使用xml配置文件动态传入类字符串
IoC底层实现:工厂(设计模式)+反射(机制) + 配置文件(xml)。

⦁ Spring核心配置文件的编写
IoC控制反转的理解和实现
步骤一:在src/resources下建立applicationContext.xml (位置:applicationContext.xml文件放置到任何目录都可以,习惯上放在src目录或者 WEB-INF目录)

ApplicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>

步骤二:配置applicationContext.xml:

<?xml version="1.0" encoding="UTF-8"?>

⦁ 通过Spring的工厂获取Bean完成相关操作
在程序中创建spring工厂对象, 通过工厂对象加载spring的xml配置文件,生产配置文件中配置 的bean对应的对象

UserServiceImpl.java:

//spring配置方式,创建spring工厂,加载spring配置文件
ApplicationContext ac = new ClassPathXmlApplicationContext(“applicationContext.xml”);
//从spring工厂中获取对象,通过bean的id/name
IUserDAO userDAO = (IUserDAO) ac.getBean(“userDAO”);
userDAO.findUserByUsernameAndPassword();
运行测试:

发现问题:该方式虽然解决了类与类之间的耦合关系,但却需要在获取对象的时候创建spring工厂,有没有更方便获取对象的依赖的方法呢?

⦁ DI依赖注入的实现
DI:Dependency Injection 依赖注入,在Spring工厂负责创建Bean对象时,动态的将依赖对象注入到Bean组件(简单的说,可以将一个bean对象动态的注入到另外一个bean中。)
回顾之前的代码:

Di的做法是:由Spring容器创建了Service、Dao对象,并且在配置中将Dao传入Service,那么Service对象就包含了Dao对象的引用。

步骤一:将service对象也交给spring容器管理
applicationContext.xml:

<?xml version="1.0" encoding="UTF-8"?>

步骤二:在程序中定义属性提供setter方法:
UserServiceImpl.java
public class UserServiceImpl implements IUserService{

//定义属性
private IUserDAO userDAO;

public void setUserDAO(IUserDAO userDAO) {
	this.userDAO = userDAO;
}

public void login() {
	 System.out.println("UserServiceImpl-service层方法调用了");
	 
	 //ioc:依赖注入
	 userDAO.findUserByUsernameAndPassword();
	
}

步骤三:测试运行,此时获取对象必须从spring工厂获取(在spring容器配置中才有依赖注入,自己创建的对象没有注入依赖关系)

public class SpringTest {
//测试
@Test
public void test(){
//创建service的示例
//IUserService userService = new UserServiceImpl();
//userService.login();

	//创建spring工厂,获取spring管理的对象
	ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
	IUserService userService = (IUserService) ac.getBean("userService");
	
	userService.login();
	
}

}

运行结果:

小结(重点):
IOC:控制反转,将对象创建管理的权利交给spring容器,获取对象通过spring工厂创建
DI:在spring容器中创建管理多个对象,通过 property标签将对象注入到需要依赖的对象中

⦁ Spring的工厂(了解)
ApplicationContext直译为应用上下文,是用来加载Spring框架配置文件,来构建Spring的工厂对象,它也称之为Spring容器的上下文对象,也称之为Spring的容器。
ApplicationContext 只是BeanFactory(Bean工厂,Bean就是一个java对象) 一个子接口:

为什么不直接使用顶层接口对象来操作呢?

  • BeanFactory 采取延迟加载,第一次getBean时才会实例化Bean
  • Beanfactory的用法:
    BeanFactory ac = new XmlBeanFactory(new FileSystemResource(“D:\applicationContext.xml”));
  • ApplicationContext是对BeanFactory扩展,提供了更多功能
    国际化处理
    事件传递
    Bean自动装配
    各种不同应用层的Context实现

ApplicationContext 更加强大, 所以现在开发基本没人使用BeanFactory。(百度和FactoryBean的区别???)

【扩展】
Bean获取的两种方式:
@Test
public void getBean(){

	ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
	
	//获取bean的两种方式
	//1.通过spring容器中bean的id/name获取
	//IUserService userService = (IUserService) ac.getBean("userService");
	
	//2.根据bean的类型或者bean接口的类型获取,一般使用接口类型
	IUserService userService = (IUserService) ac.getBean(IUserService.class);
	
	userService.login();
	
}

常用根据名称获取(id/name),即第一种方式,使用spring容器中的标识获取对象

如果根据类型获取,配置了多个类型的话,则抛出异常:
applicationContext.xml:

<property name="userDAO" ref="userDAO" />

抛出异常

⦁ IoC容器装配Bean_基于XML配置方式
⦁ 实例化Bean的四种方式 (了解)
创建包:cn.itcast.spring.b_xmlnewbean
第一种方式 无参数构造器 (最常用)
第一步:创建Bean1.java
//1。默认构造器(spring在创建bean的时候自动调用无参构造器来实例化,相当于new Bean1())
public class Bean1 {
}

第二步:在spring容器applicationContext.xml中配置

<!-- 1.默认构造器实例化对象 -->
<bean id ="bean1" class="cn.itcast.spring.b_xmlnewbean.Bean1" />

第三步:创建测试类获取bean对象
SpringTest.java:
public class SpringTest {

@Test
public  void test(){
	//创建spring工厂
	ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
	//1.默认构造器获取bean对象
	Bean1 bean1 = (Bean1) ac.getBean("bean1");
	System.out.println(bean1);
}

}
运行结果:

【错误演示】:
public class Bean1 {

//错误演示
private String name;

public Bean1(String name) {
	this.name = name;
}

}
运行报错:

第二种方式: 静态工厂方法
第一步:创建Bean2.java
//1.静态工厂方法构造:用来在初始化bean2的时候,可以初始化其他的东西
public class Bean2 {
}
第二步:创建Bean2Factory.java类
//静态工厂
public class Bean2Factory {

//静态方法,用来返回对象的实例
public static Bean2 getBean2(){
	//在做实例化的时候,可以做其他的事情,即可以在这里写初始化其他对象的代码
	//Connection conn....
	return new Bean2();
}

}
第三步:Spring的容器applicationContext.xml

<!-- class:直接指定到静态工厂类, factory-method: 指定生产实例的方法, spring容器在实例化工厂类的时候会自动调用该方法并返回实例对象 -->
<bean id = "bean2" class="cn.itcast.spring.b_xmlnewbean.Bean2Factory" factory-method="getBean2" />

第四步:测试类进行测试
@Test
public void test(){
//先构建实例化获取spring的容器(工厂、上下文)
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(“applicationContext.xml”);
//2.静态工厂
Bean2 bean2=(Bean2) applicationContext.getBean(“bean2”);
System.out.println(bean2);
}
第三种方式: 实例工厂方法
第一步:创建Bean3.java
//第三种bean,实例工厂方式创建
public class Bean3 {

}
第二步:创建实例工厂Bean3Factory类
//实例工厂:必须new工厂–》bean
public class Bean3Factory {
//普通的方法,非静态方法
public Bean3 getBean3(){
//初始化实例对象返回
return new Bean3();
}
}
第三步:Spring容器的配置:applicationContext.xml




第四步:使用测试代码,进行测试:
@Test
public void test(){
//先构建实例化获取spring的容器(工厂、上下文)
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(“applicationContext.xml”);
//3.实例工厂
Bean3 bean3=(Bean3) applicationContext.getBean(“bean3”);
System.out.println(bean3);
}

第四种方式: FactoryBean方式:
第一步: 创建Bean4:
public class Bean4 {

}

第二步: 创建Bean4Factory:

import org.springframework.beans.factory.FactoryBean;

public class Bean4Factory implements FactoryBean {

//产生对象
public Bean4 getObject() throws Exception {
return new Bean4();
}

public Class<?> getObjectType() {
return null;
}

public boolean isSingleton() {
return false;
}
}

第三步: 配置applicationContext.xml:

四种方式:
第一种:最常用
⦁ 第三种:一些框架初始化的时候用的多。
第四种:spring底层实现的比较多

⦁ Bean的作用域
由spring创建的bean对象在什么情况下有效。

项目开发中通常会使用:singleton 单例、 prototype多例
Singleton: 在一个spring容器中,对象只有一个实例。(默认值)
Prototype: 在一个spring容器中,存在多个实例,每次getBean 返回一个新的实例。
建立包:cn.itcast.spring.c_xmlscope
第一步:创建类SingletonBean.java和PrototypeBean.java
创建类SingletonBean.java类
//单例bean
public class SingletonBean {
public SingletonBean() {
System.out.println(“SingletonBean:初始化了单例”);
}
}

创建类PrototypeBean.java类
//多例bean
public class PrototypeBean {
public PrototypeBean() {
System.out.println("–PrototypeBean初始化了多例的");
}
}

第二步:定义spring容器,applicationContext.xml:





第三步:测试代码,创建SpringTest.java:
//newbean的方式
public class SpringTest {

@Test
public void test(){
	//先构建实例化获取spring的容器(工厂、上下文)
	ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
	//目标1:看看多次获取bean的时候,是不是同一个
	//目标2:看看bean什么时候初始化的
	//获取单例的bean:应该是同一个
	//单例:每次从spring容器中获取的对象,是同一个对象
	//单例初始化:是在spring容器初始化的时候,就初始化了
	SingletonBean singletonBean1=(SingletonBean)applicationContext.getBean("singletonBean");
	SingletonBean singletonBean2=(SingletonBean)applicationContext.getBean("singletonBean");
	System.out.println(singletonBean1);
	System.out.println(singletonBean2);
	//获取多例的bean:
	//多例:每次从spring容器中获取的对象,不是同一个对象
	//多例初始化:是在getBean的时候初始化,相当于每次getbean就是在new Bean()
	PrototypeBean prototypeBean1=(PrototypeBean)applicationContext.getBean("prototypeBean");
	PrototypeBean prototypeBean2=(PrototypeBean)applicationContext.getBean("prototypeBean");
	System.out.println(prototypeBean1);
	System.out.println(prototypeBean2);
	
}

}
运行查看,测试结果:

【注意】
单例是默认值,如果需要单例对象,则不需要配置scope。

课后百度:无状态和有状态?
⦁ Bean的生命周期
通过spring工厂,可以控制bean的生命周期。
⦁ 在xml配置Bean的初始化和销毁方法
通过 init-method属性 指定实例化后的调用方法
通过 destroy-method属性 指定销毁对象前的方法

创建包cn.itcast.spring.d_xmllifecycle

第一步:创建LifeCycleBean,指定一个init的方法,和一个destroy的方法。
//测试生命周期过程中的初始化和销毁bean
public class LifeCycleBean {

//定义构造方法
public LifeCycleBean() {
	System.out.println("LifeCycleBean构造器调用了");		
}

//初始化后自动调用方法:方法名随意,但也不能太随便,一会要配置
public void init(){
	System.out.println("LifeCycleBean-init初始化时调用");
}

//bean销毁时调用的方法
public void destroy(){
	System.out.println("LifeCycleBean-destroy销毁时调用");
}

}
第二步:Spring的核心容器,applicationContext.xml的配置

<bean id="lifeCycleBean" class="cn.itcast.spring.d_xmllifecycle.LifeCycleBean" init-method="init" destroy-method="destroy" />

第三步:SpringTest.java测试代码:
@Test
public void test(){
//先获取spring的容器,工厂,上下文
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(“applicationContext.xml”);
//对于单例此时已经被初始化
//获取bean
LifeCycleBean lifeCycleBean=(LifeCycleBean) applicationContext.getBean(“lifeCycleBean”);
System.out.println(lifeCycleBean);
//为什么没有销毁方法调用。
//原因是:使用debug模式jvm直接就关了,spring容器还没有来得及销毁对象。
//解决:手动关闭销毁spring容器,自动销毁单例的对象
((ClassPathXmlApplicationContext) applicationContext).close();

}

测试时查看控制台打印,发现销毁方法没有执行。

提示:销毁方法的执行必须满足两个条件:
⦁ 单例(singleton)的bean才会可以手动销毁。
⦁ 必须手动关闭容器(调用close的方法)时,才会执行手动销毁的方法。

⦁ Bean属性的依赖注入
⦁ 属性依赖注入的两种方式
什么是Bean属性的注入?就是对一个对象的属性赋值。有两种方式:

⦁ 第一种:构造器参数注入 new Book(“金瓶梅”,15.8)
⦁ 第二种:setter方法属性注入(setter方法的规范需要符合JavaBean规范)

创建包cn.itcast.spring.e_xmlpropertydi

⦁ 构造器参数注入 constructor-arg
【示例】
第一步:构造器参数注入属性值。
创建Car类,定义构造方法
//目标,构造器参数注入,new car直接将参数的值直接赋值
public class Car {
private Integer id;
private String name;
private Double price;

//有参构造
public Car(Integer id, String name, Double price) {
	this.id = id;
	this.name = name;
	this.price = price;
}


@Override
public String toString() {
	return "Car [id=" + id + ", name=" + name + ", price=" + price + "]";
}

}
第二步:配置applicationContext.xml

<bean id="car" class="cn.itcast.spring.e_xmlpropertydi.Car">
	<!--constructor-arg:告诉spring容器,要调用有参构造方法了,不再调用默认的构造方法了  
	new Car(1,"宝马",99999d)
	参数第一组:定位属性
	    * index:根据索引定位属性,0表示第一个位置
		* name:根据属性参数名称定位属性
		* type:根据属性数据类型定位属性
	参数第二组:值
		* value:简单的值,字符串
		* ref:复杂的(由spring容器创建的bean对象)
	-->
	<!-- <constructor-arg index="0" value="1"/> -->
	<constructor-arg index="0" name="id" value="1"/>
	<!-- <constructor-arg name="name" value="宝马1代"/> -->
	<constructor-arg name="name" >
		<value>宝马2代</value>
	</constructor-arg>
	<constructor-arg type="java.lang.Double" value="99999d"/>
</bean>

第三步:使用SpringTest.java测试:
@Test
public void test(){
//spring容器
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(“applicationContext.xml”);
//获取car
Car car =(Car) applicationContext.getBean(“car”);
System.out.println(car);
}

【补充】
1.定位属性的标签,可以混用

2.自标签的属性赋值问题,可以使用子标签的value,效果和value属性一样

等同于

宝马2代

⦁ setter方法属性注入 property
使用的默认的构造器(new Bean()),但必须提供属性的setter方法,使用setter方法也是企业经常使用的属性注入方式。
两步:在类中加入setter方法,在配置文件中使用property

【示例】
第一步:创建Person.java,定义id、name、car属性
/**

  • 定义人类

  • setter方法属性注入

  • 相当于new Person();
    */
    public class Person {
    private Integer id;
    private String name;
    private Car car;
    //必须提供setter属性方法
    public void setId(Integer id) {
    this.id = id;
    }
    public void setName(String name) {
    this.name = name;
    }
    public void setCar(Car car) {
    this.car = car;
    }

    @Override
    public String toString() {
    return “Person [id=” + id + “, name=” + name + “, car=” + car + “]”;
    }

}
第二步:配置spring容器applicationContext.xml

<bean id="person" class="cn.itcast.spring.e_xmlpropertydi.Person">
	<!-- 
	property:专门进行setter属性注入用的标签 。
		* name:setter方法的属性的名字,例如SetXxx-那么name的属性值为xxx。
		* value:简单的值
		* ref:bean的名字,对象的引用
	-->
	<property name="id" value="1001"/>
	<property name="name" value="Tom"/>
	<!-- <property name="car" ref="car"/> --><!--等同于-->
	<property name="car">
		<ref bean="car"/>
	</property>
</bean>

第三步:使用SpringTest.java测试:
@Test
public void test(){
//spring容器
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(“applicationContext.xml”);
//获取人
Person person=(Person)applicationContext.getBean(“person”);
System.out.println(person);
}
【扩展】

  1. 标签的用法:





    ⦁ p名称空间的使用-了解
    什么是名称空间?
    作用:Schema区分同名元素。(有点类似于java的包)

回顾:Xmlns没有前缀是默认的名称空间。
为简化XML文件的配置,Spring2.5版本开始引入了一个新的p名称空间。简单的说,它的作用是为了简化setter方法属性依赖注入配置的,它不是真正的名称空间。
它的使用方法:
p:<属性名>=“xxx” 引入常量值
p:<属性名>_ref=“xxx” 引用其它Bean对象

操作步骤:
第一步:引入p名称空间

第二步:将 子元素 简化为 元素的属性注入

第三步:测试 @Test public void test(){ //spring容器 ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); Person person2=(Person)applicationContext.getBean("person2"); System.out.println(person2); } 配置时不需要 子元素,简化了配置 . ⦁ spEL表达式的使用 –会用即可 spEL(Spring Expression Language)是一种表达式语言,它是spring3.x版本的新特性。 它的作用是:支持在运行时操作和查询对象,其语法类似统一的EL语言,但是SpEL提供了额外的功能,功能更强大。 什么是EL、OGNL、spEL? EL:操作servlet相关的一些对象和相关的值 OGNL:主要操作struts2值栈,mybatis的动态sql spEL:操作bean相关的

语法: #{…} , 引用另一个Bean 、属性、 方法 , 运算
SpEL表达式的使用功能比较多,Bean操作相关的通常有:
⦁ #{beanid} 引用Bean(具体对象)
⦁ #{beanId.属性} 引用Bean的属性
⦁ #{beanId.方法(参数)} 调用Bean的方法

案例一:配置applicationContext.xml

	<!-- #{person.id} 相当于调用了person的getId()方法 -->
<bean id="person3" class="cn.itcast.spring.e_xmlpropertydi.Person" 
p:id="#{1+1}" p:name="#{person.name.toUpperCase()}" p:car="#{car}"></bean>
如果抛出异常:

需要在Person对象中调用get方法,获取属性值,然后赋值到Person对象name的属性中。

public Integer getId() {
	return id;
}
public String getName() {
	return name;
}
public Car getCar() {
	return car;
}
@Override
public String toString() {
	return "Person [id=" + id + ", name=" + name + ", car=" + car + "]";
}案例二:配置applicationContext.xml

更多参考 : \spring5_day1_课前资料\参考图书\Spring_表达式语言.pdf

⦁ IoC容器装配Bean_基于注解配置方式
⦁ Bean的定义(注册) – 扫描机制
新建项目:spring5_day02_annotation

第一步:
引入依赖

org.springframework
spring-context

 		<dependency>
		<groupId>org.slf4j</groupId>
		<artifactId>slf4j-log4j12</artifactId>
</dependency>

导入log4j.properties,
导入applicationContext.xml

第二步: 编写Service和DAO
xml做法 : ,用的方式创建对象
注解做法 : spring2.5引入 @Component 注解 如果放置到类的上面,相当于在spring容器中定义
创建包:cn.itcast.spring.a_ioc
创建类:CustomerService.java类
/**

  • @Component注解放置到类上

  • 相当于spring容器中定义:

  • 其中id属性默认bean的名字是类名的小写

  • ——————————————————————————————————————————————————————

  • @Component(value=“customerService”)//自定义bean的名字

  • 相当于spring容器中定义:

  • ——————————————————————————————————————————————————————
    */
    @Component(value=“customerService”)
    public class CustomerService {

    //保存业务方法
    public void save(){
    System.out.println(“CustomerService业务层被调用了。。。”);
    }

}

第三步: 配置注解开启和注解Bean的扫描。配置的示例如下:配置applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>

<!-- 配置注解扫描 
		context:component-scan:专门扫描含有@Component注解的类,自动将其作为bean
		base-package:要扫描包的路径,包含子包,cn.itcast.spring表示子包下的所有类定义注解都有效
		注解扫描配置的时候,会自动开启注解功能
-->
<context:component-scan base-package="cn.itcast.spring"/>

第四步:测试:
public class SpringTest {

@Test
public void test(){
	//spring容器
	ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
	//获取bean
	CustomerService customerService=(CustomerService) applicationContext.getBean("customerService");
	customerService.save();
	
}

}

2.衍生注解的问题
实际开发中,使用的是@Component三个衍生注解(“子注解”)
子注解的作用:有分层的意义(分层注解)。
Spring3.0为我们引入了组件自动扫描机制,它可以在类路径底下寻找标注了@Component、@Service、@Controller、@Repository注解的类,并把这些类纳入进spring容器中管理。
除了@Component外,Spring提供了3个功能基本和@Component等效的注解
功能介绍
@Service用于标注业务层组件、(如Service层)
@Controller用于标注控制层组件(如struts中的action层,springMVC中的controller)
@Repository用于标注数据访问组件,(如DAO层组件)。
而 泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注。

第一步:
修改CutomerService.java
//@Component(value=“customerService”)//注释掉
@Service(value=“customerService”)
public class CustomerService {

//保存业务方法
public void save(){
	System.out.println("CustomerService业务层被调用了。。。");
}

}
创建CustomerDao.java
//持久层
@Repository(“customerDao”)
public class CustomerDao {

public void save(){
	System.out.println("CustomerDao层被调用了");
}

}
问题:如果将Dao注入到Service呢?
回顾:如果使用xml的配置,那么可以使用setter方法进行注入



⦁ Bean属性的依赖注入
⦁ 简单数据类型依赖注入(了解)
Spring3.0后,提供 @Value注解,可以完成简单数据的注入
//@Component(value=“customerService”)
@Service(value=“customerService”)
public class CustomerService {
//简单类型的成员变量
@Value(“Rose”)//参数的值简单类型
private String name=“Jack”;

//保存业务方法
public void save(){
	System.out.println("CustomerService业务层被调用了。。。");
	System.out.println("name:"+name);
}

}
⦁ 复杂类型数据依赖注入
下面完成,将Dao类的对象注入到Service类进行使用。
注解实现属性依赖注入,将注解加在setXxx方法上 或者 属性定义上 !(任选其一,省代码了)

第一种: 使用@Value 结合SpEL ---- spring3.0 后用
//@Component(value=“customer”)
@Service(value=“customer”)
public class CustomerService {
//简单类型的成员变量
@Value(“Rose”)//参数的值简单类型
private String name=“Jack”;

//在属性声明上面注入,底层自动还是生成setCustomerDao()
//第一种: 使用@Value 结合SpEL  ---- spring3.0 后用
//其中customerDao表示<bean>节点id的属性值
@Value("#{customerDao}")
private CustomerDao customerDao;

//保存业务方法
public void save(){
	System.out.println("CustomerService业务层被调用了。。。");
	System.out.println("name:"+name);
	customerDao.save();
}

}
第二种:使用@Autowired 结合 @Qualifier
单独使用@Autowired ,表示按照类型注入,会到spring容器中查找CustomerDao的类型,对应,class的属性值,如果找到,可以匹配。
//第二种:使用spring的@Autowired
@Autowired//默认按照类型注入
private CustomerDao customerDao;
使用@Autowired + @ Qualifier 表示按照名称注入,回到spring容器中查找customerDao的名称,对应,id的属性值,如果找到,可以匹配。
//第二种:使用spring的@Autowired 结合 @Qualifier
@Autowired//默认按照类型注入的
@Qualifier(“customerDao”)//必须配合@Autowired注解使用,根据名字注入
private CustomerDao customerDao;

第三种: JSR-250标准(基于jdk) 提供注解@Resource
单独使用@Resource注解,表示先按照名称注入,会到spring容器中查找customerDao的名称,对应,id的属性值,如果找到,可以匹配。
如果没有找到,则会按照类型注入,会到spring容器中查找CustomerDao的类型,对应,class的属性值,如果找到,可以匹配,如果没有找到会抛出异常。
//第三种: JSR-250标准(jdk) 提供@Resource
@Resource//默认先按照名称进行匹配,再按照类型进行匹配
private CustomerDao customerDao;

如果@Resource注解上添加name名称
使用@Resource注解,则按照名称注入,会到spring容器中查找customerDao的名称,对应,id的属性值,如果找到,可以匹配。
如果没有找到,抛出异常。
//第三种: JSR-250标准(jdk) 提供@Resource
@Resource(name=“customerDao”)//只能按照customerDao名称进行匹配
private CustomerDao customerDao;

第四种: JSR-330标准(基于jdk) 提供注解@Inject(麻烦,需要额外导包)

javax.inject
javax.inject
1

在实际开发过程中,第二种方式用的最多(推荐!)

⦁ Bean的初始化和销毁
使用注解定义Bean的初始化和销毁
Spring初始化bean或销毁bean时,有时需要作一些处理工作,因此spring可以在创建和拆卸bean的时候调用bean的两个生命周期方法。
回顾配置文件的写法:<bean id=“foo” class=“…Foo” init-method=“setup”destroy-method=“teardown”/>
注解的写法:
(1)当bean被载入到容器的时候调用setup ,
注解方式如下:
@PostConstruct
初始化
(2)当bean从容器中删除的时候调用teardown(scope= singleton有效)
注解方式如下:
@PreDestroy
销毁

使用 @PostConstruct 注解, 标明初始化方法 —相当于 init-method 指定初始化方法
使用 @PreDestroy 注解, 标明销毁方法 ----相当于 destroy-method 指定对象销毁方法
第一步:创建类:LifeCycleBean.java,定义构造方法、初始化的方法、销毁的方法。
//测试生命周期过程中的初始化和销毁bean
@Component(“lifeCycleBean”)
public class LifeCycleBean {

public LifeCycleBean() {
	System.out.println("LifeCycleBean构造器调用了");
}

//初始化后自动调用方法:方法名随意,但也不能太随便,一会要配置
@PostConstruct//初始化的方法
public void init(){
	System.out.println("LifeCycleBean-init初始化时调用");
}

//bean销毁时调用的方法
@PreDestroy
public void destroy(){
	System.out.println("LifeCycleBean-destroy销毁时调用");
}

}

第二步:使用SpringTest.java完成测试
public class SpringTest {

@Test
public void test() throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException{
	
	ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
	
	/*CustomerService customerService = (CustomerService) ac.getBean("customerService");
	
	customerService.save();*/
	
	//((AbstractApplicationContext) ac).close();
	
	//反射:
	ac.getClass().getMethod("close").invoke(ac);
	
}

}

注意:如果要执行对象的销毁方法
条件一: 单例Bean (在容器close时,单例Bean才会执行销毁方法 )
条件二: 必须调用容器 close 方法

⦁ Bean的作用域
通过@Scope注解,指定Bean的作用域(默认是 singleton 单例)
//测试生命周期过程中的初始化和销毁bean
@Component(“lifeCycleBean”)
//@Scope(value=ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Scope(“prototype”)//默认是单例(singleton),更改为多例(prototype)
public class LifeCycleBean {

}

⦁ XML和注解混合配置 (重点)
一个项目中XML和注解都有(时代变迁,夹缝中的产物)
⦁ Spring2.0 就有@Autowired注解
⦁ Spring2.5 之后才有@Component注解

使用
XML 完成Bean定义
注解 完成Bean属性注入

创建包:cn.itcast.spring.b_mixed

第一步:
(1)创建ProductDao类
//产品的数据层
public class ProductDao {

public void save(){
	System.out.println("查询保存到数据口--数据层调用了");
}

}
(2)创建ProductService类
//产品的业务层
public class ProductService {

//注入dao
//强调:注入必须是bean注入bean
@Autowired
private ProductDao productDao;

//产品的保存
public void save(){
	System.out.println("产品保存了,--业务层");
	//调用dao层
	productDao.save();
}

}

第二步:使用XML的方式完成Bean的定义
创建applicationContext-mixed.xml文件,定义:

<?xml version="1.0" encoding="UTF-8"?>

<!-- xml方式定义bean -->
<bean id="productDao" class="cn.itcast.spring.b_mixed.ProductDao"/>
<bean id="productService" class="cn.itcast.spring.b_mixed.ProductService"/>

<!-- 需要单独开启注解功能 -->
<context:annotation-config/>

备注:这里配置 context:annotation-config
才能使用 @PostConstruct @PreDestroy @Autowired @Resource

<context:annotation-config/>

提示:因为采用注解开发时, context:component-scan 具有context:annotation-config的功能 。
如果没有配置注解扫描,需要单独配置 context:annotation-config, 才能使用注解注入!

⦁ Spring的web集成 (Spring监听器)

第一步: 新建web项目 spring5_day02_web

添加web工程的打包方式为war:

选择项目,编辑项目结构:

右击模块添加web支持:

添加web目录:

目录接口必须为: 项目名\src\main\webapp

添加web.xml文件:

导入jar包,导入applicationContext.xml和log4j.properties文件

Pom.xml:



org.springframework
spring-context
${spring.version}

<!-- JSP相关 -->
<dependency>
    <groupId>jstl</groupId>
    <artifactId>jstl</artifactId>
    <version>${jstl.version}</version>
</dependency>
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>servlet-api</artifactId>
    <version>${servlet-api.version}</version>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>jsp-api</artifactId>
    <version>${jsp-api.version}</version>
    <scope>provided</scope>
</dependency>

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
</dependency>
org.apache.tomcat.maven tomcat7-maven-plugin 2.2 9090 /

最终项目效果如下:

第二步: 创建cn.itcast.spring.service包,
编写HelloService.java 业务类
//业务层
public class HelloService {

public void sayHello(){
	System.out.println("嘿,传智播客。。。。");
}

}

将对象的创建,交给Spring容器管理,applicationContext.xml文件

<?xml version="1.0" encoding="UTF-8"?>

<!-- HelloService的bean -->
<bean id="helloService" class="cn.itcast.spring.service.HelloService"></bean>
第三步: 创建cn.itcast.spring.servlet包,编写HelloServlet.java ,调用HelloService类

使用ApplicationContext applicationContext = new ClassPathXmlApplicationContext()的方式加载spring容器;
public class HelloServlet extends HttpServlet {

public void doGet(HttpServletRequest request, HttpServletResponse response)
		throws ServletException, IOException {

	//传统方式:
	//new service
	//HelloService helloService = new HelloService();
	//spring方式:只要看到new,你就想到spring容器中的<bean>
	ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
	HelloService helloService=(HelloService)applicationContext.getBean("helloService");
	helloService.sayHello();
}

public void doPost(HttpServletRequest request, HttpServletResponse response)
		throws ServletException, IOException {
	this.doGet(request, response);
}

}

Web.xml

helloSerlvet
cn.itcast.servlet.HelloServlet


helloSerlvet
/helloservlet

第四步:将程序部署到tomcat测试:

没问题!

【思考、阅读】直接new ClassPathXmlApplicationContext()有什么缺点?
缺点:在创建Spring容器同时,需要对容器中对象初始化。而每次访问servlet的时候,都要创建了新的spring容器对象,消耗了资源,降低了性能。
解决思路:保证spring容器对象从服务器启动到关闭整个过程中只有一个。
解决方案:将Spring容器绑定到ServletContext容器上,让ServletContext来管理Spring容器。
分析:ServletContext在Web服务运行过程中是唯一的, 其初始化的时候,会自动执行ServletContextListener 监听器 (用来监听上下文的创建和销毁),
具体步骤为:编写一个ServletContextListener监听器,在监听ServletContext到创建的时候,读取spring配置文件创建Spring容器,并将其以属性的方式放到ServletContext中保存(setAttribute(Spring容器名字,Spring容器对象) )。
我们无需手动创建该监听器,因为Spring提供了一个叫ContextLoaderListener的监听器,它位于spring-web-5.0.10.RELEASE.jar中。

开发步骤:

第一步:引入spring-web的依赖

org.springframework
spring-web

第二步:在web.xml 配置Spring的核心监听器

org.springframework.web.context.ContextLoaderListener 第三步:启动tomcat服务器,结果发现异常,因为默认会加载

根据异常提示:发现spring的BeanFactory没有初始化,说明没有找到spring容器,即applicationContext.xml文件

第四步:在web容器中配置spring文件路径
为什么没有找到applicationContext.xml文件呢?因为此时加载的是WEB-INF/applicationContext.xml,而不是src下的applicationContext.xml文件
原因:找到ContextLoaderListener.class,再找到ContextLoader.class,发现默认加载的WEB-INF/applicationContext.xml

解决方案:需要在web.xml中配置,加载spring容器applicationContext.xml文件的路径

org.springframework.web.context.ContextLoaderListener contextConfigLocation classpath:applicationContext.xml 重新启动tomcat服务器,没有异常,问题解决。 第五步:修改Servlet代码。在Servlet 中通过ServletContext 获取Spring容器对象 第一种方式: 使用getAttribute //每次获取的都是一个spring容器 ApplicationContext applicationContext = (ApplicationContext)this.getServletContext().getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE); 第二种方式:使用WebApplicationContextUtils (推荐) //工具类 WebApplicationContext applicationContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext()); ⦁ Spring的junit测试集成 Spring提供spring-test.RELEASE 可以整合junit。 优势:可以简化测试代码(不需要手动创建上下文) org.springframework spring-test

使用spring和junit集成
第一步: 通过@RunWith注解,使用junit整合spring
通过@ContextConfiguration注解,指定spring容器的位置
//目标:测试一下spring的bean的某些功能
@RunWith(SpringJUnit4ClassRunner.class)//junit整合spring的测试//立马开启了spring的注解
@ContextConfiguration(locations=“classpath:applicationContext.xml”)//加载核心配置文件,自动构建spring容器
public class SpringTest {
//使用注解注入要测试的bean
@Autowired
private HelloService helloService;

@Test
public void testSayHello(){

	helloService.sayHello();
	
}

}
上述代码表示:在测试类运行前的初始化的时候,会自动创建ApplicationContext对象

第二步: 通过@Autowired注解,注入需要测试的对象
在这里注意2点:

  1. 将测试对象注入到测试用例中
  2. 测试用例不需要配置context:annotion-config/,因为使用测试类运行的时候,会自动启动注解的支持。
    //使用注解注入要测试的bean
    @Autowired
    private HelloService helloService;

第三步:调用测试方法完成测试
@Test
public void testSayHello(){
helloService.sayHello();

}

⦁ AOP面向切面编程的相关概念
⦁ 什么是AOP ?
AOP (Aspect Oriented Programing) 称为:面向切面编程,它是一种编程思想。
AOP采取横向抽取机制,取代了传统纵向继承体系重复性代码的编写方式(例如性能监视、事务管理、安全检查、缓存、日志记录等)。
【扩展了解】AOP 是 OOP(面向对象编程(Object Oriented Programming,OOP,面向对象程序设计)是一种计算机编程架构),思想延续 !

AOP 思想: 基于代理思想,对原来目标对象,创建代理对象,在不修改原对象代码情况下,通过代理对象,调用增强功能的代码,从而对原有业务方法进行增强 !

⦁ AOP的应用场景
场景一: 记录日志
场景二: 监控方法运行时间 (监控性能)
场景三: 权限控制
场景四: 缓存优化 (第一次调用查询数据库,将查询结果放入内存对象, 第二次调用, 直接从内存对象返回,不需要查询数据库 )
场景五: 事务管理 (调用方法前开启事务, 调用方法后提交或者回滚、关闭事务 )

⦁ Spring AOP编程两种方式
⦁ Spring AOP使用纯Java实现,不需要专门的编译过程和类加载器,在运行期通过代理方式向目标类植入增强代码。
⦁ AsPectJ是一个基于Java语言的AOP框架,Spring2.0开始,Spring AOP引入对Aspect的支持。

简单的说,Spring内部支持两套AOP编程的方案:
⦁ Spring 1.2 开始支持AOP编程 (传统SpringAOP 编程),编程非常复杂 ---- 更好学习Spring 内置传统AOP代码
⦁ Spring 2.0 之后支持第三方 AOP框架(AspectJ ),实现另一种 AOP编程 – 推荐

⦁ AOP编程相关术语
AOP思想编程的机制

AOP的相关术语
Aspect(切面): 是通知和切入点的结合,通知和切入点共同定义了关于切面的全部内容—它的功能、在何时和何地完成其功能
joinpoint(连接点):所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点.
Pointcut(切入点):所谓切入点是指我们要对哪些joinpoint进行拦截的定义.
通知定义了切面的”什么”和”何时”,切入点就定义了”何地”.
Advice(通知):所谓通知是指拦截到joinpoint之后所要做的事情就是通知.通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能)
Target(目标对象):代理的目标对象
weaving(织入):是指把切面应用到目标对象来创建新的代理对象的过程.切面在指定的连接点织入到目标对象
Introduction(引介):在不修改类代码的前提下, Introduction可以在运行期为类动态地添加一些方法或Field.

通过案例解释AOP相关概念
需求:UserDao中有5个方法,分别是save()、update()、delete()、find()、login();在访问UserDao的save()、update()、delete()方法之前,进行记录日志的操作。

⦁ AOP编程底层实现机制(了解)
AOP 就是要对目标进行代理对象的创建, Spring AOP是基于动态代理的,基于两种动态代理机制: JDK动态代理和CGLIB动态代理
⦁ JDK动态代理
JDK动态代理,针对目标对象的接口进行代理 ,动态生成接口的实现类 !(必须有接口)

【过程要点】:
1、 必须对接口生成代理
2、 采用Proxy类,通过newProxyInstance方法为目标创建代理对象。
该方法接收三个参数 :
(1)目标对象类加载器
(2)目标对象实现的接口
(3)代理后的处理程序InvocationHandler
使用 Proxy类提供 newProxyInstance 方法对目标对象接口进行代理

参数说明:
loader:定义代理类的类加载器
interfaces:代理类要实现的接口列表
h:指派方法调用的调用处理程序

3、 实现InvocationHandler 接口中 invoke方法,在目标对象每个方法调用时,都会执行invoke 

【示例】:需求:对目标对象中存在保存和查询的方法,在执行保存的方法的时候,记录日志
第一步:新建工程:spring5_day03_proxy。创建包cn.itcast.spring.a_proxy包
第二步:编写业务接口,接口中定义save()和find()的方法。
//接口(表示代理的目标接口)
public interface ICustomerService {
//保存
public void save();
//查询
public int find();
}

第三步:编写业务类,类要实现接口
//实现类
public class CustomerServiceImpl implements ICustomerService{

public void save() {
	System.out.println("客户保存了。。。。。");
}

public int find() {
	System.out.println("客户查询数量了。。。。。");
	return 100;
}

}

第四步:使用JDK代理完成
代理工厂:
有三种方案完成JDK动态代理:
方案一:在内部实现new InvocationHandler(),指定匿名类
//专门用来生成jdk的动态代理对象的-通用
public class JdkProxyFactory{
//成员变量
private Object target;
//注入target目标对象
public JdkProxyFactory(Object target) {
this.target = target;
}

public Object getProxyObject(){
	//参数1:目标对象的类加载器
	//参数2:目标对象实现的接口
	//参数3:回调方法对象
	/**方案一:在内部实现new InvocationHandler(),指定匿名类*/
	return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler(){

		public Object invoke(Object proxy, Method method, Object[] args)
				throws Throwable {
			//如果是保存的方法,执行记录日志操作
			if(method.getName().equals("save")){
				writeLog();
			}
			//目标对象原来的方法执行
			Object object = method.invoke(target, args);//调用目标对象的某个方法,并且返回目标对象方法的返回值
			return object;
		}
		
	});
}

//记录日志
private static void writeLog(){
	System.out.println("增强代码:写日志了。。。");
}

}
方案二:传递内部类的对象,指定内部类
//专门用来生成jdk的动态代理对象的-通用
public class JdkProxyFactory{
//成员变量
private Object target;
//注入target
public JdkProxyFactory(Object target) {
this.target = target;
}

public Object getProxyObject(){
	//参数1:目标对象的类加载器
	//参数2:目标对象实现的接口
	//参数3:回调方法对象
	/**方案二:传递内部类的对象,指定内部类*/
	return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),new MyInvocationHandler());
}

//自己制定内部类:类的内部可以多次使用类型
private class MyInvocationHandler implements InvocationHandler{

	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		//如果是保存的方法,执行记录日志操作
		if(method.getName().equals("save")){
			writeLog();
		}
		//目标对象原来的方法执行
		Object object = method.invoke(target, args);//调用目标对象的某个方法,并且返回目标对象方法的返回值
		return object;
	}
	
}

//记录日志
private static void writeLog(){
	System.out.println("增强代码:写日志了。。。");
}

}

方案三:直接使用调用类作为接口实现类,实现InvocationHandler接口
//专门用来生成jdk的动态代理对象的-通用
public class JdkProxyFactory implements InvocationHandler{
//成员变量
private Object target;
//注入target
public JdkProxyFactory(Object target) {
this.target = target;
}

public Object getProxyObject(){
	//参数1:目标对象的类加载器
	//参数2:目标对象实现的接口
	//参数3:回调方法对象
	/**方案三:直接使用调用类作为接口实现类,实现InvocationHandler接口*/
	return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),this);
}

//记录日志
private static void writeLog(){
	System.out.println("增强代码:写日志了。。。");
}

//参数1:代理对象
//参数2:目标的方法对象
//参数3:目标的方法的参数
public Object invoke(Object proxy, Method method, Object[] args)
		throws Throwable {
	//如果是保存的方法,执行记录日志操作
	if(method.getName().equals("save")){
		writeLog();
	}
	//目标对象原来的方法执行
	Object object = method.invoke(target, args);//调用目标对象的某个方法,并且返回目标对象
	return object;
}

}

第五步:使用SpringTest.java进行测试
//目标:使用动态代理,对原来的方法进行功能增强,而无需更改原来的代码。
//JDK动态代理:基于接口的(对象的类型,必须实现接口!)
@Test
public void testJdkProxy(){
//target(目标对象)
ICustomerService target = new CustomerServiceImpl();
//实例化注入目标对象
JdkProxyFactory jdkProxyFactory = new JdkProxyFactory(target);
//获取Proxy Object代理对象:基于目标对象类型的接口的类型的子类型的对象
ICustomerService proxy = (ICustomerService)jdkProxyFactory.getProxyObject();
//调用目标对象的方法
proxy.save();
System.out.println("————————————————————————————————————————");
proxy.find();
}

第六步:在控制台查看输出结果

从结果上看出:在保存方法的前面,输入了日志增强。

最后,使用断点查看JDK代理,生成的代理对象

JDK原理分析:

Interface ICustomerService{
//目标接口
}
Class CustomerServiceImpl implements ICustomerService{
//目标类实现接口
}
JDK代理对接口代理
Class $Proxy2 implements ICustomerService{
//JDK代理类是目标接口的实现类
ICustomerService customerService = new CustomerServiceImpl();
public void save() {
writeLog()
customerService.save();
}

public int find() {
int returnValue = customerService.find();
return returnValue;
}

//记录日志
private static void writeLog(){
System.out.println(“增强代码:写日志了。。。”);
}

}

注意:
JDK动态代理的缺点: 只能面向接口代理,不能直接对目标类进行代理 ,如果没有接口,则不能使用JDK代理。

⦁ Cglib动态代理
Cglib的引入为了解决类的直接代理问题(生成代理子类),不需要接口也可以代理 !
什么是cglib ?
CGLIB(Code Generation Library)是一个开源项目!是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。

该代理方式需要相应的jar包,但不需要导入。因为Spring core包已经包含cglib ,而且同时包含了cglib 依赖的asm的包(动态字节码的操作类库)

【示例】:需求:对目标对象中存在保存和查询的方法,在执行保存的方法的时候,记录日志
第一步: 引入依赖spring-context

org.springframework
spring-context

spring-core中已经包含了cglib
第二步:编写业务类,创建类ProductService.java,类不需要实现接口
//没有接口的类
public class ProductService {
public void save() {
System.out.println(“商品保存了。。。。。”);

}

public int find() {
	System.out.println("商品查询数量了。。。。。");
	return 99;
}

}
第三步:使用cglib代理,创建类CglibProxyFactory.java
//cglib动态代理工厂:用来生成cglib代理对象
public class CglibProxyFactory implements MethodInterceptor{
//声明一个代理对象引用
private Object target;
//注入代理对象
public CglibProxyFactory(Object target) {
this.target = target;
}

//获取代理对象
public Object getProxyObject(){
	//1.代理对象生成器(工厂思想)
	Enhancer enhancer = new Enhancer();
	//2.在增强器上设置两个属性
	//设置要生成代理对象的目标对象:生成的目标对象类型的子类型
	enhancer.setSuperclass(target.getClass());
	//设置回调方法
	enhancer.setCallback(this);

// Callback
//3.创建获取对象
return enhancer.create();
}

//回调方法(代理对象的方法)
//参数1:代理对象
//参数2:目标对象的方法对象
//参数3:目标对象的方法的参数的值
//参数4:代理对象的方法对象
public Object intercept(Object proxy, Method method, Object[] args,
		MethodProxy methodProxy) throws Throwable {
	//如果是保存的方法,执行记录日志操作
	if(method.getName().equals("save")){
		writeLog();
	}
	//目标对象原来的方法执行
	Object object = method.invoke(target, args);//调用目标对象的某个方法,并且返回目标对象方法的执行结果
	return object;
}

//写日志的增强功能
//Advice通知、增强
//记录日志
private static void writeLog(){
	System.out.println("增强代码:写日志了。。。");
}

}

第四步:测试代码,使用SpringTest.java进行测试
//cglib动态代理:可以基于类(无需实现接口)生成代理对象
@Test
public void testCglibProxy(){
//target目标:
ProductService target = new ProductService();
//weave织入,生成proxy代理对象
//代理工厂对象,注入目标
CglibProxyFactory cglibProxyFactory = new CglibProxyFactory(target);
//获取proxy:思考:对象的类型
//代理对象,其实是目标对象类型的子类型
ProductService proxy=(ProductService) cglibProxyFactory.getProxyObject();
//调用代理对象的方法
proxy.save();
System.out.println("————————————————————————————————————————");
proxy.find();

}

第五步:控制台输出结果

最后,使用断点查看cglib代理,生成的代理对象

Cglib代理原理分析:

Class ProductService{
//目标类
}
Cglib对类代理
Class ProductService E n h a n c e r B y C G L I B EnhancerByCGLIB EnhancerByCGLIBdf9980d0 extends ProductService{
//CGLIB代理类是目标类的子类
ProductService productService= new ProductService();
public void save() {
writeLog()
productService.save();
}

public int find() {
int returnValue = productService.find();
return returnValue;
}

//记录日志
private static void writeLog(){
System.out.println(“增强代码:写日志了。。。”);
}

}

⦁ 代理知识小结
区别:
⦁ Jdk代理:基于接口的代理,一定是基于接口,会生成目标对象的接口类型的子对象。
⦁ Cglib代理:基于类的代理,不需要基于接口,会生成目标对象类型的子对象。

代理知识总结:

⦁ spring在运行期,生成动态代理对象,不需要特殊的编译器.
⦁ spring有两种代理方式:
1.若目标对象实现了若干接口,spring使用JDK的java.lang.reflect.Proxy类代理。
2.若目标对象没有实现任何接口,spring使用CGLIB库生成目标对象的子类。
⦁ 使用该方式时需要注意:
1.对接口创建代理优于对类创建代理,spring推荐面向接口编程,因为这样会产生更加松耦合的系统,所以spring默认是使用JDK代理。
对类代理是让遗留系统或无法实现接口的第三方类库同样可以得到通知,这种方式应该是备用方案。
2.标记为final的方法不能够被通知。spring是为目标类产生子类。任何需要被通知的方法都被复写,将通知织入。final方法是不允许重写的。
3.spring只支持方法连接点:不提供属性接入点,spring的观点是属性拦截破坏了封装。
面向对象的概念是对象自己处理工作,其他对象只能通过方法调用的得到的结果。

提示:
⦁ Spring AOP 优先对接口进行代理 (使用Jdk动态代理)
⦁ 如果目标对象没有实现任何接口,才会对类进行代理 (使用cglib动态代理)

⦁ 传统Spring AOP编程案例
传统SpringAOP 是指 1.2版本之后开始支持AOP编程 。
提示:
老的AOP的编程配置过于复杂,这里采用AspectJ的切入点语法来讲解。

面向切面编程开发步骤(动态织入)
1、确定目标对象(target—>bean)
2、编写Advice通知方法 (增强代码)
3、配置切入点和切面

直接使用 CustomerService(需要接口)和ProductService(不需要接口)作为 target目标对象

提示:spring的所有功能,都是基于bean的,下面所说的“目标”,都是bean。
⦁ 传统SpringAOP的Advice编写(了解)
传统Spring AOP的通知(增强)种类:
⦁ AOP联盟为通知Advice定义了org.aopalliance.aop.Interface.Advice
⦁ Spring按照通知Advice在目标类方法的连接点位置,可以分为5类
(1)前置通知 org.springframework.aop.MethodBeforeAdvice
* 在目标方法执行前实施增强
(2)后置通知 org.springframework.aop.AfterReturningAdvice
* 在目标方法执行后实施增强
(3)环绕通知 org.aopalliance.intercept.MethodInterceptor
* 在目标方法执行前后实施增强
(4)异常抛出通知 org.springframework.aop.ThrowsAdvice
* 在方法抛出异常后实施增强
(5)引介通知 org.springframework.aop.IntroductionInterceptor
* 在目标类中添加一些新的方法和属性

简单的说:通知就是增强的方式方法
遵循aop联盟规范,传统Spring AOP编程的Advice有五种(前置通知、后置通知、环绕通知、异常通知、引介通知) ,
传统SpringAOP的Advice 必须实现对应的接口!
【需求】:开发一个记录方法运行时间的例子。将目标方法的运行时间,写入到log4j的日志中。
开发步骤:
第一步:引入所需依赖:

	<!-- spring核心依赖 -->
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-context</artifactId>
	</dependency>
	
	<!—- springaspect集成 -->
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-aspects</artifactId>
	</dependency>

	<!-- 单元测试 -->
	<dependency>
		<groupId>junit</groupId>
		<artifactId>junit</artifactId>
		<scope>test</scope>
	</dependency>

	<!-- 日志 -->
	<dependency>
		<groupId>org.slf4j</groupId>
		<artifactId>slf4j-log4j12</artifactId>
	</dependency>
	
	<!-- spring集成测试 -->
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-test</artifactId>

test

引入applicationContext.xml配置文件和日志文件:

使用动态代理的代码将对象配置到spring工厂中:

第二步:编写传统aop的Advice通知类。
创建包:cn.itcast.spring.b_oldaop,创建类TimeLogInterceptor 。
传统aop的通知,必须实现MethodInterceptor(环绕通知)接口
package com.itheima.spring.b_oldaop;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.log4j.Logger;

public class TimeLogInterceptor implements MethodInterceptor {

//日志记录器

private static Logger LOGGER = Logger.getLogger(TimeLogInterceptor.class);

@Override
//invocation:代理对象的包装类,获取 代理对象,目标对象,目标方法等信息
public Object invoke(MethodInvocation invocation) throws Throwable {
	
	//方法开始前
	long beforeTime = System.currentTimeMillis();
	
	
	//调用目标对象原来的方法,并返回结果
	Object proceed = invocation.proceed();//相当于 method.invoke();
	
	
	//方法结束后
	long afterTime = System.currentTimeMillis();
	
	
	//计算运行时间
	 long runTime = afterTime - beforeTime;
	 
	 System.out.println(invocation.getThis().getClass().getName()+"类的"+invocation.getMethod().getName()+"方法运行了:"+runTime);
	 
	 LOGGER.info(invocation.getThis().getClass().getName()+"类的"+invocation.getMethod().getName()+"方法运行了:"+runTime+"毫秒");

	 
	return proceed;
}

}

配置log4j.properties:
log4j.rootLogger=INFO,stdout,file
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.Target=System.err
log4j.appender.stdout.layout.ConversionPattern=%p\t%d{ISO8601}\t%r\t%c\t[%t]\t%m%n

log4j.appender.file=org.apache.log4j.DailyRollingFileAppender
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.File=d:/mylog.log
log4j.appender.file.layout.ConversionPattern=%p\t%d{ISO8601}\t%r\t%c\t[%t]\t%m%n

第三步:核心配置文件中,创建applicationContext.xml文件:(确定目标和配置通知),仍然使用CustomerServiceImpl和ProductService进行测试。





<!-- 2.配置增强:原则bean能增强bean 
	Advice:通知,增强
-->
<bean id="timeLogAdvice" class="cn.itcast.spring.b_oldaop.TimeLogInterceptor"/>

第四步:使用SpringTest进行测试
//springjunit集成测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations=“classpath:applicationContext.xml”)
public class SpringTest {
//注入要测试bean
@Autowired
private ICustomerService customerService;
@Autowired
private ProductService productService;

//测试
@Test
public void test(){
	//基于接口
	customerService.save();
	customerService.find();
	//基于类的
	productService.save();
	productService.find();
}

}
第六步:查看执行结果:

但是发现,此时并没有执行TimeLogInterceptor 类的invoke()方法,也就是说,并没有计算执行Service类的时间,那怎么办呢?我们往下看,需要在spring容器中配置spring的aop。

⦁ 配置切入点和切面
目的:让哪个类(切面)、哪个方法(切入点),进行怎样的增强(通知)。

配置切面,让通知关联切入点






<!-- 2.配置增强: 
	Advice:通知,增强
-->
<bean id="timeLogAdvice" class="cn.itcast.spring.b_oldaop.TimeLogInterceptor"/>

<!-- 3.配置切入点和切面 :aop:config-->
<aop:config>
	<!-- 
		配置切入点:即你要拦截的哪些 连接点(方法)
			* expression:表达式:匹配方法的,语法:使用aspectj的语法,相对简单
			* 表达式:bean(bean的名字),你要对哪些bean中的所有方法增强
			* expression=bean(*Service):在spring容器中,所有id/name以Service单词结尾的bean的都能被拦截
			* id="myPointcut":为切入点定义唯一标识
	-->

<aop:pointcut expression=“bean(*Service)” id=“myPointcut”/>

	<!-- 
		    advice-ref="timeLogAdvice"
			* 配置切面:通知(增强的方法)关联切入点(目标对象调用的方法)
			pointcut-ref="myPointcut"
			* 告诉:你要对哪些方法(pointcut),进行怎强的增强 (advice)
	 -->
			<aop:advisor advice-ref="timeLogAdvice" pointcut-ref="myPointcut"/>
</aop:config>

第三步:使用SpringTest.java进行测试:
//springjunit集成测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations=“classpath:applicationContext.xml”)
public class SpringTest {
//注入要测试bean
@Autowired
private ICustomerService customerService;
@Autowired
private ProductService productService;

//测试
@Test
public void test(){
	//基于接口
	customerService.save();
	customerService.find();
	//基于类的
	productService.save();
	productService.find();
}

}

第四步:查看结果:

切入点表达式的语法整理如下:
⦁ bean(bean Id/bean name)
例如 bean(customerService) 增强spring容器中定义id属性/name属性为customerService的bean中所有方法
⦁ execution(<访问修饰符>?<返回类型>空格<方法名>(<参数>)<异常>?)
例如:
execution(* cn.itcast.spring.a_jdkproxy.CustomerServiceImpl.(…)) 增强bean对象所有方法
execution(
cn.itcast.spring….(…)) 增强spring包和子包所有bean所有方法
提示:最灵活的

案例应用:

切入点的表达式
execution(void cn.itcast.e_xml.a_before.UserServiceImpl.saveUser(java.lang.String,java.lang.String))

  • 表示:无返回类型,cn.itcast.e_xml.a_before包中的UserServiceImpl类,类中的saveUser方法,参数2个,都是String类型

表达式的写法
execution(modifiers-pattern? (非必填项)–<访问修饰符>?
ret-type-pattern (必填项)–<返回类型>
declaring-type-pattern? (非必填项)
name-pattern(param-pattern)(必填项)–<方法名>(<参数>)
throws-pattern?(非必填项)<异常>?

一共有5个参数
其中的?表示非必填项

文档中写的:
除了返回类型模式(上面代码片断中的ret-type-pattern),名字模式和参数模式以外, 所有的部分都是可选的。
返回类型模式决定了方法的返回类型必须依次匹配一个连接点。 你会使用的最频繁的返回类型模式是*,它代表了匹配任意的返回类型。
一个全限定的类型名将只会匹配返回给定类型的方法。名字模式匹配的是方法名。 你可以使用通配符作为所有或者部分命名模式。
参数模式稍微有点复杂:()匹配了一个不接受任何参数的方法, 而(…)匹配了一个接受任意数量参数的方法(零或者更多)。
模式(
)匹配了一个接受一个任何类型的参数的方法。 模式(*,String)匹配了一个接受两个参数的方法,第一个可以是任意类型, 第二个则必须是String类型。更多的信息请参阅AspectJ编程指南中 语言语义的部分。

1:modifiers-pattern? (非必填项):表示方法的修饰符
execution(public void cn.itcast.e_xml.a_before.UserServiceImpl.saveUser(java.lang.String,java.lang.String))

  • 表示:共有方法,无返回类型,cn.itcast.e_xml.a_before包中的UserServiceImpl类,类中的saveUser方法,参数2个,都是String类型
    execution(private void cn.itcast.e_xml.a_before.UserServiceImpl.saveUser(java.lang.String,java.lang.String))
  • 表示:私有方法,无返回类型,cn.itcast.e_xml.a_before包中的UserServiceImpl类,类中的saveUser方法,参数2个,都是String类型
    2:ret-type-pattern (必填项):表示方法的返回类型
    execution(void cn.itcast.e_xml.a_before.UserServiceImpl.saveUser(java.lang.String,java.lang.String))
  • 表示:无返回类型,cn.itcast.e_xml.a_before包中的UserServiceImpl类,类中的saveUser方法,参数2个,都是String类型
    execution(java.lang.String cn.itcast.e_xml.a_before.UserServiceImpl.saveUser(java.lang.String,java.lang.String))
  • 表示:返回类型String类型,cn.itcast.e_xml.a_before包中的UserServiceImpl类,类中的saveUser方法,参数2个,都是String类型
    execution(* cn.itcast.e_xml.a_before.UserServiceImpl.saveUser(java.lang.String,java.lang.String))
  • 表示:返回类型任意,cn.itcast.e_xml.a_before包中的UserServiceImpl类,类中的saveUser方法,参数2个,都是String类型
    3:declaring-type-pattern? (非必填项):表示包,或者子包的,或者类的修饰符
    execution(* cn.itcast.e_xml.a_before.UserServiceImpl.saveUser(java.lang.String,java.lang.String))
  • 表示返回类型任意,cn.itcast.e_xml.a_before包中的UserServiceImpl类,类中的saveUser方法,参数2个,都是String类型
    execution(* cn.itcast.e_xml.*.UserServiceImpl.saveUser(java.lang.String,java.lang.String))
  • 表示返回类型任意,cn.itcast.e_xml包中的所有子包,包中UserServiceImpl类,类中的saveUser方法,参数2个,都是String类型
    execution(* cn.itcast.e_xml.*.saveUser(java.lang.String,java.lang.String))
  • 表示返回类型任意,cn.itcast.e_xml包中的所有类,类中的saveUser方法,参数2个,都是String类型
    execution(* cn.itcast.e_xml…*.saveUser(java.lang.String,java.lang.String))
  • 表示返回类型任意,cn.itcast.e_xml包中及其子包中的所有类,类中的saveUser方法,参数2个,都是String类型
    execution(* *.saveUser(java.lang.String,java.lang.String))
  • 表示返回类型任意,所有包中的所有类,类中的saveUser方法,参数2个,都是String类型
    execution(* saveUser(java.lang.String,java.lang.String))
  • 表示返回类型任意,所有包中的所有类,类中的saveUser方法,参数2个,都是String类型
    4:name-pattern(param-pattern)(必填项):方法的名称(方法的参数)
    (1)方法名称
    execution(* cn.itcast.e_xml.a_before.UserServiceImpl.saveUser(java.lang.String,java.lang.String))
  • 表示返回类型任意,cn.itcast.e_xml.a_before包中的UserServiceImpl类,类中的saveUser方法,参数2个,都是String类型
    execution(* cn.itcast.e_xml.a_before.UserServiceImpl.save*(java.lang.String,java.lang.String))
  • 表示返回类型任意,cn.itcast.e_xml.a_before包中的UserServiceImpl类,类中的以save开头的方法,参数2个,都是String类型
    execution(* cn.itcast.e_xml.a_before.UserServiceImpl.*(java.lang.String,java.lang.String))
  • 表示返回类型任意,cn.itcast.e_xml.a_before包中的UserServiceImpl类,类中的所有方法,参数2个,都是String类型

(2)方法的参数
execution(* cn.itcast.e_xml.a_before.UserServiceImpl.saveUser(java.lang.String,java.lang.String))

  • 表示返回类型任意,cn.itcast.e_xml.a_before包中的UserServiceImpl类,类中的saveUser方法,参数2个,都是String类型
    execution(* cn.itcast.e_xml.a_before.UserServiceImpl.saveUser(java.lang.String,java.lang.Integer))
  • 表示返回类型任意,cn.itcast.e_xml.a_before包中的UserServiceImpl类,类中的saveUser方法,参数2个,参数1是String类型,参数二是Integer
    execution(* cn.itcast.e_xml.a_before.UserServiceImpl.saveUser(java.lang.String,*))
  • 表示返回类型任意,cn.itcast.e_xml.a_before包中的UserServiceImpl类,类中的saveUser方法,参数2个,参数1是String类型,参数二是任意类型
    execution(* cn.itcast.e_xml.a_before.UserServiceImpl.saveUser(*))
  • 表示返回类型任意,cn.itcast.e_xml.a_before包中的UserServiceImpl类,类中的saveUser方法,参数1个,参数是任意类型
    execution(* cn.itcast.e_xml.a_before.UserServiceImpl.saveUser())
  • 表示返回类型任意,cn.itcast.e_xml.a_before包中的UserServiceImpl类,类中的saveUser方法,没有参数
    execution(* cn.itcast.e_xml.a_before.UserServiceImpl.saveUser(…))
  • 表示返回类型任意,cn.itcast.e_xml.a_before包中的UserServiceImpl类,类中的saveUser方法,参数任意(可以是0个,也可以多个)
    5:throws-pattern?(非必填项):方法上抛出的异常

项目开发中表达式(最多用)

1:execution(* cn.itcast.procject.service….(…))

  • 返回类型任意,cn.itcast.procject.service包及其子包中所有类,类中所有方法,参数任意
    2:execution(* .*(…))
  • 返回类型任意,任意包中及其子包中所有类,类中所有方法,参数任意
    3:execution(* *(…))
  • 返回类型任意,任意包中及其子包中所有类,类中所有方法,参数任意

下面给出一些通用切入点表达式的例子。

任意公共方法的执行:
execution(public * *(…))

任何一个名字以“set”开始的方法的执行:
execution(* set*(…))

AccountService接口定义的任意方法的执行:
execution(* com.xyz.service.AccountService.*(…))

在service包中定义的任意方法的执行:
execution(* com.xyz.service..(…))

在service包或其子包中定义的任意方法的执行:
execution(* com.xyz.service….(…))

⦁ within(包.类)
例如: within(cn.itcast.spring…*) 增强spring包和子包所有bean“所有方法 ”
⦁ this(完整类型)/target(完整类型)
范围最小,只针对某个类型。
this对某一个类-(对代理对象有效),target对代理对象无效(只对目标对象有效)
例如: this(cn.itcast.spring.a_jdkproxy.CustomerServiceImpl) 增强类型所有方法(对代理对象有效)
target(cn.itcast.spring.a_jdkproxy.CustomerServiceImpl)增强类型所有方法(对目标对象有效)
注意:我们一般都对目标对象进行拦截,很少对代理对象进行拦截

【AspectJ类型匹配的通配符】
*:匹配任何数量字符(一个);
…:匹配任何数量字符的重复(多个),如在类型模式中匹配任何数量子包;而在方法参数模式中匹配任何数量参数。
+:匹配指定类型的子类型;仅能作为后缀放在类型模式后边。

【测试】:在applicationContext.xml文件中,测试切入点表达式的写法:

<aop:config>
	<!-- <aop:pointcut expression="bean(*Service)" id="myPointcut"/> -->
	<aop:pointcut expression="execution(* cn.itcast.spring..*.*(..))" id="myPointcut"/> 
	 
	<aop:advisor advice-ref="timeLogAdvice" pointcut-ref="myPointcut"/>
</aop:config>

【补充】:事实上,当运行的时候,两个Service已经是代理对象了。如:使用Debug断点进行调试
(1)有接口的customerService类:(jdk动态代理)

(2)没有接口的productService:(cglib动态代理)

⦁ AspectJ 切面编程(xml方式)

Xml配置aop开发方法还是三步:

  1. 确定目标对象(bean)
  2. 编写通知,对目标对象增强(advice)
  3. 配置切入点(pointcut)、切面(aspect)
    ⦁ AspectJ 提供Advice类型
    普通的pojo即可。(不需要实现接口)

AspectJ提供不同的通知类型:
⦁ Before 前置通知,相当于BeforeAdvice
⦁ AfterReturning 后置通知,相当于AfterReturningAdvice
⦁ Around 环绕通知,相当于MethodInterceptor
⦁ AfterThrowing抛出通知,相当于ThrowAdvice
⦁ After 最终final通知,不管是否异常,该通知都会执行
⦁ DeclareParents 引介通知,相当于IntroductionInterceptor (不要求掌握)

相比传统Spring AOP通知类型多了 After最终通知 (类似 finally )。

实现步骤:
第一步:确定目标对象,即确定bean对象
第二步:advice通知(编写)
第三步:配置切面(包括切入点),让切入点关联通知

第一步:确定目标对象,即确定bean对象:
在src下,创建applicationContext-aspect.xml。






第二步:编写Before 前置通知Advice增强 :
创建包:cn.itcast.spring.c_aspectaop
创建类:MyAspect.java

编写MyAspect.java
//aspectj的advice通知增强类,无需实现任何接口
public class MyAspect {

//前置通知
//普通的方法。方法名随便,但也不能太随便,一会要在applicationContext.xml中配置
public void firstbefore(){
	System.out.println("------------第一个前置通知执行了。。。");
}

}
将前置通知配置到spring的容器中

⦁ 配置切入点和切面(让切入点关联通知) 第三步:配置切面(包括切入点),让切入点关联通知 核心配置文件applicationContext-aspect.xml中添加:
<!-- 3:配置aop -->
<aop:config>
	<!-- 切入点:拦截哪些bean的方法 -->
	<aop:pointcut expression="bean(*Service)" id="myPointcut"/>
	<!--
		切面:要对哪些方法进行怎样的增强  
		aop:aspect:aspejctj的方式!
		ref:配置通知
	-->
	<aop:aspect ref="myAspectAdvice">
		
		<!-- 第一个前置通知 :在访问目标对象方法之前,先执行通知的方法
			method:advice类中的方法名,
            pointcut-ref="myPointcut":注入切入点
            目的是让通知关联切入点
		-->
	    <aop:before method="firstbefore" pointcut-ref="myPointcut"/>
	</aop:aspect>
</aop:config>

使用SpringTest测试代码:
//springjunit集成测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations=“classpath:applicationContext-aspect.xml”)
public class SpringTest {
//注入要测试bean
@Autowired
private ICustomerService customerService;
@Autowired
private ProductService productService;

//测试
@Test
public void test(){
	//基于接口
	customerService.save();
	customerService.find();
	//基于类的
	productService.save();
	productService.find();
}

}

测试结果:

和传统的aop配置相比,更灵活,advice不需要实现接口,简单的pojo就可以了;一个通知可以增强多个连接点,一个连接点可以被多次增强。

【扩展优化】:
1.将切入点放入aspect标签里面写,同时配置多个通知方法

<bean id="myAspectAdvice" class="cn.itcast.spring.c_aspectaop.MyAspect"/>

<!-- 3:配置aop -->
<aop:config>
	<!--
		切面:要对哪些方法进行怎样的增强  
		aop:aspect:aspejctj的方式!
		ref:配置通知
	-->
	<aop:aspect ref="myAspectAdvice">
		<aop:pointcut expression="bean(*Service)" id="myPointcut"/>
		<!-- 第一个前置通知 :在访问目标对象方法之前,先执行通知的方法
			method:advice类中的方法名,
            pointcut-ref="myPointcut":注入切入点
                                    目的是让通知关联切入点
		-->
	    <aop:before method="firstbefore" pointcut-ref="myPointcut"/>
		<aop:before method="firstbefore2" pointcut-ref="myPointcut"/>
	</aop:aspect>
</aop:config>

2.配置多个通知方法:
//aspectj的advice通知增强类,无需实现任何接口
public class MyAspect {

//前置通知
//普通的方法。方法名随便,但也不能太随便,一会要配置
public void firstbefore(){
	System.out.println("------------第一个前置通知执行了。。。");
}

public void firstbefore2(){
	System.out.println("------------第二个前置通知执行了222。。。");
}

}

  1. 执行结果:表示在执行目标对象方法之前执行

AspectJ切面编程,相比于传统的SpringAOP,定义的通知方法更多。
⦁ 分析各种通知应用
⦁ Before前置通知
案例应用: 实现权限控制 (即:权限不足的时候,抛出异常)、 记录方法调用信息日志
第一步:配置MyAspect类(切面),配置before方法(通知)
//aspectj的advice通知增强类,无需实现任何接口
public class MyAspect {
//前置通知的:方法运行之前增强
//应用: 权限控制 (权限不足,抛出异常)、 记录方法调用信息日志
//参数:org.aspectj.lang.JoinPoint
//参数:连接点对象(方法的包装对象:方法,参数,目标对象)
public void before(JoinPoint joinPoint){
//分析:抛出异常拦截的
//当前登录用户
String loginName = “Rose”;
System.out.println(“方法名称:”+joinPoint.getSignature().getName());
System.out.println(“目标对象:”+joinPoint.getTarget().getClass().getName());
System.out.println(“代理对象:”+joinPoint.getThis().getClass().getName());
//判断当前用户有没有执行方法权限
if(joinPoint.getSignature().getName().equals(“save”)){
if(!loginName.equals(“admin”)){
//只有超级管理员admin有权限,其他人不能执行某个方法,比如查询方法
throw new RuntimeException(“您没有权限执行方法:”+joinPoint.getSignature().getName()+",类型为:"+joinPoint.getTarget().getClass().getName());
}
}

}

}
通过JoinPoint 连接点对象,获取目标对象信息 !
这里注意:引包不要引错了,使用aspectj中的连接点(org.aspectj.lang.JoinPoint):

第二步:Spring容器中配置,配置applicationContext-aspect.xml

<!-- 对于spring来说,目标对象:就是bean对象 -->
<!-- 基于接口类 -->
<bean id="customerService" class="cn.itcast.spring.a_proxy.CustomerServiceImpl"/>
<!-- 基于一般类的 -->
<bean id="productService" class="cn.itcast.spring.a_proxy.ProductService"/>
<!-- 2.配置advice通知增强 -->
<bean id="myAspectAdvice" class="cn.itcast.spring.c_aspectaop.MyAspect"/>

<!-- 3:配置aop -->
<aop:config>
	<aop:aspect ref="myAspectAdvice">
		<aop:pointcut expression="bean(*Service)" id="myPointcut"/>
		<!-- 前置通知 -->
		<aop:before method="before" pointcut-ref="myPointcut" />
	</aop:aspect>

</aop:config>

第三步:使用SpringTest.java进行测试:
//springjunit集成测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations=“classpath:applicationContext-aspect.xml”)
public class SpringTest {
//注入要测试bean
@Autowired
private ICustomerService customerService;
@Autowired
private ProductService productService;

//测试
@Test
public void test(){
	//基于接口
	customerService.save();
	customerService.find();
	//基于类的
	productService.save();
	productService.find();
}

}

⦁ AfterReturing 后置通知
特点:在目标方法运行后,返回值后执行通知增强代码逻辑。
应用场景:与业务相关的,如网上营业厅查询余额后,自动下发短信功能。

分析: 后置通知可以获取到目标方法返回值,如果想对返回值进行操作,使用后置通知(但不能修改目标方法返回 )
第一步:配置MyAspect类(切面),配置afterReturing方法(通知)
//aspectj的advice通知增强类,无需实现任何接口
public class MyAspect {
//应用场景:与业务相关的,如网上营业厅查询余额后,自动下发短信。
//后置通知:会在目标方法执行之后调用通知方法增强。
//参数1:连接点对象(方法的包装对象:方法,参数,目标对象)
//参数2:目标方法执行后的返回值,类型是object,“参数名”随便,但也不能太随便,一会要配置
public void afterReturing(JoinPoint joinPoint,Object returnVal){
//下发短信:调用运行商的接口,短信猫。。。
System.out.println("-+++++++±后置通知-当前下发短信的方法"+"-尊敬的用户,您调用的方法返回余额为:"+returnVal);

}

}

第二步:Spring容器中配置,配置applicationContext-aspect.xml

<!-- 对于spring来说,目标对象:就是bean对象 -->
<!-- 基于接口类 -->
<bean id="customerService" class="cn.itcast.spring.a_proxy.CustomerServiceImpl"/>
<!-- 基于一般类的 -->
<bean id="productService" class="cn.itcast.spring.a_proxy.ProductService"/>
<!-- 2.配置advice通知增强 -->
<bean id="myAspectAdvice" class="cn.itcast.spring.c_aspectaop.MyAspect"/>

<!-- 3:配置aop -->
<aop:config>
	<aop:aspect ref="myAspectAdvice">
		<aop:pointcut expression="bean(*Service)" id="myPointcut"/>
		<!-- 后置通知 
			returning:配置方法中的参数名字,与通知方法的第二个参数的名字,名字必须对应。
			在运行的时候,spring会自动将返回值传入该参数中。
		-->
		<aop:after-returning method="afterReturing" returning="returnVal" pointcut-ref="myPointcut"/>
	</aop:aspect>

</aop:config>

第三步:使用SpringTest.java进行测试:
//springjunit集成测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations=“classpath:applicationContext-aspect.xml”)
public class SpringTest {
//注入要测试bean
@Autowired
private ICustomerService customerService;
@Autowired
private ProductService productService;

//测试
@Test
public void test(){
	//基于接口
	customerService.save();
	customerService.find();
	//基于类的
	productService.save();
	productService.find();
}

}

⦁ Around 环绕通知
特点:目标执行前后,都进行增强(控制目标方法执行)
应用场景:日志、缓存、权限、性能监控、事务管理
增强代码的方法要求:
接受的参数:ProceedingJoinPoint(可执行的连接点)
返回值:Object返回值(即目标对象方法的返回值)
抛出Throwable异常。
【示例】
第一步:配置MyAspect类(切面),配置around方法(通知)
//aspectj的advice通知增强类,无需实现任何接口
public class MyAspect {
//应用场景:日志、缓存、权限、性能监控、事务管理
//环绕通知:在目标对象方法的执行前+后,可以增强
//参数:可以执行的连接点对象ProceedingJoinPoint(方法),特点是调用proceed()方法可以随时随地执行目标对象的方法(相当于目标对象的方法执行了)
//必须抛出一个Throwable
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
//目标:事务的控制:
//开启事务:
System.out.println("-----开启了事务。。。。。。。。。");

	//执行了目标对象的方法
	Object resultObject = proceedingJoinPoint.proceed();  		//结束事务
	System.out.println("-----提交了事务。。。。。。。。。");  		return resultObject;//目标对象执行的结果
}

}

第二步:Spring容器中配置,配置applicationContext-aspect.xml

<!-- 对于spring来说,目标对象:就是bean对象 -->
<!-- 基于接口类 -->
<bean id="customerService" class="cn.itcast.spring.a_proxy.CustomerServiceImpl"/>
<!-- 基于一般类的 -->
<bean id="productService" class="cn.itcast.spring.a_proxy.ProductService"/>
<!-- 2.配置advice通知增强 -->
<bean id="myAspectAdvice" class="cn.itcast.spring.c_aspectaop.MyAspect"/>

<!-- 3:配置aop -->
<aop:config>
	<aop:aspect ref="myAspectAdvice">
		<aop:pointcut expression="bean(*Service)" id="myPointcut"/>
		<!-- 环绕通知 -->
		<aop:around method="around" pointcut-ref="myPointcut"/>
	</aop:aspect>

</aop:config>

第三步:使用SpringTest.java进行测试:
//springjunit集成测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations=“classpath:applicationContext-aspect.xml”)
public class SpringTest {
//注入要测试bean
@Autowired
private ICustomerService customerService;
@Autowired
private ProductService productService;

//测试
@Test
public void test(){
	//基于接口
	customerService.save();
	customerService.find();
	//基于类的
	productService.save();
	productService.find();
}

}

⦁ AfterThrowing 抛出通知
作用:目标代码出现异常,通知执行。记录异常日志、通知管理员(短信、邮件)
应用场景:处理异常(一般不可预知),记录日志
【示例】
第一步:配置MyAspect类(切面),配置aterThrowing方法(通知)
//aspectj的advice通知增强类,无需实现任何接口
public class MyAspect {
//作用:目标代码出现异常,通知执行。记录异常日志、通知管理员(短信、邮件)
//只有目标对象方法抛出异常,通知才会执行
//参数1:静态连接点(方法对象)
//参数2:目标方法抛出的异常,参数名随便,但也不能太随便
public void afterThrowing(JoinPoint joinPoint,Throwable ex){
//一旦发生异常,发送邮件或者短信给管理员
System.out.println("++管理员您好,"+joinPoint.getTarget().getClass().getName()+“的方法:”
+joinPoint.getSignature().getName()+“发生了异常,异常为:”+ex.getMessage());
}
}

第二步:Spring容器中配置,配置applicationContext-aspect.xml

<!-- 对于spring来说,目标对象:就是bean对象 -->
<!-- 基于接口类 -->
<bean id="customerService" class="cn.itcast.spring.a_proxy.CustomerServiceImpl"/>
<!-- 基于一般类的 -->
<bean id="productService" class="cn.itcast.spring.a_proxy.ProductService"/>
<!-- 2.配置advice通知增强 -->
<bean id="myAspectAdvice" class="cn.itcast.spring.c_aspectaop.MyAspect"/>

<!-- 3:配置aop -->
<aop:config>
	<aop:aspect ref="myAspectAdvice">
		<aop:pointcut expression="bean(*Service)" id="myPointcut"/>
		<!-- 抛出通知
			throwing:通知中的方法的第二个参数,异常类型的参数的名字,在运行的时候,spring会自动将异常传入该参数中。-->
		<aop:after-throwing method="aterThrowing" throwing="ex" pointcut-ref="myPointcut"/>		
    </aop:aspect>

</aop:config>

第三步:使用SpringTest.java进行测试:
//springjunit集成测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations=“classpath:applicationContext-aspect.xml”)
public class SpringTest {
//注入要测试bean
@Autowired
private ICustomerService customerService;
@Autowired
private ProductService productService;

//测试
@Test
public void test(){
	//基于接口
	customerService.save();
	customerService.find();
	//基于类的
	productService.save();
	productService.find();
}

}
此时发现通知的方法并没有执行。
那我们在目标对象的方法中故意抛出异常,大家看看效果
测试:
在ProductService.java中save的方法中,制造异常:
//没有接口的类
public class ProductService {
public void save() {
System.out.println(“商品保存了。。。。。”);
//故意制造异常
int d = 1/0;
}

public int find() {
	System.out.println("商品查询数量了。。。。。");
	return 99;
}

}
查看测试结果:

⦁ After 最终通知
作用:不管目标方法是否发生异常,最终通知都会执行(类似于finally代码功能)
应用场景:释放资源 (关闭文件、 关闭数据库连接、 网络连接、 释放内存对象 )
【示例】
第一步:配置MyAspect类(切面),配置after方法(通知)
//aspectj的advice通知增强类,无需实现任何接口
public class MyAspect {
//应用场景:释放资源 (关闭文件、 关闭数据库连接、 网络连接、 释放内存对象 )
//最终通知:不管是否有异常都会执行
public void after(JoinPoint joinPoint){
//释放数据库连接
System.out.println(“数据库的connection被释放了。。。。。,执行的方法是:”+joinPoint.getSignature().getName());
}
}

第二步:Spring容器中配置,配置applicationContext-aspect.xml

<!-- 对于spring来说,目标对象:就是bean对象 -->
<!-- 基于接口类 -->
<bean id="customerService" class="cn.itcast.spring.a_proxy.CustomerServiceImpl"/>
<!-- 基于一般类的 -->
<bean id="productService" class="cn.itcast.spring.a_proxy.ProductService"/>
<!-- 2.配置advice通知增强 -->
<bean id="myAspectAdvice" class="cn.itcast.spring.c_aspectaop.MyAspect"/>

<!-- 3:配置aop -->
<aop:config>
	<aop:aspect ref="myAspectAdvice">
		<aop:pointcut expression="bean(*Service)" id="myPointcut"/>
		<!-- 最终通知 -->
		<aop:after method="after" pointcut-ref="myPointcut"/>
        <!--  以上代码也可以写成:pointcut切入点表达式:只能给一个通知方法来用,相当于省略了<aop:pointcut expression="bean(*Service)" id="myPointcut"/>
		<aop:after method="after" pointcut="bean(*Service)"/>-->
	</aop:aspect>

</aop:config>

第三步:使用SpringTest.java进行测试:
//springjunit集成测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations=“classpath:applicationContext-aspect.xml”)
public class SpringTest {
//注入要测试bean
@Autowired
private ICustomerService customerService;
@Autowired
private ProductService productService;

//测试
@Test
public void test(){
	//基于接口
	customerService.save();
	customerService.find();
	//基于类的
	productService.save();
	productService.find();
}

}
查看测试结果:

⦁ 通知小结

五种通知小结:
(1)只要掌握Around(环绕通知)通知类型,就可实现其他四种通知效果。
(2)因为你可以在环绕通知的方法中编写如下代码:
try {
//前置通知
Object result = proceedingJoinPoint.proceed();
//后置通知
}catch(Exception){
//抛出通知
}finally{
//最终通知
}

方法格式:
public returnType method (param)
public 返回值类型 方法名 (参数类型 参数名)
返回值类型:void和Object
方法名:任意名称(但是也不能太随意)
参数类型:

  • 参数类型为JoinPoint接口类型,返回值类型为void
  • 参数类型为ProceedingJoinPoint接口类型,返回值类型为Object
    具体为:

通知类型 输入参数(可选) 返回值类型 其他
Before前置通知 JoinPoint(静态连接点信息) void
AfterReturning后置通知 JoinPoint, Object void
Around环绕通知 ProceedingJoinPoint(可执行的连接点信息) Object throws Throwable
AfterThrowing抛出通知 JoinPoint, Throwable void
After最终通知 JoinPoint void

⦁ @Aspectj注解配置切面编程
⦁ 搭建环境
新建项目 spring5_day03_annotation , 引入依赖


org.springframework
spring-context

	<!-- springaop相关包 -->
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-aspects</artifactId>
	</dependency>

	<!-- 单元测试 -->
	<dependency>
		<groupId>junit</groupId>
		<artifactId>junit</artifactId>
		<scope>test</scope>
	</dependency>

	<!-- 日志 -->
	<dependency>
		<groupId>org.slf4j</groupId>
		<artifactId>slf4j-log4j12</artifactId>
	</dependency>
	
	<!-- spring集成测试 -->
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-test</artifactId>
		<version>4.3.13.RELEASE</version>
	</dependency>

同时导入
applicationContext.xml,
log4j.properties到工程
⦁ 第一步: 编写目标对象 (bean)、spring容器、测试类
创建包:cn.itcast.spring.a_aspect
(1):创建接口CustomerService.java
//接口
public interface CustomerService {
//保存
public void save();

//查询
public int find();

}
创建接口的实现类,CustomerServiceImpl
//实现类
/**

  • @Service(“customerService”)
  • 相当于spring容器中定义:

*/
@Service(“customerService”)
public class CustomerServiceImpl implements CustomerService{

public void save() {
	System.out.println("客户保存了。。。。。");
	
}

public int find() {
	System.out.println("客户查询数量了。。。。。");
	return 100;
}

}
创建类ProductService.java,不需要实现接口
//没有接口的类
/**

  • @Service(“productService”)
  • 相当于spring容器中定义:

*/
@Service(“productService”)
public class ProductService {
public void save() {
System.out.println(“商品保存了。。。。。”);

}

public int find() {
	System.out.println("商品查询数量了。。。。。");
	return 99;
}

}

(2):配置applicationContext.xml
引入几个命名空间:bean、aop、context

使用bean注解的扫描(自动开启注解功能)

<!-- 扫描bean组件 -->
<context:component-scan base-package="cn.itcast.spring"/>

(3):测试代码SpringTest.java
//springjunit集成测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations=“classpath:applicationContext.xml”)
public class SpringTest {
//注入要测试bean
@Autowired
private CustomerService customerService;
@Autowired
private ProductService productService;

//测试
@Test
public void test(){
	//基于接口
	customerService.save();
	customerService.find();
	
	//基于类的
	productService.save();
	productService.find();
}

}
测试结果:

⦁ 第二步: 编写通知,配置切面

⦁ 编写通知类,在通知类 添加@Aspect 注解,代表这是一个切面类,并将切面类交给spring管理(能被spring扫描到@Component)。
@Component(“myAspect”):将增强的类交给spring管理,才可以增强
@Aspect:将该类标识为切面类(这里面有方法进行增强),相当于<aop:aspect ref=”myAspect”>
//advice通知类增强类
@Component(“myAspect”)//相当于
@Aspect//相当于<aop:aspect ref=“myAspect”>
public class MyAspect {

}

2) 在切面的类,通知方法上添加
@AspectJ提供不同的通知类型
@Before 前置通知,相当于BeforeAdvice
@AfterReturning 后置通知,相当于AfterReturningAdvice
@Around 环绕通知,相当于MethodInterceptor
@AfterThrowing抛出通知,相当于ThrowAdvice
@After 最终final通知,不管是否异常,该通知都会执行
@DeclareParents 引介通知,相当于IntroductionInterceptor (不要求掌握)

复习回顾:如果是applicationContext.xml中配置通知类型:如下:

aop:config
<aop:aspect ref=“myAspectAdvice”>
<aop:pointcut expression=“bean(*Service)” id=“myPointcut”/>

<aop:before method=“before” pointcut-ref=“myPointcut” />
</aop:aspect>
</aop:config>
等同于:以下是简化的写法!可以省略aop:pointcut

aop:config
<aop:aspect ref=“myAspectAdvice”>

<aop:before method=“before” pointcut=“bean(*Service)” />
</aop:aspect>
</aop:config>
3) 在spring容器中开启AspectJ 注解自动代理机制
使用aop:aspectj-autoproxy/
作用:能自动扫描带有@Aspect的bean,将其作为增强aop的配置,有点相当于:aop:config


<context:component-scan base-package=“cn.itcast.spring”/>

<!-- 3:配置aop的aspectj的自动代理:
		自动扫描bean组件中,含有@Aspect的bean,将其作为aop管理,开启动态代理    -->

aop:aspectj-autoproxy/

⦁ 前置通知
在切面的类MyAspect.java类中添加通知方法@Before(),
方案一:可以直接将切入点的表达式写到@Before()中
//前置通知
//相当于:<aop:before method=“before” pointcut=“bean(*Service)”/>
//@Before(“bean(*Service)”):参数值:自动支持切入点表达式或切入点名字
@Before(“bean(*Service)”)
public void before(JoinPoint joinPoint){
System.out.println("=======前置通知。。。。。");
}

方案二:可以使用自定义方法,使用@Pointcut 定义切入点
切入点方法的语法要求:
切点方法:private void 无参数、无方法体的方法,方法名为切入点的名称
一个通知方法@Before可以使用多个切入点表达式,中间使用“||”符合分隔,用来表示多个切入点

//自定义切入点
//方法名就是切入点的名字
//相当于<aop:pointcut expression="bean(*Service)" id="myPointcut"/>
@Pointcut("bean(customerService)")
private void myPointcut(){}
//自定义切入点
//方法名就是切入点的名字
//相当于<aop:pointcut expression="bean(*Service)" id="myPointcut2"/>
@Pointcut("bean(productService)")
private void myPointcut2(){}


//前置通知
//相当于:<aop:before method="before" pointcut-ref="myPointcut"/>
//相当于:<aop:before method="before" pointcut-ref="myPointcut2"/>
@Before("myPointcut()||myPointcut2()")
public void before(JoinPoint joinPoint){
	System.out.println("=======前置通知。。。。。");
}

使用SpringTest进行测试:
//springjunit集成测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations=“classpath:applicationContext.xml”)
public class SpringTest {
//注入要测试bean
@Autowired
private CustomerService customerService;
@Autowired
private ProductService productService;

//测试
@Test
public void test(){
	//基于接口
	customerService.save();
	customerService.find();		
	//基于类的
	productService.save();
	productService.find();
}

}
测试结果:

⦁ 后置通知
在切面的类MyAspect.java类中添加通知方法
//后置通知
@AfterReturning(value=“bean(*Service)”,returning=“returnVal”)
public void afterReturning(JoinPoint joinPoint,Object returnVal){
System.out.println("=======后置通知。。。。。");
}
使用SpringTest进行测试:
//springjunit集成测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations=“classpath:applicationContext.xml”)
public class SpringTest {
//注入要测试bean
@Autowired
private CustomerService customerService;
@Autowired
private ProductService productService;

//测试
@Test
public void test(){
	//基于接口
	customerService.save();
	customerService.find();
	
	//基于类的
	productService.save();
	productService.find();
}

}
查看测试结果:

⦁ 环绕通知
在切面的类MyAspect.java类中添加通知方法
//环绕通知:
@Around(“bean(*Service)”)
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
System.out.println("—环绕通知-----前");
Object object = proceedingJoinPoint.proceed();
System.out.println("—环绕通知-----后");
return object;
}
使用SpringTest进行测试:
//springjunit集成测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations=“classpath:applicationContext.xml”)
public class SpringTest {
//注入要测试bean
@Autowired
private CustomerService customerService;
@Autowired
private ProductService productService;

//测试
@Test
public void test(){
	//基于接口
	customerService.save();
	customerService.find();
	
	//基于类的
	productService.save();
	productService.find();
}

}
测试结果:

⦁ 抛出通知
在切面的类MyAspect.java类中添加通知方法
//抛出通知

@AfterThrowing(value="bean(*Service)",throwing="ex")
public void afterThrowing(JoinPoint joinPoint ,Throwable ex){
	System.out.println("---抛出通知。。。。。。"+"抛出的异常信息:"+ex.getMessage());
}

使用SpringTest进行测试:
//springjunit集成测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations=“classpath:applicationContext.xml”)
public class SpringTest {
//注入要测试bean
@Autowired
private CustomerService customerService;
@Autowired
private ProductService productService;

//测试
@Test
public void test(){
	//基于接口
	customerService.save();
	customerService.find();
	
	//基于类的
	productService.save();
	productService.find();
}

}
发现没有执行抛出通知,原因是目标对象没有异常,在ProductService添加异常。
@Service(“productService”)
public class ProductService {
public void save() {
System.out.println(“商品保存了。。。。。”);
int d = 1/0;
}

public int find() {
	System.out.println("商品查询数量了。。。。。");
	return 99;
}

}
测试结果:

⦁ 最终通知
在切面的类MyAspect.java类中添加通知方法
//最终通知
//拦截所有以ice结尾的bean
@After(“bean(*ice)”)
public void after(JoinPoint joinPoint){
System.out.println("+++++++++最终通知。。。。。。。");
}
使用SpringTest进行测试:
//springjunit集成测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations=“classpath:applicationContext.xml”)
public class SpringTest {
//注入要测试bean
@Autowired
private CustomerService customerService;
@Autowired
private ProductService productService;

//测试
@Test
public void test(){
	//基于接口
	customerService.save();
	customerService.find();
	
	//基于类的
	productService.save();
	productService.find();
}

}
测试不管是否抛出异常,都会执行最终通知。
@Service(“productService”)
public class ProductService {
public void save() {
System.out.println(“商品保存了。。。。。”);
int d = 1/0;
}

public int find() {
	System.out.println("商品查询数量了。。。。。");
	return 99;
}

}
测试结果:

【扩展补充】: 我们的aop代理是使用的Spring的内部代理机制,默认是如果有接口就优先对接口代理(jdk动态代理)。
问题:如果目标对象有接口,能否只对实现类代理,而不对接口进行代理呢?

【测试】
第一步:在CustomerServiceImpl的子类中添加一个新的方法update(),而接口中不要定义update()的方法:
@Service(“customerService”)
public class CustomerServiceImpl implements CustomerService{

public void save() {
	System.out.println("客户保存了。。。。。");
	
}

public int find() {
	System.out.println("客户查询数量了。。。。。");
	return 100;
}

//子类扩展方法
public void update(){
	System.out.println("客户更新了。。。新增方法。。。");
}

}
第二步:在测试类中调用子类的扩展方法:
//springjunit集成测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations=“classpath:applicationContext.xml”)
public class SpringTest {
//注入要测试bean
@Autowired
private CustomerService customerService;
@Autowired
private ProductService productService;

//测试
@Test
public void test(){
	//基于接口
	customerService.save();
	customerService.find();
	
	//基于类的
	productService.save();
	productService.find();
	
	//扩展方法执行:customerService是一个动态代理对象,原因,该对象是接口的子类型的对象
	((CustomerServiceImpl)customerService).update();
}

}
结果发现异常:

为什么会抛出异常呢?原因是代理的目标对象是接口,无法转换为子类。

设置 proxy-target-class = true
方案一:注解方式:

<aop:aspectj-autoproxy proxy-target-class="true"/>

方案二:配置文件XML的方式

<aop:config proxy-target-class="true">
</aop:config>

⦁ Spring JdbcTemplate的使用
Spring JdbcTemplate 是一个模板工具类,简化Jdbc编程 (类似 Apache DbUtils )
Spring对不同持久化技术的支持,Spring为各种支持的持久化技术,都提供了简单操作的模板和回调:
ORM持久化技术 模板类
JDBC org.springframework.jdbc.core.JdbcTemplate
Hibernate5.0 org.springframework.orm.hibernate5.HibernateTemplate
IBatis(MyBatis) org.springframework.orm.ibatis.SqlMapClientTemplate
JPA org.springframework.orm.jpa.JpaTemplate

⦁ JdbcTemplate 快速入门
第一步: 基础工程搭建:
新建项目 spring5_day04_jdbctemplate

第二步:引入依赖:


org.springframework
spring-context

	<!-- 单元测试 -->
	<dependency>
		<groupId>junit</groupId>
		<artifactId>junit</artifactId>
		<scope>test</scope>
	</dependency>

	<!-- 日志 -->
	<dependency>
		<groupId>org.slf4j</groupId>
		<artifactId>slf4j-log4j12</artifactId>
	</dependency>
	
	<!-- spring集成测试 -->
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-test</artifactId>
		</dependency>
	
	<!-- 操作数据库 -->
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-jdbc</artifactId>
	</dependency>

	<!-- MySql -->
	<dependency>
		<groupId>mysql</groupId>
		<artifactId>mysql-connector-java</artifactId>
	</dependency>

导入配置文件:在src中导入log4j.properties和applicationContiext.xml

第三步: 建立mysql数据库,创建itcastspring

第四步:使用JDBCTemplate编写程序(建表) ,基本步骤如下:
1)构建连接池
2)构建JDBCTemplate
3)调用JDBCTemplate的execute方法
使用mysql数据库,创建包cn.itcast.spring.test,创建测试类JdbcTemplateTest.java进行测试:
public class JdbcTemplateTest {

@Test
public void test(){
	//目标:使用jdbctemplate执行一段sql
	//1.构建数据源
	//spring内置了一个数据源
	DriverManagerDataSource dataSource = new DriverManagerDataSource();
	dataSource.setDriverClassName("com.mysql.jdbc.Driver");
	dataSource.setUrl("jdbc:mysql:///itcastspring");
	dataSource.setUsername("root");
	dataSource.setPassword("root");
	
	//2.创建jdbctemplate实例
	JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
	//等同于

// jdbcTemplate.setDataSource(dataSource)

	//3.执行sql,创建表test001
	jdbcTemplate.execute("create table test001(id int,name varchar(20))");
	
}

}
⦁ 通过XML配置创建JdbcTemplate对象(多种数据源)
下面将使用几种数据源的方式进行配置。
⦁ Spring内置数据源
目标:将数据源和jdbcTemplate都交给Spring来管理:
在applicationContext.xml中配置dataSource连接池和jdbcTemplate模版对象。编写applicationContext.xml文件

<!-- 配置内置的数据源bean -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
	<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
	<property name="url" value="jdbc:mysql:///itcastspring"/>
	<property name="username" value="root"/>
	<property name="password" value="root"/>
</bean>
<!-- jdbctemplate对象 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
	<!-- 注入数据源 -->
	<property name="dataSource" ref="dataSource"/>
</bean>

数据源:DriverManagerDataSource是spring内置的连接池,不建议生产环境使用,可以在测试环境使用
编写测试,使用SpringTest.java进行测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations=“classpath:applicationContext.xml”)
public class SpringTest {
//注入要测试bean
@Autowired
private JdbcTemplate jdbcTemplate;

@Test
public void testCreatetable(){
	jdbcTemplate.execute("create table test002(id int,name varchar(20))");
}

}
⦁ C3P0 连接池配置
引入c3p0依赖:


c3p0
c3p0
0.9.1.2

配置applicationContext.xml文件

<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
	<property name="driverClass" value="com.mysql.jdbc.Driver"/>
	<property name="jdbcUrl" value="jdbc:mysql:///itcastspring"/>
	<property name="user" value="root"/>
	<property name="password" value="root"/>
</bean>
<!-- jdbctemplate对象 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
	<!-- 注入数据源 -->
	<property name="dataSource" ref="dataSource"/>
</bean>

测试类:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations=“classpath:applicationContext.xml”)
public class SpringTest {
//注入要测试bean
@Autowired
private JdbcTemplate jdbcTemplate;

@Test
public void testCreatetable(){
	jdbcTemplate.execute("create table test003(id int,name varchar(20))");
}

}
⦁ 外部属性文件的配置
模拟需求:
现在数据源的相关参数配置,是测试环境下的。
现在,要将工程搭建在正式的服务器上,因为测试环境和正式环境的数据库肯定不是一个,所以肯定首先要更改数据源相关的配置。
缺点:必须手动修改applicationContext.xml文件,容易造成误操作。
解决方案:不修改。可以将数据源相关配置参数,外置。

目的:可以将xml配置中可能要经常修改内容,抽取到一个properties文件
应用:使用properties文件配置参数,如数据库连接参数等。

第一步: src新建db.properties
将经常需要修改变量抽取出来
jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql:///itcastspring
jdbc.username=root
jdbc.password=root
第二步: 配置applicationContext.xml文件,在applicationContext.xml 通过
context:property-placeholder 引入外部属性文件
通过${key} 引用属性的值

<context:property-placeholder location="classpath:db.properties"/> 

<!-- 配置内置的数据源bean,使用db.properties -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
	<property name="driverClassName" value="${jdbc.driverClass}"/>
	<property name="url" value="${jdbc.url}"/>
	<property name="username" value="${jdbc.username}"/>
	<property name="password" value="${jdbc.password}"/>
</bean>

第三步:使用SpringTest.java进行测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations=“classpath:applicationContext.xml”)
public class SpringTest {
//注入要测试bean
@Autowired
private JdbcTemplate jdbcTemplate;

@Test
public void testCreatetable(){
	jdbcTemplate.execute("create table test004(id int,name varchar(20))");
}

}

⦁ 基于JdbcTemplate实现DAO(CURD)
方案一: 在dao中注入jdbctemplate成员变量操作数据库

第一步:创建一个表book:
CREATE TABLE book (
id int(11) NOT NULL AUTO_INCREMENT,
name varchar(50) DEFAULT NULL,
price double DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

第二步:创建cn.itcast.spring.domain包,创建Book类,类中的属性用来对应book表的字段
//实体类
public class Book {

private Integer id;
private String name;
private Double price;

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 Double getPrice() {
	return price;
}
public void setPrice(Double price) {
	this.price = price;
}

public String toString() {
	return "Book [id=" + id + ", name=" + name + ", price=" + price + "]";
}

}

第三步:编写Dao类
package cn.itcast.spring.dao;

import org.springframework.jdbc.core.JdbcTemplate;

import cn.itcast.spring.domian.Book;

public class BookDao {

// 注入jdbctemplate
private JdbcTemplate jdbcTemplate;

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

// 添加图书
public void saveBook(Book book) {
	String sql = "insert into book values(null,?,?)";
	this.jdbcTemplate.update(sql, book.getName(), book.getPrice());
}

}

第四步: 在配置文件中配置dao并将jdbctemplate注入到dao对象中:

<context:property-placeholder location="classpath:jdbc.properties" />

<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
	<property name="driverClass" value="${driver}" />
	<property name="jdbcUrl" value="${url}" />
	<property name="user" value="${username}" />
	<property name="password" value="${password}" />
</bean>



<bean id="jdbctemplate" class="org.springframework.jdbc.core.JdbcTemplate">
	<property name="dataSource" ref="dataSource" />
</bean>


<bean id="bookDao" class="cn.itcast.spring.dao.BookDao">
	<property name="jdbcTemplate" ref="jdbctemplate"></property>
</bean>

第五步: 编写测试方法
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = “classpath:applicationContext.xml”)
public class SpringTest {

@Autowired
private BookDao bookDao;

@Test
public void test(){
	Book book = new Book();
	book.setName("从入门到精通");
	book.setPrice(99d);
	bookDao.saveBook(book);
}

}

方案二: 继承Spring框架封装的JdbcDaoSupport类获得jdbctemplate对象操作数据库

为了方便Dao中注入JdbcTemplate,Spring为每一个持久化技术都提供了支持类,如图

如果想编写DAO实现CURD,只需要继承Spring提供 JdbcDAOSupport支持类 ! 

源代码分析JdbcDaoSupport:不难发现,需要注入数据源

而且只要注入datasource,就有了jdbcTemplate,相当于也注入了jdbcTemplate

编写的Dao类继承JdbcDaoSupport
//图书操作的dao层
//JdbcDaoSupport简化JdbcTemplate的代码开发。
public class BookDao extends JdbcDaoSupport {

//注入jdbctempate

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

//保存图书
public void save(Book book){
	String sql="insert into book values(null,?,?)";
	//调用jdbctemplate

// jdbcTemplate.update(sql, book.getName(),book.getPrice());
super.getJdbcTemplate().update(sql, book.getName(),book.getPrice());
}
}
配置spring核心配置文件,注入jdbcTemplate到BookDao:

<bean id="bookDao" class="cn.itcast.spring.dao.BookDao">
	<!-- 方案一:在BookDao中提供jdbcTempate属性,通过set方法注入 jdbcTemplate-->
	<!-- <property name="jdbcTemplate" ref="jdbcTemplate"/> -->
	<!-- 方案二:BookDao类继承JdbcDaoSupport,直接注入数据源,就拥有了jdbctempate对象 -->
	<property name="dataSource" ref="dataSource"/>
</bean>

⦁ 实现增加、删除、修改功能
通过jdbcTemplate提供 update一个方法就可以 增删改
参看api文档:spring-framework-4.2.4.RELEASE/docs/javadoc-api/index.html

创建cn.itcast.spring.dao包,创建BookDao类
编写BookDao类:
//图书操作的dao层
//JdbcDaoSupport简化JdbcTemplate的代码开发。
public class BookDao extends JdbcDaoSupport {

//注入jdbctempate

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

//保存图书
public void save(Book book){
	String sql="insert into book values(null,?,?)";
	//调用jdbctemplate

// jdbcTemplate.update(sql, book.getName(),book.getPrice());
super.getJdbcTemplate().update(sql, book.getName(),book.getPrice());
}

//更新
public void update(Book book){
	String sql="update book set name =? ,price =? where id =?";
	super.getJdbcTemplate().update(sql, book.getName(),book.getPrice(),book.getId());
}

//删除
public void delete(Book book){
	super.getJdbcTemplate().update("delete from book where id =?", book.getId());
}

}
⦁ 查询
查询单个对象

查询集合

//根据id查询
public Book findById(Integer id){
String sql =“select * from book where id = ?”;
return super.getJdbcTemplate().queryForObject(sql,BeanPropertyRowMapper.newInstance(Book.class), id);
}

//查询所有

public List<Book> findAll( ){
	String sql ="select * from book";
	return super.getJdbcTemplate().query(sql,BeanPropertyRowMapper.newInstance(Book.class));
}

//条件查询: 根据图书名称模糊查询信息
public List<Book> findByNameLike(String name ){
	String sql ="select * from book where name like ?";
	return super.getJdbcTemplate().query(sql,BeanPropertyRowMapper.newInstance(Book.class),"%"+name+"%");
}

创建包cn.itcast.spring.test
创建SpringTest.java进行测试:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations=“classpath:applicationContext.xml”)
public class SpringTest {
//测试dao
@Autowired
private BookDao bookDao;

/**保存测试*/
@Test
public void testSave(){
	Book book = new Book();
	book.setName("如来神掌");
	book.setPrice(1998d);
	bookDao.save(book);
}

/**更新测试*/
@Test
public void testUpdate(){
	Book book = new Book();
	book.setId(1);
	book.setName("降龙十八掌");
	book.setPrice(298d);
	bookDao.update(book);
}

/**保存更新*/
@Test
public void testDelete(){
	Book book = new Book();
	book.setId(2);
	bookDao.delete(book);
}

/**使用主键ID查询测试*/
@Test
public void testFindById(){
	Integer id = 3;
	Book book = bookDao.findById(id);
	System.out.println(book);
}


/**查询测试*/
@Test
public void testFindAll(){
	List<Book> list = bookDao.findAll();
	System.out.println(list);
}

/**查询条件查询测试*/
@Test
public void testFindCondition(){
	Book book = new Book();
	book.setName("如来神掌");
	book.setPrice(1998d);
	List<Book> list = bookDao.findByCondition(book);
	System.out.println(list);
}

}

⦁ Spring的事务管理机制
Spring事务管理高层抽象主要包括3个接口,Spring的事务主要是由他们共同完成的:
⦁ PlatformTransactionManager:事务管理器—主要用于平台相关事务的管理
⦁ TransactionDefinition: 事务定义信息(隔离、传播、超时、只读)—通过配置如何进行事务管理。
⦁ TransactionStatus:事务具体运行状态—事务管理过程中,每个时间点事务的状态信息。
⦁ PlatformTransactionManager事务管理器
参考:spring-framework-4.2.4.RELEASE/docs/javadoc-api/index.html

该接口提供三个方法:
⦁ commit:提交事务
⦁ rollback:回滚事务
⦁ getTransaction:获取事务状态

Spring为不同的持久化框架提供了不同PlatformTransactionManager接口实现:
事务 说明
org.springframework.jdbc.datasource.DataSourceTransactionManager 使用Spring JDBC或iBatis 进行持久化数据时使用
org.springframework.orm.hibernate5.HibernateTransactionManager 使用Hibernate5.0版本进行持久化数据时使用
org.springframework.orm.jpa.JpaTransactionManager 使用JPA进行持久化时使用
org.springframework.jdo.JdoTransactionManager 当持久化机制是Jdo时使用
org.springframework.transaction.jta.JtaTransactionManager 使用一个JTA实现来管理事务,在一个事务跨越多个资源时必须使用

⦁ DataSourceTransactionManager针对JdbcTemplate、MyBatis 事务控制 ,使用Connection(连接)进行事务控制 :
开启事务 connection.setAutoCommit(false);
提交事务 connection.commit();
回滚事务 connection.rollback();

事务管理器的选择?
用户根据选择和使用的持久层技术,来选择对应的事务管理器。

⦁ TransactionDefinition事务定义信息
用来定义事务相关的属性的,给事务管理器用。
参考:spring-framework-4.2.4.RELEASE/docs/javadoc-api/index.html

该接口主要提供的方法:
⦁ getIsolationLevel:隔离级别获取
⦁ getPropagationBehavior:传播行为获取
⦁ getTimeout:获取超时时间(事务的有效期)
⦁ isReadOnly 是否只读(保存、更新、删除—对数据进行操作-变成可读写的,查询-设置这个属性为true,只能读不能写),事务管理器能够根据这个返回值进行优化。

这些事务的定义信息,都可以在配置文件中配置和定制。

⦁ 事务的隔离级别IsolationLevel
隔离级别 含义
DEFAULT 使用后端数据库默认的隔离级别(spring中的的选择项)
READ_UNCOMMITED 允许你读取还未提交的改变了的数据。可能导致脏、幻、不可重复读
READ_COMMITTED 允许在并发事务已经提交后读取。可防止脏读,但幻读和 不可重复读仍可发生
REPEATABLE_READ 对相同字段的多次读取是一致的,除非数据被事务本身改变。可防止脏、不可重复读,但幻读仍可能发生。
SERIALIZABLE 完全服从ACID的隔离级别,确保不发生脏、幻、不可重复读。这在所有的隔离级别中是最慢的,它是典型的通过完全锁定在事务中涉及的数据表来完成的。

脏读:一个事务读取了另一个事务改写但还未提交的数据,如果这些数据被回滚,则读到的数据是无效的。
不可重复读:在同一事务中,多次读取同一数据返回的结果有所不同。换句话说就是,后续读取可以读到另一事务已提交的更新数据。相反,“可重复读”在同一事务中多次读取数据时,能够保证所读数据一样,也就是,后续读取不能读到另一事务已提交的更新数据。
幻读:一个事务读取了几行记录后,另一个事务插入一些记录,幻读就发生了。再后来的查询中,第一个事务就会发现有些原来没有的记录。
事务四大特性 ACID —隔离性引发问题 ---- 解决事务的隔离问题 隔离级别
Mysql 默认隔离级别 REPEATABLE_READ
Oracle 默认隔离级别 READ_COMMITTED

⦁ 事务的传播行为PropagationBehavior
什么是事务的传播行为? 有什么作用?
事务传播行为用于解决两个被事务管理的方法互相调用问题

业务层两个方法面临的事务问题:
* 有些时候需要处于同一个事务(删除用户删除完成之后,需要同时删除用户对应的订单,需要事务回滚,例如商场工作人员删除订单业务),

  • 有些时候不能在同一个事务(取款是一个事务操作,打印凭条是一个事务操作,例如ATM取款业务) !

事务的传播行为的7种类型:
事务传播行为类型 说明
PROPAGATION_REQUIRED 支持当前事务,如果不存在 就新建一个
PROPAGATION_SUPPORTS 支持当前事务,如果不存在,就不使用事务
PROPAGATION_MANDATORY 支持当前事务,如果不存在,抛出异常
PROPAGATION_REQUIRES_NEW 如果有事务存在,挂起当前事务,创建一个新的事务
PROPAGATION_NOT_SUPPORTED 以非事务方式运行,如果有事务存在,挂起当前事务
PROPAGATION_NEVER 以非事务方式运行,如果有事务存在,抛出异常
PROPAGATION_NESTED 如果当前事务存在,则嵌套事务执行
只对DataSourceTransactionManager 起效

主要分为三大类:

//开启事务
A
//提交事务

//开启事务
B
//提交事务

⦁ PROPAGATION_REQUIRED(默认值)、PROPAGATION_SUPPORTS、PROPAGATION_MANDATORY
支持当前事务, A调用B,如果A事务存在,B和A处于同一个事务 。
事务默认传播行为 REQUIRED。最常用的。

⦁ PROPAGATION_REQUIRES_NEW、PROPAGATION_NOT_SUPPORTED、PROPAGATION_NEVER
不会支持原来的事务 ,A调用B, 如果A事务存在, B肯定不会和A处于同一个事务。
常用的事务传播行为:PROPAGATION_REQUIRES_NEW

⦁ PROPAGATION_NESTED
嵌套事务 ,只对DataSourceTransactionManager有效 ,底层使用JDBC的SavePoint机制,允许在同一个事务设置保存点,回滚保存点

附录:嵌套事务的示例:
Connection conn = null;
try {
conn.setAutoCommit(false);
Statement stmt = conn.createStatement();
stmt.executeUpdate(“update person set name=‘888’ where id=1”);

Savepoint savepoint = conn.setSavepoint();
try{   
        conn.createStatement().executeUpdate("update person set name='222' where sid=2");
}catch(Exception ex){
        conn.rollback(savepoint);    
 }
  stmt.executeUpdate("delete from person where id=9");
  conn.commit();
   stmt.close();
} catch (Exception e) {
     conn.rollback();
 }finally{
           try {
    if(null!=conn && !conn.isClosed()) conn.close();
            } catch (SQLException e) { e.printStackTrace(); }
 }

}

【面试题】REQUIRED、REQUIRES_NEW、NESTED区分
REQUIRED:只有一个事务(默认,推荐)
REQUIRES_NEW:存在两个事务 ,如果事务存在,挂起事务,重新又开启了一个新的事务
NESTED 嵌套事务,事务可以设置保存点,回滚到保存点 ,选择提交或者回滚

⦁ TransactionStatus 事务状态
事务运行过程中,每个时间点 事务状态信息 !

flush(),给hibernate使用,底层发出sql的
hasSavepoint():判断是否有保留点
isCompleted():判断事务是否结束
isNewTransaction():判断当前事务是否是新开的一个事务。
isRollbackOnly():判断事务是否只能回滚
setRollbackOnly():设置事务是否回滚

事务的结束:必须通过commit 确认事务提交, rollback 作用标记为回滚。
数据库操作中,如果只是回滚,后面不操作,数据库在关闭连接的时候,自动发出了commit。
try {
操作
} catch (){
rollback
} finally {
commit
}

【三个事务超级接口对象之间的关系】
1)首先用户管理事务,需要先配置TransactionManager(事务管理器)进行事务管理
2)然后根据TransactionDefinition(事务定义信息),通过TransactionManager(事务管理器)进行事务管理;
3)最后事务运行过程中,每个时刻都可以通过获取TransactionStatus(事务状态)来了解事务的运行状态。

⦁ Spring事务管理两种方式
Spring 支持两种方式事务管理
⦁ 一:编程式的事务管理
通过TransactionTemplate手动管理事务
在实际应用中很少使用,原因是要修改原来的代码,加入事务管理代码 (侵入性 )
参考文档:http://www.yiibai.com/spring/programmatic_management.html

⦁ 二:使用XML或注解配置声明式事务
* Spring的声明式事务是通过AOP实现的(环绕通知)
*开发中经常使用(代码侵入性最小)–推荐使用!

⦁ 声明式事务管理案例-转账(xml、注解)
⦁ 编写转账案例,引出事务管理问题
需求:账号转账,Tom账号取出1000元,存放到Jack账号上
数据表和测试数据准备:

建表脚本(MySQL):
第一步:创建表t_account
CREATE TABLE t_account (
id int(11) NOT NULL AUTO_INCREMENT,
name varchar(50) DEFAULT NULL,
money double DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;
第二步:插入测试数据:
INSERT INTO itcastspring.t_account (id, name, money) VALUES (‘1’, ‘tom’, ‘1000’);
INSERT INTO itcastspring.t_account (id, name, money) VALUES (‘2’, ‘jack’, ‘1100’);
INSERT INTO itcastspring.t_account (id, name, money) VALUES (‘3’, ‘rose’, ‘1200’);

第一步:新建工程,spring5_day04_transaction

第二步:引入依赖:

	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-context</artifactId>
	</dependency>
	
	<!-- springaop相关包 -->
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-aspects</artifactId>
	</dependency>

	<!-- 单元测试 -->
	<dependency>
		<groupId>junit</groupId>
		<artifactId>junit</artifactId>
		<scope>test</scope>
	</dependency>

	<!-- 日志 -->
	<dependency>
		<groupId>org.slf4j</groupId>
		<artifactId>slf4j-log4j12</artifactId>
	</dependency>
	
	<!-- spring集成测试 -->
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-test</artifactId>
		<version>4.3.13.RELEASE</version>
	</dependency>
	
	<!-- 操作数据库 -->
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-jdbc</artifactId>
	</dependency>

	<!-- MySql -->
	<dependency>
		<groupId>mysql</groupId>
		<artifactId>mysql-connector-java</artifactId>
	</dependency>

	<!-- c3p0数据源 -->
	<dependency>
		<groupId>c3p0</groupId>
		<artifactId>c3p0</artifactId>
		<version>0.9.1.2</version>
	</dependency>

建包: cn.itcast.spring.dao
第三步:创建IAccountDao接口
public interface IAccountDao {

//(存入)转入
public void in(String name,Double money);

//(取出)转出
public void out(String name,Double money);

}
创建AccounDaoImpl实现类,实现了IAccountDao接口
//账户操作持久层
//技术方案:jdbctempate
public class AccountDaoImpl extends JdbcDaoSupport implements IAccountDao {

//(存入)转入
public void in(String name,Double money){
	String sql="update t_account set money = money+ ? where name = ?";
	super.getJdbcTemplate().update(sql, money,name);
}

//(取出)转出
public void out(String name,Double money){
	String sql="update t_account set money = money- ? where name = ?";
	super.getJdbcTemplate().update(sql, money,name);
}

}

第四步:建立service层,创建IAccountService接口,编写转账的业务代码:
建包: cn.itcast.spring.service

public interface IAccountService {

void transfer(String outName,String inName,Double money);

}

创建AccountServiceImpl实现类,实现了IAccountService接口,编写转账的业务操作
//掌握操作的业务层
public class AccountServiceImpl implements IAccountService{

//注入dao
private IAccountDao accountDao;

public void setAccountDao(IAccountDao accountDao) {
	this.accountDao = accountDao;
}


//转账操作的业务逻辑
public void transfer(String outName,String inName,Double money){
	//调用dao层
	//先取出
	accountDao.out(outName, money);
	//再转入
	accountDao.in(inName, money);
}

}
配置applicationContext.xml:

	 <bean id="accountDAO" class="cn.itcast.spring.dao.AccountDAOImpl">
	 	<property name="dataSource" ref="dataSource" />
	 </bean>
	 
	 <!-- service -->
	 <bean id="accountService" class="cn.itcast.spring.dao.AccountServiceImpl">
	 	<property name="accountDAO" ref="accountDAO" />
	 </bean>

第五步:使用SpringTest进行测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={“classpath:applicationContext.xml”})
public class SpringTest {
//注入测试的service
@Autowired
private IAccountService accountService;

//需求:账号转账,Tom账号取出1000元,存放到Jack账号上
@Test
public void testTransfer(){
	accountService.transfer("Tom", "Jack", 1000d);
	System.out.println("转账成功!");
}

}
但是发现问题:
事务管理问题:在Service层没有事务的情况下,如果出现异常,则会转账不成功,数据异常。
如果不配置事务,那么每一个数据库的操作都是单独的一个事务。

⦁ XML配置方式添加事务管理(tx、aop元素)
【操作思路】:
1、 确定目标:需要对AccountService 的 transfer方法,配置切入点
2、 需要Advice (环绕通知),方法前开启事务,方法后提交关闭事务
3、 配置切面和切入点

第一步:

配置Advice通知:
Spring为简化事务的配置,提供了tx:advice来配置事务管理,也可以理解为该标签是spring为你实现好了的事务的通知增强方案。

第二步:配置spring容器,applicationContext.xml文件

<!-- 引入外部属性配置文件-->
<context:property-placeholder location="classpath:db.properties"/> 

 <!-- 配置数据源 -->
<!-- c3p0连接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
	<property name="driverClass" value="${jdbc.className}" />
	<property name="jdbcUrl" value="${jdbc.url}" />
	<property name="user" value="${jdbc.user}" />
	<property name="password" value="${jdbc.password}" />
</bean>


<!-- 第一步:定义具体的平台事务管理器(DataSource事务管理器) -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
	<!-- 注入数据源 -->
	<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 第二步:定义通知,通知中要处理的就是事务 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
	<!-- 配置事务的属性定义 -->
	<tx:attributes>
		<!-- 配置具体的方法的事务属性
		isolation//事务的隔离级别,默认是按数据库的隔离级别来
		propagation//事务的传播行为,默认是同一个事务
		timeout="-1":事务的超时时间,默认值使用数据库的超时时间。
		read-only="false":事务是否只读,默认可读写。
		rollback-for:遇到哪些异常就回滚,其他的都不回滚
		no-rollback-for:遇到哪些异常不回滚,其他的都回滚。和上面互斥的
		 -->
		<tx:method name="transfer" isolation="DEFAULT"	propagation="REQUIRED" timeout="-1" read-only="false"/>
		
		<!-- 支持通配符
			要求service中 方法名字必须符合下面的规则
		 -->
		<tx:method name="save*"/>
		<tx:method name="update*"/>
		<tx:method name="delete*"/>
		<tx:method name="find*" read-only="true"/>
	</tx:attributes>
</tx:advice>
<!-- 第三步:配置切入点,让通知关联切入点,即事务控制业务层的方法 -->
<aop:config>
	<!-- 切入点 -->
	<aop:pointcut expression="execution(* cn.itcast.spring.service..*.*(..))" id="txPointcut"/>
	<!-- 切面 -->
	<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>

<!-- dao -->
<bean id="accountDao" class="cn.itcast.spring.dao.AccountDaoImpl">
	<!-- 注入数据源,才拥有jdbctemplate -->
	<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 业务层 -->
<bean id="accountService" class="cn.itcast.spring.service.AccountServiceImpl">
	<!-- 注入dao -->
	<property name="accountDao" ref="accountDao"/>
</bean>

使用SpringTest.java测试:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={“classpath:applicationContext.xml”})
public class SpringTest {
//注入测试的service
@Autowired
private IAccountService accountService;

//需求:账号转账,Tom账号取出1000元,存放到Jack账号上
@Test
public void testTransfer(){
	accountService.transfer("Tom", "Jack", 1000d);
	System.out.println("转账成功!");
}

}
数据正常!

【声明式事务处理的原理图】
没有添加事务:

添加事务:

【注意】
如果不配置,则走默认的事务(默认事务是每个数据库操作都是一个事务,相当于没事务),所以我们开发时需要配置事务。

⦁ 注解配置方式添加事务管理 @Transactional
参考之前的项目代码创建项目spring5_day04_transaction_anno

步骤:
1.在需要管理事务的方法或者类上面 添加@Transactional 注解
2.配置注解驱动事务管理(事务管理注解生效的作用)(需要配置对特定持久层框架使用的事务管理器)

第一步:确定目标(bean的方法):

  1. IAccountDao.java接口
    public interface IAccountDao {

    //(存入)转入
    public void in(String name,Double money);

    //(取出)转出
    public void out(String name,Double money);
    }
    (2)AccountDaoImpl.java类
    //账户操作持久层
    //技术方案:jdbctempate
    /**

  • @Repository(“accountDao”)

  • 相当于容易中定义
    */
    @Repository(“accountDao”)
    public class AccountDaoImpl extends JdbcDaoSupport implements IAccountDao {

    //注入数据源
    @Autowired
    //private DataSource dataSource;//没有注入数据源成功~
    原理:放到属性上的的注解相当于,自动生成setter方法上加注解
    //@Autowired //自动到spring的容器中寻找类型是参数类型(DataSource)的bean
    //public void setDataSource(DataSource dataSource){
    // this.dataSource=dataSource;
    //}

    @Autowired//当初始化dao的时候,会调用该方法啊,通过set方法的形参注入数据源
    //方法名无所谓
    public void setSuperDataSource(DataSource dataSource){
    //调用父类的方法
    super.setDataSource(dataSource);
    }

    //(存入)转入
    public void in(String name,Double money){
    String sql=“update t_account set money = money+ ? where name = ?”;
    super.getJdbcTemplate().update(sql, money,name);
    }

    //(取出)转出
    public void out(String name,Double money){
    String sql=“update t_account set money = money- ? where name = ?”;
    super.getJdbcTemplate().update(sql, money,name);
    }
    }

  1. IAccountService接口
    public interface IAccountService {

    void transfer(String outName,String inName,Double money);

}

  1. AccountServiceImpl类
    //掌握操作的业务层
    /**
  • @Service(“accountService”)

  • 相当于spring容器中定义:
    */
    @Service(“accountService”)
    @Transactional//会对该类中,所有的共有的方法,自动加上事务–全局的设置,默认是可写
    public class AccountServiceImpl implements IAccountService{

    //注入dao
    @Autowired
    private IAccountDao accountDao;

    //转账操作的业务逻辑
    // @Transactional//在方法上添加事务
    public void transfer(String outName,String inName,Double money){

     //调用dao层
     //先取出
     accountDao.out(outName, money);
     int d = 1/0;
     //再转入
     accountDao.in(inName, money);
    

    }

    @Transactional(readOnly=true)//使用局部覆盖全局的
    public void findAccount(){
    System.out.println(“查询帐号的信息了”);
    }

}
第二步:创建applicationContext.xml在applicationContext.xml中配置:

<?xml version="1.0" encoding="UTF-8"?>

<!-- 引入外部属性文件 -->
<context:property-placeholder location="classpath:db.properties" />
<!-- 配置数据源 -->
<!-- c3p0连接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
	<property name="driverClass" value="${jdbc.className}" />
	<property name="jdbcUrl" value="${jdbc.url}" />
	<property name="user" value="${jdbc.user}" />
	<property name="password" value="${jdbc.password}" />
</bean>	
<!-- 配置bean注解扫描 -->
<context:component-scan base-package="cn.itcast.spring "/>

<!-- 定义具体的平台事务管理器(DataSource事务管理器) -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
	<!-- 注入数据源 -->
	<property name="dataSource" ref="dataSource"/>
</bean>


<!-- 配置事务注解驱动 :识别事务的注解@tr。。。
transaction-manager:具体的平台事务管理器
-->
<!-- <tx:annotation-driven transaction-manager="transactionManager"/> -->
<!-- 默认的平台事务管理器的名字叫:transactionManager,此时transaction-manager="transactionManager"可以不写 -->
<tx:annotation-driven transaction-manager="transactionManager"/>

⦁ 小结-xml和注解的选择
XML配置方式和注解配置方式 进行事务管理 哪种用的多?
XML方式,集中式维护,统一放置到applicationContext.xml文件中,缺点在于配置文件中的内容太多。
使用@Transactional注解进行事务管理,配置太分散,使用XML进行事务管理,属性集中配置,便于管理和维护

注意:以后的service的方法名字的命名,必须是上面规则,否则,不能被spring事务管理。!!!!
即以save开头的方法,update开头的方法,delete开头的方法,表示增删改的操作,故事务为可写
以find开头的方法,表示查询,故事务为只读
(1)xml方式小结

(2)注解方式小结

作业:
⦁ JdbcTemplate 实现 CURD –大家要都用一遍
⦁ 声明式事务管理原理 (转账)(xml和注解)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值