概述
Spring第一天
- Spring概述
1.1程序的耦合和解耦
-
-
- 什么是程序的耦合
-
我们在开发中,会写很多的类,而有些类之间不可避免的产生依赖关系,这种依赖关系称之为耦合。有些依赖关系是必须的,有些依赖关系可以通过优化代码来解除的。请看下面的示例代码:
/** * 客户的业务层实现类依赖customerDao类,这两个类就称为耦合 */ public class CustomerServiceImpl implements ICustomerService { private ICustomerDao customerDao = new CustomerDaoImpl(); } |
上面的代码表示:业务层调用持久层,并且此时业务层在依赖持久层的接口和实现类。如果此时没有持久层实现类,编译将不能通过。这种依赖关系就是我们可以通过优化代码解决的。
再比如:
下面的代码中,我们的类依赖了 MySQL 的具体驱动类,如果这时候更换了数据库品牌,我们需要改源码来修改数据库驱动。这显然不是我们想要的。
public class JdbcDemo1 { /** * JDBC 操作数据库的基本入门中存在什么问题? * 导致驱动注册两次是个问题,但不是严重的。 * 严重的问题:是当前类和 mysql 的驱动类有很强的依赖关系。 * 当我们没有驱动类的时候,连编译都不让。 * 那这种依赖关系,就叫做程序的耦合 * * 我们在开发中,理想的状态应该是: * 我们应该尽力达到的:编译时不依赖,运行时才依赖。 */ public static void main(String[] args) throws Exception { //1.注册驱动 //DriverManager.registerDriver(new com.mysql.jdbc.Driver()); Class.forName("com.mysql.jdbc.Driver"); //2.获取连接 //3.获取预处理 sql 语句对象 //4.获取结果集 //5.遍历结果集 } |
-
-
- 解决程序耦合的思路
-
当是我们讲解 jdbc 时,是通过反射来注册驱动的,代码如下:
Class.forName("com.mysql.jdbc.Driver");
这时的好处是,我们的类中不再依赖具体的驱动类,此时就算删除 mysql 的驱动 jar 包,依然可以编译。但是因为没有驱动类,所以不能运行。
不过,此处也有个问题,就是我们反射类对象的全限定类名字符串是在 java 类中写死的,一旦要改还是要修改源码。
解决这个问题也很简单,使用配置文件配置。
-
-
- 工厂模式解耦
-
在实际开发中我们可以把所有的 dao mapper 和 service 和 action web controller对象使用配置文件配置起来,当启动服务器应用加载的时候,通过读取配置文件,把这些对象创建出来 并存起来。在接下来的使用的时候,直接拿过来用就好了。
1.1.4 控制反转-Inversion Of Control
1 、存哪去?
分析:由于我们是很多对象,肯定要找个集合来存。这时候有 Map 和 List 供选择。
到底选 Map 还是 List 就看我们有没有查找需求。有查找需求,选 Map。
所以我们的答案就是
在应用加载时,创建一个 Map,用于存放 action,Service 和 dao 对象。
我们把这个 map 称之为 容器。
2、还是没解释什么是工厂?
工厂就是负责给我们从容器中获取指定对象的类。这时候我们获取对象的方式发生了改变。
原来:
我们在获取对象时,都是采用 new 的方式。是主动的。
现在:
我们获取对象时,同时跟工厂要,有工厂为我们查找或者创建对象。是被动的。
这种被动接收的方式获取对象的思想就是控制反转,它是 spring 框架的核心之一。
它的作用只有一个:削减计算机程序的耦合。
-
- Spring介绍
- Spring 概述
- Spring介绍
Spring框架有两大核心 分别是IOC(控制反转)和AOP(面向切面编程)
1.2.1.1 Spring简介
Spring 是分层的 Java SE/EE 应用 full-stack 轻量级开源框架,以 IoC(Inverse Of Control:
反转控制)和 AOP(Aspect Oriented Programming:面向切面编程)为内核,提供了展现层 SpringMVC 和持久层 Spring JDBC 以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多著名的第三方框架和类库,逐渐成为使用最多的 Java EE 企业应用开源框架。
1.2.1.2 Spring的发展历程
1997 年 IBM 提出了 EJB 的思想
1998 年,SUN 制定开发标准规范 EJB1.0
1999 年,EJB1.1 发布
2001 年,EJB2.0 发布
2003 年,EJB2.1 发布
2006 年,EJB3.0 发布
Rod Johnson (spring 之父)
Expert One-to-One J2EE Design and Development(2002)
阐述了 J2EE 使用 EJB 开发设计的优点及解决方案
Expert One-to-One J2EE Development without EJB(2004)
阐述了 J2EE 开发不使用 EJB 的解决方式(Spring 雏形)
-
-
- Spring的优势
-
方便解耦,简化开发
通过 Spring 提供的 IoC 容器,可以将对象间的依赖关系交由 Spring 进行控制,避免硬编码所造成的过度程序耦合。用户也不必再为单例模式类、属性文件解析等这些很底层的需求编写代码,可以更专注于上层的应用。
AOP 编程的支持
通过 Spring 的 AOP 功能,方便进行面向切面的编程,许多不容易用传统 OOP 实现的功能可以通过 AOP 轻松应付。
声明式事务的支持
可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活的进行事务的管理,提高开发效率和质量。
方便程序的测试
可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随手可做的事情。
方便集成各种优秀框架
Spring 可以降低各种框架的使用难度,提供了对各种优秀框架(Struts、Hibernate、Hessian、Quartz 任务调度框架 等)的直接支持。低 降低 JavaEE API 的使用难度Spring 对 JavaEE API(如 JDBC、JavaMail、远程调用 CXF 等)进行了薄薄的封装层,使这些 API 的使用难度大为降低。
Java 源码是经典学习范例
Spring 的源代码设计精妙、结构清晰、匠心独用,处处体现着大师对 Java 设计模式灵活运用以及对 Java 技术的高深造诣。它的源代码无意是 Java 技术的最佳实践的范例。
-
-
- Spring的体系结构
-
-
-
- Spring开发包
-
官网:http://spring.io/
下载地址:
http://repo.springsource.org/libs-release-local/org/springframework/spring
解压:(Spring 目录结构:)
* docs :API 和开发规范.
* libs :jar 包和源码.
* schema :约束.
我们上课使用的版本是Spring 4.2.4
- 使用Spring的IOC解决程序耦合
2.1案例的前期准备
本章我们使用的案例是,客户的业务层和持久层的依赖关系解决。在开始 spring 的配置之前,我们要先准备一下环境。由于我们是使用 spring 解决依赖关系,并不是真正的要做增删改查操作,所以此时我们没必要写实体类。并且我们在此处使用的是 java 工程,不是 java web 工程。
2.1.1 创建业务层接口和实现类
客户接口: CustomerService
/** * 客户的业务层接口 */ public interface ICustomerService { /** * 保存客户 * @param customer */ void saveCustomer(); } |
客户的实现类 CustomerServiceImpl:
/** * 客户的业务层实现类 */ public class CustomerServiceImpl implements ICustomerService { private ICustomerDao customerDao = new CustomerDaoImpl();// 此处有依赖关系 @Override public void saveCustomer() { customerDao.saveCustomer(); } } |
2.1.2 创建持久层接口和实现类
客户的持久层接口:
public interface ICustomerDao { /** * 保存客户 */ void saveCustomer(); }
|
客户的持久层实现类:
public class CustomerDaoImpl implements ICustomerDao { @Override public void saveCustomer() { System.out.println("保存了客户"); } }
|
2.2 基于XML的配置
2.2.1 环境搭建
2.2.1.1 第一步: 拷贝必备的jar包到工程的lib目录
2.2.1.2 第二步:在类的根路径下创建一个任意名称的 xml 文件 (不能是中文)
给配置文件导入约束:
<?xml version="1.0" encoding="UTF-8"?> <!-- 导入 schema 约束的位置在: ..spring-framework-4.2.4.RELEASEdocsspring-framework-referencehtmlxsd-configuration.html 文件中。注意:要导入 schema 约束 --> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> </beans> |
2.2.1.3 第三步:把资源交给 spring 来管理置 ,在配置文件中配置 service 和 dao
<!-- 把资源交给 spring 来管理 --> <bean id="customerDao" class="com.bdqn.dao.impl.CustomerDaoImpl"/> <bean id="customerService" class="com.bdqn.service.impl.CustomerServiceImpl"/> |
2.2.2 测试环境是否成功
2.2.2.1 获取 spring 容器
/** * 模拟一个表现层 */ public class Client { /** * 获取 spring 的容器,以及从容器中获取 bean 对象 * * ApplicationContext * 它是一个接口,有两个实现类。 * 分别是: * ClassPathXmlApplicationContext: * 它永远都是从类的根路径下加载配置文件 推荐使用这种 * FileSystemXmlApplicationContext: * 它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置。 */ public static void main(String[] args) { //1.使用 ApplicationContext 接口,就是在获取 spring 容器 ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); //2.根据 bean 的 id 获取对象 ICustomerService customerService = (ICustomerService) ac.getBean("customerService"); System.out.println(customerService); ICustomerDao customerDao = (ICustomerDao) ac.getBean("customerDao"); System.out.println(customerDao); } } 问题:由于对象都交给 spring 管理了,这时如果我们把 service 实现了的这行代码: private ICustomerDao customerDao = new CustomerDaoImpl();// 此处有依赖关系 改为 private ICustomerDao customerDao = null; 使用 service 对象调用 saveCustomer 方法会出现什么问题呢? 运行结果: 空指针异常。 原因是 customerDao 对象没有赋值。 如果在程序运行期间,spring 框架能帮我们给 customerDao 对象赋了值,那该多好。 带着脑海里的这个想法,继续往下看。 |
2.2.2.2 Spring工厂类结构
BeanFactory 才是 Spring 容器中的顶层接口。ApplicationContext 是它的子接口。
BeanFactory 和 ApplicationContext 的区别:创建对象的时间点不一样。
ApplicationContext:只要一读取配置文件,默认情况下就会创建对象。
BeanFactory:什么时候对象使用什么时候创建对象。
2.2.3 IOC 中 中 bean 标签 和 管理对象细节
2.2.3.1 bean 标签
作用:
用于配置对象让 spring 来创建的。
默认情况下它调用的是类中的无参构造函数。如果没有无参构造函数则不能创建成功。
属性:
id :给对象在容器中提供一个唯一标识。用于获取对象。
class :指定类的全限定类名。用于反射创建对象。默认情况下调用无参构造函数。
scope :指定对象的作用范围。
* singleton :默认值,单例的.
* prototype :多例的.
* request :WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 request 域中.
* session :WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 session 域中.
init-method :指定类中的初始化方法名称。
destroy-method :指定类中销毁方法名称。
2.2.3.2 bean 的作用范围和生命周期
单例对象: scope="singleton"
一个应用只有一个对象的实例。它的作用范围就是整个引用。
生命周期:
对象出生:当应用加载,创建容器时,对象就被创建了。
对象活着:只要容器在,对象一直活着。
对象死亡:当应用卸载,销毁容器时,对象就被销毁了。
多例对象: scope="prototype"
每次访问对象时,都会重新创建对象实例。
生命周期:
对象出生:当使用对象时,创建新的对象实例。
对象活着:只要对象在使用中,就一直活着。
对象死亡:当对象长时间不用时,被 java 的垃圾回收器回收了。
2.2.4 spring 的依赖注入
2.2.4.1 依赖注入的概念
它是 spring 框架核心 ioc 的具体实现方式。简单的说,就是坐等框架把对象传入,而不用我们自己去获取.
2.2.4.2 构造函数注入
顾名思义,就是使用类中的构造函数,给成员变量赋值。注意,赋值的操作不是我们自己做的,而是通过配置的方式,让 spring 框架来为我们注入。具体代码如下:
public class CustomerServiceImpl implements ICustomerService { private String name; private Integer age; private Date birthday; public CustomerServiceImpl(String name, Integer age, Date birthday) { this.name = name; this.age = age; this.birthday = birthday; } @Override public void saveCustomer() { System.out.println(name+","+age+","+birthday); } } <!-- 使用构造函数的方式,给 service 中的属性传值 要求: 类中需要提供一个对应参数列表的构造函数。 涉及的标签: constructor-arg 属性: index:指定参数在构造函数参数列表的索引位置 type:指定参数在构造函数中的数据类型 name:指定参数在构造函数中的名称 用这个找给谁赋值 =======上面三个都是找给谁赋值,下面两个指的是赋什么值的============== value:它能赋的值是基本数据类型和 String 类型 ref:它能赋的值是其他 bean 类型,也就是说,必须得是在配置文件中配置过的 bean --> <bean id="customerService" class="com.bdqn.service.impl.CustomerServiceImpl"> <constructor-arg name="name" value=" 张三 "></constructor-arg> <constructor-arg name="age" value="18"></constructor-arg> <constructor-arg name="birthday" ref="now"></constructor-arg> </bean> <bean id="now" class="java.util.Date"></bean> |
2.2.4.3 Set方法注入(重点)
顾名思义,就是在类中提供需要注入成员的 set 方法。具体代码如下:
public class CustomerServiceImpl implements ICustomerService { private String name; private Integer age; private Date birthday; public void setName(String name) { this.name = name; } public void setAge(Integer age) { this.age = age; } public void setBirthday(Date birthday) { this.birthday = birthday; } @Override public void saveCustomer() { System.out.println(name+","+age+","+birthday); } } <!-- 通过配置文件给 bean 中的属性传值:使用 set 方法的方式 涉及的标签: property 属性: name:找的是类中 set 方法后面的部分 ref:给属性赋值是其他 bean 类型的 value:给属性赋值是基本数据类型和 string 类型的 实际开发中,此种方式用的较多。 --> <bean id="customerService" class="com.bdqn.service.impl.CustomerServiceImpl"> <property name="name" value="test"></property> <property name="age" value="21"></property> <property name="birthday" ref="now"></property> </bean> <bean id="now" class="java.util.Date"></bean>
Spring 第 二 天第1 章 基于注解的 IOC 配置1.1 写在最前学习基于注解的 IoC 配置,大家脑海里首先得有一个认知,即注解配置和 xml 配置要实现的功能都是一样的,都是要降低程序间的耦合。只是配置的形式不一样。关于实际的开发中到底使用 xml 还是注解,每家公司有着不同的使用习惯。所以这两种配置方式我们都需要掌握。 1.2 环境搭建1.2.1 第一步:拷贝必备 jar 包到工程的 lib 目录。注意:在基于注解的配置中,我们还要多拷贝一个 aop 的 jar 包。如下图: 1.2.2 第二步:在类的根路径下创建一个任意名称的 xml 文件 (不能是中文)基于注解整合时,导入约束时需要多导入一个 context 名称空间下的约束.
给配置文件导入 context 约束:
1.2.3 使用@Component 注解配置管理的资源
1.2.4 第四步在 spring 的配置文件中开启 spring 对注解 ioc 的支持
1.3 常用注解1.3.1 用于创建对象的相当于:<bean id="" class=""> 1.3.1.1 @Component 作用: 把资源让 spring 来管理。相当于在 xml 中配置一个 bean。 属性: value:指定 bean 的 id。如果不指定 value 属性,默认 bean 的 id 是当前类的类名。首 字母小写。 1.3.1.2 @Controller @Service @Repository 他们三个注解都是针对一个的衍生注解,他们的作用及属性都是一模一样的。 他们只不过是提供了更加明确的语义化。 @Controller :一般用于表现层的注解。 @Service :一般用于业务层的注解。 @Repository :一般用于持久层的注解。 细节:如果注解中有且只有一个属性 要赋值时是 ,且名称是 value ,value可以省略 。 1.3.2 用于注入数据的相当于:<property name="" ref=""> <property name="" value=""> 1.3.2.1 @Autowired 作用: 自动按照类型注入。当使用注解注入属性时,set 方法可以省略。它只能注入其他 bean 类型。当有多个类型匹配时,使用要注入的对象变量名称作为 bean 的 id,在 spring 容器查找,找到了也可以注入成功。找不到就报错。 1.3.2.2 @Qualifier(不经常使用) 作用: 在自动按照类型注入的基础之上,再按照 Bean 的 id 注入。它在给字段注入时不能独 立使用,必须和@Autowire 一起使用;但是给方法参数注入时,可以独立使用。 属性: value:指定 bean 的 id。 1.3.2.3 @Resource 作用: 直接按照 Bean 的 id 注入。它也只能注入其他 bean 类型。 属性: name:指定 bean 的 id。 1.3.2.4 @Value 作用: 注入基本数据类型和 String 类型数据的 属性: value:用于指定值 1.3.3 用于改变作用范围的:相当于:<bean id="" class="" scope=""> 1.3.3.1 @Scope 作用: 指定 bean 的作用范围。 属性:value:指定范围的值。 取值:singleton prototype request session 1.3.4 和生命周期相关的:( 了解)相当于:<bean id="" class="" init-method="" destroy-method="" /> 1.3.4.1 @PostConstruct 作用: 用于指定初始化方法。 1.3.4.2 @PreDestroy 作用: 用于指定销毁方法。 1.3.5 关于注解和 XML 的选择问题注解的优势: 配置简单,维护方便(我们找到类,就相当于找到了对应的配置)。 XML 的优势: 修改时,不用改源码。不涉及重新编译和部署 1.4 spring 管理对象细节基于注解的 spring IoC 配置中,bean 对象的特点和基于 XML 配置是一模一样的。 写到此处,基于注解的 IoC 配置已经完成,但是大家都发现了一个问题:我们依然离不开 spring的 xml 配置文件,那么能不能不写这个 bean.xml,所有配置都用注解来实现呢? 答案是肯定的,请看下一章节。 1.5 spring 的纯注解配置创建对象的注解: 1.5.1 待改造的问题我们发现,之所以我们现在离不开 xml 配置文件,是因为我们有一句很关键的配置: <!-- 告知spring框架在,读取配置文件,创建容器时,扫描注解,依据注解创建对象,并存入容器中 --> <context:component-scan base-package="cn.bdqn"></context:component-scan> 如果他要也能用注解配置,那么我们就可以脱离 xml 文件了。 1.5.2 使用注解配置要扫描的包项目结构图: 在此图中,我们已经一点也看不到 xml 的身影了。那么,那句关键的配置跑哪去了呢? 在一个新的类上: 配置类代码:
测试类代码:
1.5.3 新注解说明1.5.3.1 @Configuration 作用: 用于指定当前类是一个配置类,会从该类上加载注解。读取该类上@ ComponentScan 注解 初始化 spring 容器。 1.5.3.2 @ComponentScan 作用: 用于指定 spring 在初始化容器时要扫描的包。 属性: basePackages :用于指定要扫描的包。和该注解中的 value 属性作用一样。 1.5.3.3 @Bean 作用:把方法的返回值放入到Spring容器中 该注解只能写在方法上,表明使用此方法创建一个对象,并且交给 spring 管理。 属性:name :给当前@Bean 注解的方法创建的对象指定一个名称(即 bean 的 id)。 配置类代码:
测试代码:
1.5.3.4 @Import 作用: 用于导入其他配置类 属性: value[] :用于指定其他配置类的字节码。 示例代码:这样就不需要再向容器中放入JdbcConfig类,@Component注解省略 直接使用@Important注解加载配置类
1.5.3.5 @PropertySource 作用:用于加载.properties 文件中的配置 属性: value[] :用于指定 properties 文件位置。如果是在类路径下,需要写上 classpath:
注意4.3以前版本需要提供占位符类:
1.5.3.6 @Qualifier的第二种使用方法 1.第一种方式 和@Autowire一起配合起来使用,用于指定固定一个对象的配置. 2.用于向方法参数注入Spring容器中对象. 示例代码:
测试代码:
第2章Spring整合Junit2.1 准备测试环境2.1.1 创建业务层接口和实现类业务层接口:
业务层实现类:
2.1.2 创建持久层接口和实现类持久层接口:
持久层实现类:
2.1.3 导入 junit 的 的 jar2.1.4 编写测试类
2.2 使用 xml 配置步骤2.2.1 xml配置文件 创建对象放入容器
2.2.2 第一步:拷贝整合 junit 的必备 jar 包到 lib此处需要注意的是,导入 jar 包时,需要导入一个 spring 中 aop 的 jar 包 2.2.3 第二步:使用@RunWith注解替换原有运行器(junit运行器)
2.2.4 第三步:使用@ContextConfiguration 指定 spring 配置文件的位置
2.2.5 第四步使用@Autowired 给测试类中的变量注入数据
2.3 使用纯注解配置步骤2.3.1 第一步:拷贝整合 junit的必备 jar 包到 lib 目录此处需要注意的是,导入 jar 包时,需要导入一个 spring 中 aop 的 jar 包。
2.3.2 第二步 把资源都用注解管理2.3.3 第三步:使用注解配置方式创建 spring2.3.4 第四步:使用 RunWith 注解和 ContextConfiguration 注解配置最终代码:
2.4 为什么 不把测试类配到 xml在解释这个问题之前,先解除大家的疑虑,配到 XML 中能不能用呢? 答案是肯定的,没问题,可以使用。 那么为什么不采用配置到 xml 中的方式呢? 这个原因是这样的: 第一:当我们在 xml 中配置了一个 bean,spring 加载配置文件创建容器时,就会创建对象。 第二:测试类只是我们在测试功能时使用,而在项目中它并不参与程序逻辑,也不会解决需求上的问题,所以创建完了,并没有使用。那么存在容器中就会造成资源的浪费。 所以,基于以上两点,我们不应该把测试配置到 xml 文件中。 总结:今天的注解:
创建对象的注解: @Component @Controller(用于web层) @Service(用于service) @Repository(用于dao) 主配置文件包扫描: <context:component-scan base-package="com.bdqn"></context:component-scan> 作用范围: @Scope : singleton、prototype
依赖注入: @Autowired(自动按照类型注入) @Qualifier(和@Autowired配合使用用于指定具体类型 value属性指定) @Resource:jdk name属性 用于指定具体类型 如果只有一个实现类: autowire和resource注解可以互换 @Value
新注解: @Configuration @ComponentScan @Bean name属性 @Import @PropertySource
|
2.2.4.4 注入集合属性
顾名思义,就是给类中的集合成员传值,它用的也是 set 方法注入的方式,只不过变量的数据类型都是集合。我们这里介绍注入数组,List,Set,Map,Properties。具体代码如下:
public class CustomerServiceImpl implements ICustomerService { private String[] myStrs; private List<String> myList; private Set<String> mySet; private Map<String,String> myMap; private Properties myProps; public void setMyStrs(String[] myStrs) { this.myStrs = myStrs; } public void setMyList(List<String> myList) { this.myList = myList; } public void setMySet(Set<String> mySet) { this.mySet = mySet; } public void setMyMap(Map<String, String> myMap) { this.myMap = myMap; } public void setMyProps(Properties myProps) { this.myProps = myProps; } @Override public void saveCustomer() { System.out.println(Arrays.toString(myStrs)); System.out.println(myList); System.out.println(mySet); System.out.println(myMap); System.out.println(myProps); } } <!-- 注入集合数据 List 结构的: array,list,set Map 结构的 map,entry,props,prop --> <bean id="customerService" class="com.bdqn.service.impl.CustomerServiceImpl"> <!-- 在注入集合数据时,只要结构相同,标签可以互换 --> <!-- 给数组注入数据 --> <property name="myStrs"> <set> <value>AAA</value> <value>BBB</value> <value>CCC</value> </set> </property> <!-- 注入 list 集合数据 --> <property name="myList"> <array> <value>AAA</value> <value>BBB</value> <value>CCC</value> </array> </property> <!-- 注入 set 集合数据 --> <property name="mySet"> <list> <value>AAA</value> <value>BBB</value> <value>CCC</value> </list> </property> <!-- 注入 Map 数据 --> <property name="myMap"> <props> <prop key="testA">aaa</prop> <prop key="testB">bbb</prop> </props> </property> <!-- 注入 properties 数据 --> <property name="myProps"> <map> <entry key="testA" value="aaa"></entry> <entry key="testB"> <value>bbb</value> </entry> </map> </property> </bean> |
第3 章 附录
3.1 Spring 配置文件中提示的配置
3.1.1 复制路径:
http://www.springframework.org/schema/beans/spring-beans.xsd
3.1.2 查找 XML Catalog:
Spring第三天
- AOP的相关概念
- AOP概述
- 什么是AOP
- AOP概述
AOP:全称是 Aspect Oriented Programming 即:面向切面编程。
简单的说它就是把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不
修改源码的基础上,对我们的已有方法进行增强。
-
-
- AOP的作用及优势
-
作用: 在程序运行期间,不修改源码对已有方法进行增强。 优势: 减少重复代码 提高开发效率 维护方便 |
-
-
- AOP的实现方式
-
使用动态代理技术 |
可能通过上面的介绍 ,我们还是没有一个清晰的 认识。没关系,我们看看下面的 具体应用。
-
- AOP的具体应用
1.2.1 案例中问题
这是我们之前在struts2课程中做的一个新增与全表查询的例子。下面是客户的业务层接口和实现类。
通过下面的代码,我们能看出什么问题吗?
CustomerDao接口: public interface CustomerDao { //保存用户方法 public void saveCustomer(Customer customer); //修改客户 public void updateCustomer(Customer customer); //根据id删除客户 public void deleteCustomerById(String customerId); } CustomerDao实现类: @Override public void saveCustomer(Customer customer) { Session session = HibernateUtils.getSession(); //开启事务 Transaction transaction = session.beginTransaction(); //保存用户到数据库 session.save(customer); //提交事务 transaction.commit(); //关闭资源连接 session.close(); } @Override public void updateCustomer(Customer customer) { Session session = HibernateUtils.getSession(); //开启事务 Transaction transaction = session.beginTransaction(); //更新客户 session.update(customer);; //提交事务 transaction.commit(); //关闭资源连接 session.close();
} @Override public void deleteCustomerById(String customerId) { Session session = HibernateUtils.getSession(); //开启事务 Transaction transaction = session.beginTransaction(); Customer customer=new Customer(); customer.setCustId(Long.parseLong(customerId)); //更新客户 session.delete(customer);; //提交事务 transaction.commit(); //关闭资源连接 session.close();
} |
上面代码的问题就是: 我们的事务控制是重复性的代码。 这还只是一个业务类 里的两个方法 ,如果有 多个业务了,每个业务类中都会有这些重复性的代码。
思考:
我们有什么办法解决这个问题吗?
要想解决这个问题,我们得回顾一下之前我们讲过的知识: 动态代理。
1.2.2 动态代理回顾
1.2.2.1 动态代理的特点
字节码随用随创建,随用随加载。 它与静态代理的区别也在于此。因为静态代理是字节码一上来就创建好,并完成加载。 装饰者模式就是静态代理的一种体现。 |
1.2.2.2 动态代理常用的有两种方式
基于接口的动态代理 提供者:JDK 官方的 Proxy 类。 要求:被代理类 最少 实现一个接口。
基于子类的动态代理 提供者:第三方的 CGLib,如果报 asmxxxx 异常,需要导入 asm.jar。 要求:被代理类不能用 final 修饰的 类 (最终类)。 |
1.2.2.3 使用JDK官方Proxy类创建代理对象
此处我们使用的是一个演员的例子:
在一个村庄里,有个老人,会一门技艺,就是吹小号 ,村里有哪家办喜事,就请这位老人过来表演助助兴,老人表演完,收点钱。而随着时间的推移,这位老人有了一定的名气,请他表演的人越来越多了,他一个人接不了那么多活,他就想了一个办法,收徒弟,让徒弟去表演,他就作为一个中间人去联系表演。下面我们就用代码演示出来。
接口方法:
public interface IActor { /** * 徒弟要求 * 能够做基本表演和精彩表演 */ //基本表演 public void basicAct(float money); //精彩表演 public void wonderfulAct(float money); } |
实现类:
//实现了接口,就表示具有接口中的方法实现.即:符合老人的要求 public class Actor implements IActor { @Override public void basicAct(float money) { System.out.println("拿到钱,开始基本表演:"+money); } @Override public void wonderfulAct(float money) { System.out.println("拿到钱,开始精彩表演:"+money); }
} |
测试类:
public class Client { public static void main(String[] args) { //有一家人办喜事,找演员 //创建一个演员 Actor actor=new Actor(); /** * 代理: * 间接。 * 获取代理对象: * 要求: * 被代理类最少实现一个接口 * 创建的方式 * Proxy.newProxyInstance(三个参数) * 参数含义: * ClassLoader:和被代理对象使用相同的类加载器。 * Interfaces:和被代理对象具有相同的行为。实现相同的接口。 * InvocationHandler:如何代理。 * 策略模式:使用场景是: * 数据有了,目的明确。 * 如何达成目标,就是策略。 */ IActor proxyActor=(IActor) Proxy.newProxyInstance(actor.getClass().getClassLoader(), actor.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { /** * 执行被代理对象的任何方法,都会经过该方法. * 此方法有拦截的功能 * 参数: * proxy: 代理对象的引用.不一定每次都用到 * method: 当前执行的方法对象 * args: 执行方法所需要的参数 * 返回值: * 当前执行方法的返回值 */ //根据方法对象回到方法名 String name = method.getName(); //获得方法参数 Float money=(Float) args[0]; //定义一个返回值 Object rtValue=null; //每个演出的费用不一样,在这里开始判断 if("basicAct".equals(name)) { //基本演出 少于2000不演 if(money>2000) { //看上去商家是给了 8000,实际到徒弟手里只有 4000 //这就是我们没有修改原来 basicAct 方法源码,对方法进行了增强 System.out.println("徒弟开始基本表演...."); rtValue=method.invoke(actor, money/2); } } if("wonderfulAct".equals(name)) { //精彩表演,没有5000不演 if(money>5000) { //看上去商家是给了 8000,实际到徒弟手里只有 4000 //这就是我们没有修改原来 basicAct 方法源码,对方法进行了增强 System.out.println("徒弟开始精彩表演...."); rtValue=method.invoke(actor, money/2); } } return rtValue; } }); //没有徒弟的时候自己表演 actor.basicAct(1000f); actor.wonderfulAct(5000f); System.out.println("-----------------------------"); //有徒弟的时候,让师傅联系徒弟让徒弟表演 proxyActor.basicAct(8000f); proxyActor.wonderfulAct(6000f); } } |
1.2.2.4 使用 CGLib 的 的 Enhancer
还是那个演员的例子,只不过不让他实现接口。
public class Actor { public void basicAct(float money) { System.out.println("拿到钱,开始基本表演:"+money); } public void wonderfulAct(float money) { System.out.println("拿到钱,开始精彩表演:"+money); }
} public class Client { /** * 基于子类的动态代理 * 要求: * 被代理对象不能是最终类 * 用到的类: * Enhancer * 用到的方法: * create(Class, Callback) * 方法的参数: * Class:被代理对象的字节码 * Callback:如何代理 * @param args */ public static void main(String[] args) { final Actor actor = new Actor(); Actor cglibActor = (Actor) Enhancer.create(actor.getClass(), new MethodInterceptor() { /** * 执行被代理对象的任何方法,都会经过该方法。在此方法内部就可以对被代理对象的任何 方法进行增强。 * * 参数: * 前三个和基于接口的动态代理是一样的。 * MethodProxy:当前执行方法的代理对象。 * 返回值: * 当前执行方法的返回值 */ @Override public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { String name = method.getName(); Float money = (Float) args[0]; Object rtValue = null; if("basicAct".equals(name)){ //基本演出 if(money > 2000){ rtValue = method.invoke(actor, money/2); } } if("wonderfulAct".equals(name)){ //精彩演出 if(money > 5000){ rtValue = method.invoke(actor, money/2); } } return rtValue; } }); cglibActor.basicAct(10000); cglibActor.wonderfulAct(100000); } } |
这个故事(示例)讲完之后,我们从中受到什么启发呢?它到底能应用在哪呢?
1.2.3 解决案例中的问题
思路只有一个:使用动态代理技术创建客户业务层的代理对象,在执行 CustomerDaoImpl 时,对里面的方法进行增强,加入事务的支持。
/** * 用于创建客户业务层对象工厂(当然也可以创建其他业务层对象,只不过我们此处不做那么繁琐) * @author Johnny.Chen * */ public class BeanFactory { /** * 获取客户业务层对象的代理对象 * @return */ public static ICustomerDao getCustomerDao() { //定义客户业务层对象 final ICustomerDao customerDao = new CustomerDaoImpl(); //生成它的代理对象 ICustomerDao proxyCustomerDao = (ICustomerDao) Proxy.newProxyInstance(customerDao.getClass().getClassLoader() ,customerDao.getClass().getInterfaces(), new InvocationHandler() { //执行客户业务层任何方法,都会在此处被拦截,我们对那些方法增强,加入事务。 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String name = method.getName(); Object rtValue = null; try{ //开启事务 HibernateUtil.beginTransaction(); //执行操作 rtValue = method.invoke(customerDao, args); //提交事务 HibernateUtil.commit(); }catch(Exception e){ //回滚事务 HibernateUtil.rollback(); e.printStackTrace(); }finally{ //释放资源.hibernate 在我们事务操作(提交/回滚)之后,已经帮我们关了。 //如果他没关,我们在此处关 } return rtValue; } }); return proxyCustomerDao; } }
|
-
- Spring中的AOP
- 关于代理的选择
- Spring中的AOP
在 spring 中,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式。 实现了接口使用JDK官方Proxy类创建代理对象,没有实现接口使用CGLib的Enhancer来创建代理对象. |
-
-
- AOP相关术语
-
Joinpoint( 连接 点): 所谓连接点是指那些被拦截到的点。在 spring 中,这些点指的是方法,因为 spring 只支持方法类型的连接点。 Pointcut( 切入点): 所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义。 Advice( 通知/ 增强): 所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。 通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。 Introduction( 引介): 引介是一种特殊的通知在不修改类代码的前提下, Introduction 可以在运行期为类动态地添加一些方法或 Field。 Target( 目标对象): 代理的目标对象 Weaving( 织入): 是指把增强应用到目标对象来创建新的代理对象的过程。 spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入。 Proxy (代理): 一个类被 AOP 织入增强后,就产生一个结果代理类。 Aspect( 切面): 是切入点和通知(引介)的结合。 |
-
-
- 学习spring中的AOP要明确的事
-
a 、开发阶段(我们做的) 编写核心业务代码(开发主线):大部分程序员来做,要求熟悉业务需求。 把公用代码抽取出来,制作成通知。(开发阶段最后再做):AOP 编程人员来做。 在配置文件中,声明切入点与通知间的关系,即切面。:AOP 编程人员来做。 b 、运行阶段(Spring 框架完成的) Spring 框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。 |
- 基于XML的AOP的配置
2.1 环境准备
2.1.1 准备客户的业务层接口和实现类
//业务层接口 public interface CustomerService { //保存客户 public void saveCustomer(); //修改客户 public void updateCustomer(Integer id); } //业务层实现类 public class CustomerServiceImpl implements CustomerService { @Override public void saveCustomer() { System.out.println("调用持久层,执行保存客户!");
} @Override public void updateCustomer(Integer id) { System.out.println("调用持久层,执行修改客户:"+id); } } |
2.2.1 拷贝必备的jar包到工程lib目录
2.1.3 第三步:创建 spring 的配置文件并导入约束
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
</beans> |
2.1.4 第四步:把客户的业务 层配置到 spring 容器中
<!-- 把资源交给Spring来管理 --> <bean id="customerService" class="com.bdqn.service.impl.CustomerServiceImpl"></bean> |
2.1.5 第五步:制作通知(增强的类)
package com.bdqn.log; //一个记录日志的类 public class MyLog { public void printLog() { System.out.println("MyLog类中的pringLog方法开始记录日志了!!!"); } }
|
2.2 配置步骤
2.2.1 第一步:把通知类用 bean 标签配置起来
<!-- 把MyLog类交给Spring管理 --> <bean id="myLog" class="com.bdqn.log.MyLog"></bean> |
2.2.2 第二步:使用 aop:config 声明 aop
<!-- aop配置 --> <aop:config> <!-- 配置的代码写在此处 --> </aop:config> |
2.2.3 第三步:使用 aop:aspect 配置切面
<!-- 配置切面 此标签要出现在 aop:config标签内部 id 切面的唯一标识符 ref 引用的是通知类的bean的id --> <aop:aspect id="logAdvice" ref="myLog">
</aop:aspect> |
2.2.4 第四步:使用 aop:before 配置前置通知
<!-- method: 用于指定通知类的增强方法名 pointcut-ref: 用于指定引用的切入点表达值 --> <aop:before method="printLog" pointcut-ref="pt1"/> |
2.2.5 第五步:使用 aop:pointcut 配置切入点表达式
<!-- expression 切入点表达式 id 切入点表达式的唯一标识符 --> <aop:pointcut expression="execution(public void com.bdqn.service.impl.CustomerServiceImpl.saveCustomer())" id="pt1"/> |
2.3 切入点表达式说明
execution: 匹配方法的执行(常用) execution(表达式) 表达式语法:execution([修饰符] 返回值类型 包名.类名.方法名(参数)) 写法说明: 全匹配方式: public void cn.bdqn.service.impl.CustomerServiceImpl.saveCustomer() 访问修饰符可以省略 void cn.bdqn.service.impl.CustomerServiceImpl.saveCustomer() 返回值可以使用*号,表示任意返回值 * cn.bdqn.service.impl.CustomerServiceImpl.saveCustomer() 包名可以使用*号,表示任意包,但是有几级包,需要写几个* * *.*.*.*.CustomerServiceImpl.saveCustomer() 使用..来表示当前包,及其子包 * cn..CustomerServiceImpl.saveCustomer() 类名可以使用*号,表示任意类 * cn..*.saveCustomer() 方法名可以使用*号,表示任意方法 * cn..*.*() 参数列表可以使用*,表示参数可以是任意数据类型,但是必须有参数 * cn..*.*(*) 参数列表可以使用..表示有无参数均可,有参数可以是任意类型 * cn..*.*(..) 全通配方式: * *..*.*(..)
|
2.4 常用标签
2.4.1 <aop:config>
作用:
用于声明开始 aop 的配置
2.4.2 <aop:aspect>
作用:
用于配置切面。
属性:
id :给切面提供一个唯一标识。
ref :引用配置好的通知类 bean 的 id。
2.4.3 <aop:pointcut>
作用:
用于配置切入点表达式
属性:
expression :用于定义切入点表达式。
id :用于给切入点表达式提供一个唯一标识。
2.4.4 <aop:before>
作用:
用于配置前置通知
属性:
method :指定通知中方法的名称。
pointct :定义切入点表达式
pointcut-ref :指定切入点表达式的引用
2.4.5 <aop:after-returning>
作用:用于配置后置通知
属性:
method :指定通知中方法的名称。
pointct :定义切入点表达式
pointcut-ref :指定切入点表达式的引用
2.4.6 <aop:after-throwing>
作用:用于配置异常通知
属性:
method :指定通知中方法的名称。
pointct :定义切入点表达式
pointcut-ref :指定切入点表达式的引用
2.4.7 <aop:after>
作用:用于配置最终通知
属性:
method :指定通知中方法的名称。
pointct :定义切入点表达式
pointcut-ref :指定切入点表达式的引用
2.4.8 <aop:around>
作用:用于配置环绕通知
属性:
method :指定通知中方法的名称。
pointct :定义切入点表达式
pointcut-ref :指定切入点表达式的引用
2.5 通知的类型
2.5.1 类型说明
<!-- 配置通知的类型 aop:before: 用于配置前置通知。前置通知的执行时间点:切入点方法执行之前执行 aop:after-returning: 用于配置后置通知。后置通知的执行时间点:切入点方法正常执行之后。它和异常通知只能有一个 执行 aop:after-throwing 用于配置异常通知。异常通知的执行时间点:切入点方法执行产生异常后执行。它和后置通知只能执行一个。 aop:after 用于配置最终通知。最终通知的执行时间点:无论切入点方法执行时是否有异常,它都会在其后面执行。 aop:around 用于配置环绕通知。他和前面四个不一样,他不是用于指定通知方法何时执行的。 --> <aop:before method="beforePrintLog" pointcut-ref="pt1"/> <aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt1"/> <aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"/> <aop:after method="afterPrintLog" pointcut-ref="pt1"/> <aop:around method="aroundPringLog" pointcut-ref="pt1"/> |
2.5.2 环绕通知的特殊说明
/** * 环绕通知 它是 spring 框架为我们提供的一种可以在代码中手动控制增强部分什么时候执行的方式。 问题: * 当我们配置了环绕通知之后,增强的代码执行了,业务核心方法没有执行。 分析: 通过动态代理我们知道在 invoke * 方法中,有明确调用业务核心方法:method.invoke()。 我们配置的环绕通知中,没有明确调用业务核心方法。 解决: spring * 框架为我们提供了一个接口:ProceedingJoinPoint,它可以作为环绕通知的方法参数 在环绕通知执行时,spring * 框架会为我们提供该接口的实现类对象,我们直接使用就行。 该接口中有一个方法 proceed(),此方法就相当于 method.invoke() */ public void aroundPringLog(ProceedingJoinPoint pjp) { try { System.out.println("前置通知:Logger 类的 aroundPringLog 方法记录日志"); pjp.proceed(); System.out.println("后置通知:Logger 类的 aroundPringLog 方法记录日志"); } catch (Throwable e) { System.out.println("异常通知:Logger 类的 aroundPringLog 方法记录日志"); e.printStackTrace(); } finally { System.out.println("最终通知:Logger 类的 aroundPringLog 方法记录日志"); } } |
- 基于注解的AOP配置
3.1 准备环境
3.1.1 第一步:准 备客户的业务层和接口并用注解配置 (需要增强的类)
//业务层接口 public interface CustomerService { //保存客户 public void saveCustomer(); //修改客户 public void updateCustomer(Integer id); } //业务层实现类 public class CustomerServiceImpl implements CustomerService { @Override public void saveCustomer() { System.out.println("调用持久层,执行保存客户!"); } @Override public void updateCustomer(Integer id) { System.out.println("调用持久层,执行修改客户:"+id); } } |
3.1.2 第二步:拷贝必备的 jar 包到工 程的 lib
3.1.3 第三步:创建 spring 的配置文件并导入约束
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
</beans> |
3.1.4 第四步:把资源使用注解让 spring
@Service("customerService") public class CustomerServiceImpl implements CustomerService { @Override public void saveCustomer() { //模拟异常 int i=1/0; System.out.println("调用持久层,执行保存客户!"); } @Override public void updateCustomer(Integer id) { System.out.println("调用持久层,执行修改客户:"+id); } }
|
3.1.5 第五步:在配置文件中指定 spring
<!-- 开启注解扫描 告诉Spring框架创建对象使用包扫描的方式 --> <context:component-scan base-package="com.bdqn"/> |
3.2 配置步骤
3.2.1 第一步:把通知类也使用注解配置
//一个记录日志的类 @Component("logger") public class MyLog { public void beforePrintLog() { System.out.println("前置通知:MyLog类中的pringLog方法开始记录日志了!!!"); } } |
3.2.2 第二步:在通知类上使用@Aspect注解声明为切面
//一个记录日志的类 @Component("logger") //表明当前类是一个切面类 @Aspect public class MyLog { public void beforePrintLog() { System.out.println("前置通知:MyLog类中的pringLog方法开始记录日志了!!!"); } }
|
3.2.3 第三步:在增强的方法上使用@Before
//配置前置通知 value属性用于配置切入点表达式 @Before("execution(* com.bdqn.service.impl..*.*(..))") public void beforePrintLog() { System.out.println("前置通知:MyLog类中的pringLog方法开始记录日志了!!!"); } |
3.2.4 第四步:在 spring 配置文件中开启 spring 对注解 AOP 的支持.
<!-- 开启Spring对AOP的支持 --> <aop:aspectj-autoproxy/>
|
3.3 常用注解
3.3.1 @Aspect :
作用:把当前类声明为切面类。
3.3.2 @Before :
作用:把当前方法看成是前置通知。
属性:
value:用于指定切入点表达式,还可以指定切入点表达式的引用。
3.3.3 @AfterReturning
作用:把当前方法看成是后置通知。
属性:
value:用于指定切入点表达式,还可以指定切入点表达式的引用。
3.3.4 @AfterThrowing
作用:把当前方法看成是异常通知。
属性:
value:用于指定切入点表达式,还可以指定切入点表达式的引用。
3.3.5 @After
作用:把当前方法看成是最终通知。
属性:
value:用于指定切入点表达式,还可以指定切入点表达式的引用。
3.3.6 @Around
作用:把当前方法看成是环绕通知。
属性:
value:用于指定切入点表达式,还可以指定切入点表达式的引用。
3.3.7 @Pointcut
作用:指定切入点表达式
属性:
value:指定表达式的内容
3.4 不使用 XML
@Configuration @ComponentScan(basePackages="cn.bdqn") @EnableAspectJAutoProxy public class SpringConfiguration { } |
最后
以上就是等待万宝路为你收集整理的初学spring框架和小练习Spring第一天Spring 第 二 天第1 章 基于注解的 IOC 配置第2章Spring整合Junit总结:第3 章 附录Spring第三天的全部内容,希望文章能够帮你解决初学spring框架和小练习Spring第一天Spring 第 二 天第1 章 基于注解的 IOC 配置第2章Spring整合Junit总结:第3 章 附录Spring第三天所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复