Spring入门
Spring是什么?
Spring是一个开源框架,为简化企业级应用开发而生。使用spring可以使简单的JavaBean实现以前只有EJB才能实现的功能。
Spring是一个IOC(DI)和AOP容器框架。
Spring模块
eclipse安装spring tool suite
SPRING TOOL SUITE 是一个 Eclipse 插件,利用该插件可以更方便的在 Eclipse 平台上开发基于 Spring 的应用。
安装方法说明(springsource-tool-suite-3.4.0.RELEASE-e4.3.1-updatesite.zip):
Help --> Install New Software...
Click Add...
In dialog Add Site dialog, click Archive...
Navigate to springsource-tool-suite-3.4.0.RELEASE-e4.3.1-updatesite.zip and click Open
Clicking OK in the Add Site dialog will bring you back to the dialog 'Install'
Select the xxx/Spring IDE that has appeared
Click Next and then Finish
Approve the license
Restart eclipse when that is asked
搭建Spring开发环境
把jar包加入到工程的classpath下:
Spring HelloWorld!
public class HelloWorld {
private String user;
public HelloWorld() {
System.out.println("HelloWorld's constructor...");
}
public void setUser(String user) {
System.out.println("setUser:" + user);
this.user = user;
}
public HelloWorld(String user) {
this.user = user;
}
public void hello(){
System.out.println("Hello: " + user);
}
}
<!-- 属性注入:使用属性注入时,bean中必须有无参构造器 -->
<bean id="helloWord" class="com.atguigu.spring.beans.HelloWorld">
<!-- 与set方法的setname一致 -->
<property name="user" value="spring"></property>
</bean>
//1.创建spring的ioc容器对象
//ApplicationContext代表IOC容器
//ClassPathXmlApplicationContext 是ApplicationContext的实现类,该实现类从类路径下加载配置文件
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
//2.从ioc容器获取bean实例
//利用类型返回IOC容器当中的bean,要求IOC容器中只能有一个该类型的bean
HelloWorld helloWord2 = ctx.getBean(HelloWorld.class);
//利用id定位到容器中的bean
// HelloWorld helloWord2 = (HelloWorld) ctx.getBean("helloWord");
helloWord2.hello();
Spring Bean的配置
IOC和DI概述
IOC(Inversion of Control):其思想是 反转资源获取的方向。 传统的资源查找方式要求组件向容器发起请求查找资源。作为回应,容器适时的返回资源。而应用了IOC之后,则是容器主动将资源推送给它所管理的组件,组件所要做的仅仅是选择一种合适的方式来接受资源。 这种行为也被称为查找的被动形式。
DI(Dependency Injection) —IOC的另一种表述方式:即 组件以一些预先定义好的方式(例如:setter方法)接受来自如容器的资源注入。 相对于IOC而言,这种表述更直接。
配置bean
> 配置形式:基于xml文件的方式;基于注解的方式
在xml文件中通过bean节点来配置bean
<bean id="helloWord" class="com.atguigu.spring.beans.HelloWorld">
<!-- 与set方法的setname一致 -->
<property name="user" value="spring"></property>
</bean>
id:bean的名称。
- 在IOC容器中必须是唯一的
- 若id没有指定,spring自动将权限定性类名作为Bean的名字
Spring 提供了两种类型的IOC容器实现
- BeanFactory:IOC容器的基本实现
- ApplicationContext:提供了更多的高级特性,是BeanFactory的子接口。
- BeanFactory是Spring框架的基础设施,面向Spirng本身。ApplicationContext面向使用Spring框架的开发者,几乎所有的应用场合都直接使用ApplicationContext而非底层的BeanFactory。
- 无论使用何种方式,,配置文件是相同的。
ApplicationContext
- ApplicationContext的主要实现类:
- ClassPathXmlApplicationContext:从类路径下加载配置文件。
- FileSystemXmlApplicationContext:从文件系统中加载配置文件。
- ConfigurableApplicationContext扩展于ApplicationContext,新增加两个主要方法:refresh()和close(),让ApplicationContext具有启动、刷新和关闭上下文的能力。
- ApplicationContext在初始化时就实例化所有单例的bean。
- WebApplicationContext:专门为web应用而准备的,允许从相对于web根目录的路径中完成初始化工作。
依赖注入的方式:
- 属性注入
- 构造注入
- 工厂方法注入(很少使用,不推荐)
属性注入
- 即通过 setter方法 注入bean的属性值或依赖的对象。
- 属性注入使用 元素,使用name属性指定bean的属性名称,value属性或 子节点指定属性值。
- 属性注入是实际应用中最常用的注入方式。
构造方法注入
- 通过构造方法注入bean的属性值或依赖的对象,它保证了bean实例在实例化后就可以使用。
- 构造器注入在 元素里声明属性, 中没有name属性。
- type:匹配参数类型,对应构造方法类型
- index:按索引匹配,对应构造方法参数次序
- 如果value中包含特殊字符,可以使用
引用其它的bean
- 要使bean能够相互访问,就必须在bean配置文件中指定对bean的引用。
- 使用ref属性或元素为bean的属性或构造参数指定对bean的引用。
- 也可以在属性或构造器里包含bean的声明,这样的bean成为内部bean。
- 当bean实例仅仅给一个特定的属性使用时,可以将其声明为内部bean,内部bean声明直接包含在或元素里,不需要设置任何id或name属性。
null属性和级联属性
可以使用专用的 元素标签为Bean的字符串或其它对象类型的属性注入null值
和 Struts、Hiberante等框架一样,Spring 支持级联属性的配置。
集合属性
- 在 Spring中可以通过一组内置的xml标签(例如:, 或
- 配置 java.util.List类型的属性,需要指定 标签,在标签里包含一些元素.这些标签可以通过指定简单的常量值,通过指定对其他Bean的引用.通过指定内置Bean定义.通过指定空元素.甚至可以内嵌其他集合.
- 数组的定义和 List 一样,都使用
- 配置 java.util.Set需要使用标签,定义元素的方法与List一样.
- Java.util.Map通过
- 必须在 标签里定义键
- 因为键和值的类型没有限制,所以可以自由地为它们指定, , 或 元素.
- 可以将 Map 的键和值作为的属性定义:简单常量使用key和value来定义;Bean 引用通过 key-ref和value-ref属性定义
- 使用 定义java.util.Properties,该标签使用多个作为子标签.每个标签必须定义key 属性.
使用 utilityscheme 定义集合
- 使用基本的集合标签定义集合时,不能将集合作为独立的 Bean 定义, 导致其他Bean 无法引用该集合, 所以无法在不同Bean 之间共享集合.
- 可以使用 utilschema 里的集合标签定义独立的集合 Bean.需要注意的是,必须在根元素里添加utilschema 定义
使用 p 命名空间
- 为了简化 XML 文件的配置,越来越多的XML文件采用属性而非子元素配置信息。
- Spring 从 2.5版本开始引入了一个新的p命名空间,可以通过元素属性的方式配置Bean的属性。
- 使用 p 命名空间后,基于XML的配置方式将进一步简化
XML 配置里的 Bean自动装配
- Spring IOC 容器可以自动装配Bean.需要做的仅仅是在 的 autowire 属性里指定自动装配的模式
- byType(根据类型自动装配):若IOC容器中有多个与目标Bean类型一致的Bean.在这种情况下,Spring 将无法判定哪个 Bean 最合适该属性,所以不能执行自动装配.
- byName(根据名称自动装配):必须将目标Bean的名称和属性名设置的完全相同.
- constructor(通过构造器自动装配):当Bean中存在多个构造器时,此种自动装配方式将会很复杂.不推荐使用
XML 配置里的 Bean自动装配的缺点
•在 Bean 配置文件里设置autowire属性进行自动装配将会装配Bean的所有属性.然而,若只希望装配个别属性时,autowire属性就不够灵活了.
•autowire 属性要么根据类型自动装配,要么根据名称自动装配,不能两者兼而有之.
•一般情况下,在实际的项目中很少使用自动装配功能,因为和自动装配功能所带来的好处比起来,明确清晰的配置文档更有说服力一些
public class Person {
private String name;
private Address address;
private Car car
}
<bean id="address" class="com.atguigu.spring.beans.autowire.Address"
p:city="Beijing" p:street="Huilongguan"></bean>
<bean id="address2" class="com.atguigu.spring.beans.autowire.Address"
p:city="Beijing" p:street="ZhongShan"></bean>
<bean id="car" class="com.atguigu.spring.beans.autowire.Car"
p:brand="Audi" p:price="300000"></bean>
<!-- 可以使用autowire属性指定自动装配的方式,
byName根据bean的名字和当前bean的setter风格的属性名进行自动装配,
若有匹配的则进行自动装配,没有匹配的则不装配
byType 根据bean的类型和当前bean的属性的类型进行自动装配,若IOC容器中有一个以上类型的bean,则抛异常 -->
<bean id="person" class="com.atguigu.spring.beans.autowire.Person"
p:name="Tom" autowire="byType"></bean>
继承 Bean 配置
•Spring 允许继承 bean的配置, 被继承的bean称为父bean.继承这个父Bean的Bean称为子Bean
•子 Bean从父 Bean中继承配置,包括 Bean的属性配置
•子 Bean 也可以覆盖从父 Bean继承过来的配置
•父 Bean 可以作为配置模板,也可以作为Bean实例.若只想把父 Bean作为模板,可以设置 的abstract属性为 true,这样Spring将不会实例化这个Bean
•并不是 元素里的所有属性都会被继承.比如:autowire,abstract等.
•也可以忽略父Bean 的class 属性,让子Bean指定自己的类,而共享相同的属性配置.但此时abstract 必须设为true
<!-- 抽象bean:不能被IOC容器实例化,只用来被继承配置。abstract="true"声明为抽象bean,不能被实例化,模板bean -->
<!-- 若某个bean的class属性没有指定,必须是一个抽象bean -->
<bean id="address"
p:city="Beijing^" p:street="WuDaoKou" abstract="true"></bean>
<!-- bean配置的继承:使用bean的parent属性指定继承哪个bean的配置 -->
<bean id="address2" class="com.atguigu.spring.beans.autowire.Address"
p:street="DaZhongSi" p:city="ShangHai" parent="address"></bean>
依赖 Bean 配置
•Spring 允许用户通过 depends-on 属性设定 Bean前置依赖的Bean,前置依赖的Bean会在本Bean实例化之前创建好
•如果前置依赖于多个 Bean,则可以通过逗号,空格或的方式配置Bean 的名称
<bean id="car" class="com.atguigu.spring.beans.autowire.Car"
p:brand="Audi" p:price="300000"></bean>
<!-- 要求配置person时,必须有一个关联的car,换句话person这个bean依赖于car这个bean -->
<bean id="person" class="com.atguigu.spring.beans.autowire.Person"
p:name="Tom" p:address-ref="address2" depends-on="car"></bean>
Bean 的作用域
•在 Spring中,可以在元素的scope 属性里设置Bean的作用域.
•默认情况下, Spring 只为每个在 IOC容器里声明的 Bean 创建唯一一个实例,整个 IOC容器范围内都能共享该实例:所有后续的getBean()调用和Bean引用都将返回这个唯一的Bean实例.该作用域被称为singleton, 它是所有Bean的默认作用域.
<!-- 使用bean的scope="prototype"属性来配置bean的作用域
singleton:默认值,容器初始化时创建bena实例,在整个容器的生命周期内之创建一次
prototype:原型的,容器初始化时不创建bean的实例,每次请求时创建一个新的bean实例并返回
-->
<bean id="car" class="com.atguigu.spring.beans.autowire.Car" scope="prototype">
<property name="brand" value="Audi"></property>
<property name="price" value="300000"></property>
</bean>
使用外部属性文件
•Spring 提供了一个PropertyPlaceholderConfigurer的BeanFactory 后置处理器,这个处理器允许用户将Bean配置的部分内容外移到属性文件中.可以在Bean配置文件里使用形式为${var} 的变量,PropertyPlaceholderConfigurer从属性文件里加载属性,并使用这些属性来替换变量.
•Spring 还允许在属性文件中使用${propName},以实现属性之间的相互引用。
user=root
password=root
driverClass=com.mysql.jdbc.Driver
jdbcUrl=jdbc:mysql:///test
<!-- 导入属性文件 -->
<context:property-placeholder location="classpath:db.properties"/>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!-- 使用外部化属性文件的属性 -->
<property name="user" value="${user}"></property>
<property name="password" value="${password}"></property>
<property name="driverClass" value="${driverClass}"></property>
<property name="jdbcUrl" value="${jdbcUrl}"></property>
</bean>
Spring表达式语言:SpEL
- Spring 表达式语言(简称SpEL):是一个支持运行时查询和操作对象图的强大的表达式语言。
- 语法类似于EL:SpEL使用 #{…} 作为定界符,所有在大框号中的字符都将被认为是SpEL
- SpEL为 bean的属性进行动态赋值提供了便利
- 通过 SpEL可以实现:
- 通过 bean 的id对bean进行引用
- 调用方法以及引用对象中的属性
- 计算表达式的值
- 正则表达式的匹配
<bean id="address" class="com.atguigu.spring.beans.spel.Address">
<!-- 使用stel为属性赋一个字面值 -->
<property name="city" value="#{'Beijing'}"></property>
<property name="street" value="#{'WuDaoKou'}"></property>
</bean>
<bean id="car" class="com.atguigu.spring.beans.spel.Car">
<property name="brand" value="Audi"></property>
<property name="price" value="500000"></property>
<!-- 使用spel引用类的静态属性 -->
<property name="tyrePrimeter" value="#{T(java.lang.Math).PI * 80}"></property>
</bean>
<bean id="person" class="com.atguigu.spring.beans.spel.Person">
<!-- 使用spel来引用其它的bean -->
<property name="car" value="#{car}"></property>
<!-- 使用spel来引用其它的bean的属性 -->
<property name="city" value="#{address.city}"></property>
<!-- 在spel中使用运算符,动态赋值 -->
<property name="info" value="#{car.price>300000? '金领':'白领'}"></property>
<property name="name" value="Tom"></property>
</bean>
IOC 容器中 Bean的生命周期方法
•Spring IOC 容器可以管理 Bean 的生命周期,Spring 允许在 Bean 生命周期的特定点执行定制的任务.
•Spring IOC 容器对 Bean的生命周期进行管理的过程:
–通过构造器或工厂方法创建 Bean实例
–为 Bean 的属性设置值和对其他Bean的引用
–调用 Bean的初始化方法
–Bean 可以使用了
–当容器关闭时,调用 Bean的销毁方法
public class Car {
private String brand;
public Car() {
System.out.println("car 的构造方法");
}
public void setBrand(String brand){
System.out.println("setBrand");
this.brand = brand;
}
private void init2() {
System.out.println("init.......");
}
private void destroy() {
System.out.println("destroy.........");
}
}
•在 Bean 的声明里设置init-method和destroy-method属性,为Bean指定初始化和销毁方法.
<bean id="car" class="com.atguigu.spring.beans.cycle.Car"
init-method="init2" destroy-method="destroy">
<property name="brand" value="Audi"></property>
</bean>
创建 Bean 后置处理器
•Bean 后置处理器允许在调用初始化方法前后对Bean 进行额外的处理.
•Bean 后置处理器对IOC 容器里的所有Bean 实例逐一处理,而非单一实例.其典型应用是:检查Bean属性的正确性或根据特定的标准更改Bean的属性.
<!-- 实现BeanPostProcessor接口,并提供两个方法的实现 -->
<!-- 配置bean的后置处理器 -->
<!-- bean:实例本身
beanName:bean名称
方法返回值:实际上返回给用户的那个bean,注意:可以在以上两个方法中修改返回的bean,
甚至返回一个新的bean
-->
<!-- 配置bean的后置处理器,不需要配置id,IOC容器自动识别是一个BeanPostProcessor -->
<bean class="com.atguigu.spring.beans.cycle.MyBeanPostProcessor"></bean>
- 添加 Bean 后置处理器后Bean的生命周期
- Spring IOC 容器对 Bean的生命周期进行管理的过程:
- 通过构造器或工厂方法创建 Bean实例
- 为 Bean 的属性设置值和对其他Bean的引用
- 将 Bean实例传递给 Bean后置处理器的 postProcessBeforeInitialization 方法
- 调用 Bean 的初始化方法
- 将 Bean实例传递给 Bean后置处理器的 postProcessAfterInitialization方法
- Bean 可以使用了
- 当容器关闭时, 调用Bean的销毁方法
工厂方法创建 Bean
通过调用静态工厂方法创建 Bean
•调用静态工厂方法创建Bean是将对象创建的过程封装到静态方法中.当客户端需要对象时,只需要简单地调用静态方法,而不同关心创建对象的细节.
/* * 静态工厂方法:直接调用某一个类的静态方法就可以返回bean的实例 */ public class StaticCarFactory { private static Map<String, Car> cars = new HashMap<>(); static{ cars.put("audi", new Car("audi",300000)); cars.put("ford", new Car("ford",400000)); } public static Car getCar(String name){ return cars.get(name); } }
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans-factory.xml"); //静态工厂方法测试 Car car1 = (Car) ctx.getBean("car1"); System.out.println(car1);
通过调用实例工厂方法创建 Bean
•实例工厂方法: 将对象的创建过程封装到另外一个对象实例的方法里.
当客户端需要请求对象时,
只需要简单的调用该实例方法而不需要关心对象的创建细节.
/*
* 实例工厂方法:实例工厂的方法,需要先创建工厂本身,再调用工厂的实例方法来返回bean实例
*/
public class InstanceCarFactory {
private Map<String, Car> cars = null;
public InstanceCarFactory(){
cars = new HashMap<String, Car>();
cars.put("audi", new Car("audi",30000));
cars.put("ford", new Car("ford",40000));
}
public Car getCar(String car){
return cars.get(car);
}
}
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans-factory.xml");
//实例工厂方法测试
Car car2 = (Car) ctx.getBean("car2");
System.out.println(car2);
实现 FactoryBeanSpringIOC 容器中配置 Bean
•Spring 中有两种类型的Bean,一种是普通Bean,另一种是工厂Bean,即FactoryBean.
•工厂 Bean 跟普通Bean不同,其返回的对象不是指定类的一个实例,其返回的是该工厂Bean的getObject方法所返回的对象
1.实现spring的FactoryBean接口
//自定义的factorybean需要实现FactoryBean接口
public class CarFactoryBean implements FactoryBean<Car>{
private String brand;
public void setBrand(String brand) {
this.brand = brand;
}
//返回bean的对象
@Override
public Car getObject() throws Exception {
// TODO Auto-generated method stub
return new Car(brand,500000);
}
//返回bean的类型
@Override
public Class<?> getObjectType() {
// TODO Auto-generated method stub
return Car.class;
}
//是否是单实例
@Override
public boolean isSingleton() {
// TODO Auto-generated method stub
return true;
}
}
2.配置bean
<!-- 通过FactoryBean来配置bean的实例
class:指向FactoryBean的全类名
property:配置FactoryBean的属性,
但实际返回的实例确是FactoryBean的geteObject方法返回的实例
-->
<bean id="car" class="com.atguigu.spring.factorybean.CarFactoryBean">
<property name="brand" value="BMW"></property>
</bean>
3.main方法测试
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans-beanfactory.xml");
Car car = (Car) ctx.getBean("car");
System.out.println(car);
在 classpath中扫描组件
组件扫描(componentscanning): Spring 能够从classpath下自动扫描,侦测和实例化具有特定注解的组件.
特定组件包括:
- @Component: 基本注解,标识了一个受Spring管理的组件
- @Respository:标识持久层组件
- @Service: 标识服务层(业务层)组件
- @Controller: 标识表现层组件
对于扫描到的组件,Spring 有默认的命名策略:使用非限定类名,第一个字母小写. 也可以在注解中通过 value 属性值标识组件的名称
当在组件类上使用了特定的注解之后,还需要在Spring的配置文件中声明 :
base-package 属性指定一个需要扫描的基类包,Spring 容器将会扫描这个基类包里及其子包中的所有类.
当需要扫描多个包时, 可以使用逗号分隔.
如果仅希望扫描特定的类而非基包下的所有类,可使用 resource-pattern属性过滤特定的类,示例:
子节点表示要包含的目标类,需要配合use-default-filters属性使用
子节点表示要排除在外的目标类
下可以拥有若干个和子节点
<!-- 指定Spring IOC容器扫描的包 -->
<!-- base-package:扫描当前包和他的子包 -->
<!-- resource-pattern:指定扫描的资源,只扫描repository下的类 -->
<context:component-scan
base-package="com.atguigu.spring.beans.annotation"
resource-pattern="repository/*.class"
>
</context:component-scan>
- 和子节点支持多种类型的过滤表达式:
组件装配
元素还会自动注册AutowiredAnnotationBeanPostProcessor(Bean后置处理器)实例,
该实例可以自动装配具有@Autowired 和@Resource 、@Inject注解的属性.
默认情况下, 所有使用@Authwired 注解的属性都需要被设置. 当Spring 找不到匹配的 Bean 装配属性时, 会抛出异常, 若某一属性允许不被设置, 可以设置@Authwired 注解的required 属性为false,
@Autowired(required=false)
默认情况下, 当 IOC 容器里存在多个类型兼容的 Bean 时, 通过类型的自动装配将无法工作. 此时有两种解决方案。
- 可以在 @Qualifier 注解里提供 Bean 的名称. Spring 允许对方法的入参标注 @Qualifiter 已指定注入 Bean 的名称
- 保持IOC中bean名称和声明时的属性名一致。可在注解后设置bean名称,或修声明时的改属性名。
@Authwired 注解也可以应用在数组类型的属性上, 此时 Spring 将会把所有匹配的 Bean 进行自动装配.
@Authwired 注解也可以应用在集合属性上, 此时 Spring 读取该集合的类型信息, 然后自动装配所有与之兼容的 Bean.
- @Authwired 注解用在 java.util.Map 上时, 若该 Map 的键值为 String, 那么 Spring 将自动装配与之 Map 值类型兼容的 Bean, 此时 Bean 的名称作为键值
使用 @Resource或@Inject自动装配Bean
- Spring 还支持 @Resource和@Inject注解,这两个注解和@Autowired注解的功用类似
- @Resource 注解要求提供一个 Bean 名称的属性,若该属性为空,则自动采用标注处的变量或方法名作为Bean 的名称
- @Inject 和 @Autowired注解一样也是按类型匹配注入的Bean,但没有 reqired 属性
- 建议使用 @Autowired 注解
泛型依赖注入
•Spring 4.x 中可以为子类注入子类对应的泛型类型的成员变量的引用
public class BaseRepository<T> {
}
public class BaseService<T> {
@Autowired
protected BaseRepository<T> repository;
public void add(){
System.out.println("add ....");
System.out.println(repository);
}
}
@Repository
public class UserRepository extends BaseRepository<User>{
}
@Service
public class UserService extends BaseService<User>{
}
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans-generic-di.xml");
UserService userService = (UserService) ctx.getBean("userService");
userService.add();
}
整合多个配置文件
- Spring 允许通过 将多个配置文件引入到一个文件中,进行配置文件的集成。这样在启动Spring容器时,仅需要指定这个合并好的配置文件就可以。
- import元素的resource属性支持Spring的标准的路径资源
SpringAOP
AOP 简介
- AOP(Aspect-Oriented Programming, 面向切面编程): 是一种新的方法论,是对传统OOP(Object-OrientedProgramming, 面向对象编程) 的补充.
- AOP 的主要编程对象是切面(aspect),而切面模块化横切关注点.
- 在应用 AOP 编程时,仍然需要定义公共功能,但可以明确的定义这个功能在哪里,以什么方式应用,并且不必修改受影响的类.这样一来横切关注点就被模块化到特殊的对象(切面)里.
- AOP 的好处:
- 每个事物逻辑位于一个位置,代码不分散,便于维护和升级
- 业务模块更简洁, 只包含核心业务代码.
AOP 术语
•切面(Aspect): 横切关注点(跨越应用程序多个模块的功能)被模块化的特殊对象
•通知(Advice): 切面必须要完成的工作
•目标(Target):被通知的对象
•代理(Proxy):向目标对象应用通知之后创建的对象
•连接点(Joinpoint):程序执行的某个特定位置:如类某个方法调用前、调用后、方法抛出异常后等。连接点由两个信息确定:方法表示的程序执行点;相对点表示的方位。例如ArithmethicCalculator#add()方法执行前的连接点,执行点为ArithmethicCalculator#add();方位为该方法执行前的位置
•切点(pointcut):每个类都拥有多个连接点:例如ArithmethicCalculator的所有方法实际上都是连接点,即连接点是程序类中客观存在的事务。AOP 通过切点定位到特定的连接点。类比:连接点相当于数据库中的记录,切点相当于查询条件。切点和连接点不是一对一的关系,一个切点匹配多个连接点,切点通过org.springframework.aop.Pointcut接口进行描述,它使用类和方法作为连接点的查询条件。
Spring AOP
•AspectJ:Java社区里最完整最流行的AOP框架.
•在 Spring2.0以上版本中,可以使用基于AspectJ注解或基于XML配置的AOP
1.加入jarbao
com.springsource.org.aopalliance-1.0.0.jar
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
spring-aop-4.0.0.RELEASE.jar
spring-aspects-4.0.0.RELEASE.jarcommons-logging-1.1.1.jar
spring-beans-4.0.0.RELEASE.jar
spring-context-4.0.0.RELEASE.jar
spring-core-4.0.0.RELEASE.jar
spring-expression-4.0.0.RELEASE.jar2.配置文件中加入aop命名空间
3.基于注解方式
配置文件中加入如下配置
<!-- 使aspectj注解起作用:自动为匹配的类生成代理对象 --> <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
把横切关注点的代码抽象到切面的类中
切面首先是一个IOC容器中的bean,即加入@Component注解
切面还需要加入@Aspect注解
在类中声明各种通知
声明一个方法
- 在方法前加入@Before注解
- 可以在通知方法中声明一个类型为JoinPoint的参数。然后就能访问链接细节,如方法名称和参数值。
//把这个类声明为一个切面:把该类放入到IOC容器中,再声明为一个切面
@Aspect
@Component
public class LoggingAspect {
//当调用一个目标方法,目标方法跟@Before声明的方法匹配的时候,aop框架自动为该方法所在类
//生成一个代理对象,然后调用方法之前执行标注的方法
//声明该方法是一个前置通知:在目标方法开始之前执行,*:作用于所有方法
//@Before标识这个方法是个前置通知, 切点表达式表示执行 ArithmeticCalculator 接口的 add() 方法.
* 代表匹配任意修饰符及任意返回值, 参数列表中的
.. 匹配任意数量的参数
@Before("execution(int com.auguigu.spring.aop.impl.ArithmeticCalculatorImpl.*(int, int))")
public void beforeMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
List<Object> args = Arrays.asList(joinPoint.getArgs());
System.out.println("The method "+methodName +" begins with "+args);
}
}
<!-- 使aspectj注解起作用:自动为匹配的类生成代理对象 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
在 Spring中启用AspectJ注解支持
•要在 Spring应用中使用AspectJ注解,必须在 classpath 下包含AspectJ 类库:aopalliance.jar、aspectj.weaver.jar和spring-aspects.jar
•将 aop Schema 添加到 根元素中.
•要在 SpringIOC 容器中启用 AspectJ注解支持,只要在 Bean配置文件中定义一个空的 XML 元素
•当 SpringIOC 容器侦测到 Bean 配置文件中的元素时,会自动为与AspectJ切面匹配的Bean创建代理.
用 AspectJ注解声明切面
- 要在 Spring中声明 AspectJ 切面, 只需要在IOC 容器中将切面声明为Bean 实例.当在SpringIOC 容器中初始化 AspectJ切面之后,Spring IOC 容器就会为那些与 AspectJ切面相匹配的Bean创建代理.
- 在 AspectJ 注解中, 切面只是一个带有@Aspect 注解的 Java类.
- 通知是标注有某种注解的简单的 Java 方法.
- AspectJ 支持5种类型的通知注解:
- @Before: 前置通知,在方法执行之前执行
- @After: 后置通知,在方法执行之后执行
- @AfterRunning: 返回通知,在方法返回结果之后执行
- @AfterThrowing: 异常通知,在方法抛出异常之后
- @Around: 环绕通知,围绕着方法执行
前置通知
•前置通知:在方法执行之前执行的通知
•前置通知使用 @Before注解,并将切入点表达式的值作为注解值.
后置通知
•后置通知是在连接点完成之后执行的,即连接点返回结果或者抛出异常的时候,下面的后置通知记录了方法的终止.
•一个切面可以包括一个或者多个通知.
返回通知
•无论连接点是正常返回还是抛出异常, 后置通知都会执行.
如果只想在连接点返回的时候记录日志, 应使用返回通知代替后置通知.•在返回通知中, 只要将returning属性添加到@AfterReturning注解中,就可以访问连接点的返回值.该属性的值即为用来传入返回值的参数名称.
•必须在通知方法的签名中添加一个同名参数. 在运行时,Spring AOP 会通过这个参数传递返回值.
•原始的切点表达式需要出现在 pointcut 属性中
异常通知
•只在连接点抛出异常时才执行异常通知
•将 throwing属性添加到 @AfterThrowing 注解中,也可以访问连接点抛出的异常.Throwable是所有错误和异常类的超类.所以在异常通知方法可以捕获到任何错误和异常.
•如果只对某种特殊的异常类型感兴趣,可以将参数声明为其他异常的参数类型.然后通知就只在抛出这个类型及其子类的异常时才被执行.
环绕通知
•环绕通知是所有通知类型中功能最为强大的,能够全面地控制连接点.甚至可以控制是否执行连接点.
•对于环绕通知来说, 连接点的参数类型必须是ProceedingJoinPoint. 它是 JoinPoint的子接口,允许控制何时执行, 是否执行连接点.
•在环绕通知中需要明确调用 ProceedingJoinPoint 的proceed() 方法来执行被代理的方法. 如果忘记这样做就会导致通知被执行了, 但目标方法没有被执行.
•注意: 环绕通知的方法需要返回目标方法执行之后的结果, 即调用joinPoint.proceed(); 的返回值, 否则会出现空指针异常
@Aspect
@Component
public class AspectJ {
@Before("execution(int com.atguigu.sprinig.aop.ArithmeticCalculatorImpl.*(int, int))")
public void beforeMethod(JoinPoint joinPoint){
String methodName=joinPoint.getSignature().getName();
List<Object> asList = Arrays.asList(joinPoint.getArgs());
System.out.println("执行了 "+methodName+" 方法"+"with args "+ asList);
}
/*
* 在方法正常结束后的代码
* 返回通知是可以访问到方法返回值的
*/
@AfterReturning(value="execution(int com.atguigu.sprinig.aop.ArithmeticCalculatorImpl.*(int, int))",returning="result")
public void afterreturning(JoinPoint joinPoint,Object result){
String methodName=joinPoint.getSignature().getName();
System.out.println("The Method "+methodName+"ends with "+result);
}
/*
* 目标方法出现异常时执行
* 可以访问异常对象,可以指定在出现特定异常时再执行通知代码
*/
@AfterThrowing(value="execution(int com.atguigu.sprinig.aop.ArithmeticCalculatorImpl.*(int, int))",throwing="ex")
public void afterreThrowing(JoinPoint joinPoint,Exception ex){
String methodName=joinPoint.getSignature().getName();
System.out.println("The Method "+methodName+" with "+ex);
}
/*
* 环绕通知,需要携带ProceedingJoinPoint类型的参数
* 类似于动态代理的全过程,ProceedingJoinPoint类型的参数可以决定是否执行目标方法
* 且环绕通知必须有返回值,返回值为目标方法的返回值
*/
@Around("execution(int com.atguigu.sprinig.aop.ArithmeticCalculatorImpl.*(int, int))")
public Object arountMethod(ProceedingJoinPoint pjd){
System.out.println("arountMethod");
Object result = null;
String methodNAme = pjd.getSignature().getName();
//执行目标方法
try {
//前置通知
System.out.println("The method "+methodNAme+" begins with "+Arrays.asList(pjd.getArgs()));
result = pjd.proceed();
//返回通知
System.out.println("The method "+methodNAme+" ends with "+result);
} catch (Throwable e) {
//异常通知
System.out.println("The method "+methodNAme+" occurs exception:"+e);
throw new RuntimeException(e);//将异常抛出去
}
//后置通知
System.out.println("The method "+methodNAme+" ends");
return result;
}
}
指定切面的优先级
•在同一个连接点上应用不止一个切面时,除非明确指定,否则它们的优先级是不确定的.
•切面的优先级可以通过实现 Ordered接口或利用@Order注解指定.
@Order值越小。优先级越高
重用切入点定义
引入通知(不常用)
•引入通知是一种特殊的通知类型.
它通过为接口提供实现类,
允许对象动态地实现接口,
就像对象已经在运行时扩展了实现类一样.
基于 XML—- 声明切面
•当使用 XML 声明切面时,需要在根元素中导入aopSchema
•在 Bean 配置文件中,所有的SpringAOP 配置都必须定义在 元素内部.对于每个切面而言,都要创建一个 元素来为具体的切面实现引用后端Bean实例.
•切面 Bean 必须有一个标示符,供元素引用
<!-- 配置切面的 bean. -->
<bean id="loggingAspect"
class="com.atguigu.spring.aop.xml.LoggingAspect"></bean>
<bean id="vlidationAspect"
class="com.atguigu.spring.aop.xml.VlidationAspect"></bean>
<!-- 配置 AOP -->
<aop:config>
<!-- 配置切点表达式 -->
<aop:pointcut expression="execution(* com.atguigu.spring.aop.xml.ArithmeticCalculator.*(int, int))"
id="pointcut"/>
<!-- 配置切面及通知 -->
<aop:aspect ref="loggingAspect" order="2">
<aop:before method="beforeMethod" pointcut-ref="pointcut"/>
<aop:after method="afterMethod" pointcut-ref="pointcut"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="pointcut" throwing="e"/>
<aop:after-returning method="afterReturning" pointcut-ref="pointcut" returning="result"/>
<!--
<aop:around method="aroundMethod" pointcut-ref="pointcut"/>
-->
</aop:aspect>
<aop:aspect ref="vlidationAspect" order="1">
<aop:before method="validateArgs" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
Spring对 JDBC的支持
JdbcTemplate
简介
•为了使 JDBC 更加易于使用,Spring 在 JDBCAPI 上定义了一个抽象层, 以此建立一个JDBC存取框架.
•作为 SpringJDBC 框架的核心, JDBC 模板的设计目的是为不同类型的JDBC操作提供模板方法. 每个模板方法都能控制整个过程,并允许覆盖过程中的特定任务.通过这种方式,可以在尽可能保留灵活性的情况下,将数据库存取的工作量降到最低.
使用 JdbcTemplate更新数据库
/*
* 执行批量更新
* 最后一个参数是Object[]的List类型,因为修改一条记录需要一个Object的数组,那么多条就需要多个Object的数组
*/
@Test
public void testBatchUpdate(){
String sql="insert into employees (last_name,email,dept_id) values(?,?,?)";
List<Object[]> batchArgs = new ArrayList<Object[]>();
batchArgs.add(new Object[]{"AA","aaa@atguigu.com",1});
batchArgs.add(new Object[]{"BB","aaa@atguigu.com",2});
batchArgs.add(new Object[]{"CC","aaa@atguigu.com",3});
batchArgs.add(new Object[]{"DD","aaa@atguigu.com",4});
jdbcTemplate.batchUpdate(sql, batchArgs);
}
/*
* 执行insert,update,delete
*/
@Test
public void testUpdate(){
String sql = "update employees set last_name=? where id=?";
jdbcTemplate.update(sql, "jack",5);
}
使用 JdbcTemplate查询数据库
/*
* 获取单个列的值,或做统计查询
* 使用queryForObject方法
*/
@Test
public void testQueryForObject2(){
String sql = "select count(id) from employees";
Long count = jdbcTemplate.queryForObject(sql, Long.class);
System.out.println(count);
}
/*
* 查到实体类的集合
* 注意:调用的不是queryforlist方法
*/
@Test
public void testQueryForList(){
String sql = "select id,last_name lastName,email from employees where id>?";
RowMapper<Employee> rowMapper = new BeanPropertyRowMapper<>(Employee.class);
List<Employee> employees = jdbcTemplate.query(sql, rowMapper, 5);
System.out.println(employees);
}
/*
* 从数据库获取一条记录,实际得到对应的一个对象
* 注意不是调用queryForObject(String sql, Class<Employee> requiredType, Object... args) 方法
* 而需要调用queryForObject(String sql, RowMapper<Employee> rowMapper, Object... args)方法
* 1.其中的rowMapper指定如何去映射结果集的行,常用的实现类为BeanPropertyRowMapper
* 2.使用SQl中列的别名完成列名和类的属性名的映射,例如last_name lastName
* 3.不支持级联属性,JdbcTemplate只是一个JDBC的小工具,不是ORM框架
*/
@Test
public void testQueryForObject(){
String sql = "select id,last_name lastName,email,dept_id as \"department.id\" from employees where id=?";
RowMapper<Employee> rowMapper = new BeanPropertyRowMapper<>(Employee.class);
Employee employee = jdbcTemplate.queryForObject(sql, rowMapper,2);
System.out.println(employee);
}
简化 JDBC 模板查询
•JdbcTemplate 类被设计成为线程安全的,所以可以再IOC容器中声明它的单个实例,并将这个实例注入到所有的DAO实例中.
•JdbcTemplate也利用了Java1.5 的特定(自动装箱,泛型,可变长度等)来简化开发
•Spring JDBC 框架还提供了一个JdbcDaoSupport类来简化DAO实现.该类声明了jdbcTemplate属性,它可以从IOC容器中注入,或者自动从数据源中创建.
扩展 JdbcDaoSupport(不推荐) 示例代码
在 JDBC 模板中使用具名参数
•在经典的 JDBC 用法中,SQL 参数是用占位符 ? 表示,并且受到位置的限制.定位参数的问题在于,一旦参数的顺序发生变化,就必须改变参数绑定.
•在 SpringJDBC 框架中, 绑定SQL参数的另一种选择是使用具名参数(namedparameter).
•具名参数: SQL按名称(以冒号开头)而不是按位置进行指定.具名参数更易于维护,也提升了可读性.具名参数由框架类在运行时用占位符取代
•具名参数只在 NamedParameterJdbcTemplate中得到支持
<!-- 配置NamedParameterJdbcTemplate,该对象可以使用具名参数,其没有无参构造器,所以必须为其构造器指定参数 -->
<bean id="namedParameterJdbcTemplate"
class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
<constructor-arg ref="dataSource"></constructor-arg>
</bean>
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
/*使用具名参数时可以使用update(String sql, SqlParameterSource paramSource)方法进行更新操作。
* 1.SQl语句中的参数名和类的属性一致
* 2.使用SqlParameterSource的BeanPropertySqlParameterSource实现类作为参数
*/
@Test
public void testNameParameterJdbcTemplate2(){
String sql = "insert into employees(last_name,email,dept_id)values(:lastname,:email,:deptid)";
Employee employee = new Employee();
employee.setLastname("XYZ");
employee.setEmail("asdf@wr.com");
employee.setDeptid(3);
SqlParameterSource paramSource = new BeanPropertySqlParameterSource(employee);
namedParameterJdbcTemplate.update(sql, paramSource);
}
/*可以为参数起名字,当参数多了比用"?"好维护
* 好处:若有多个参数,则不用再去对应位置,直接对应参数名,好维护
* 缺点:较为麻烦
*/
@Test
public void testNameParameterJdbcTemplate(){
String sql = "insert into employees(last_name,email,dept_id)values(:ln,:email,:deptid)";
Map<String, Object> paramMap = new HashMap<String, Object>();
paramMap.put("ln", "FF");
paramMap.put("email", "jklhk@asdasd.com");
paramMap.put("deptid", 2);
namedParameterJdbcTemplate.update(sql, paramMap);
}
Spring中的事务管理
•作为企业级应用程序框架, Spring 在不同的事务管理 API 之上定义了一个抽象层.而应用程序开发人员不必了解底层的事务管理API,就可以使用Spring的事务管理机制.
•Spring 既支持编程式事务管理,也支持声明式的事务管理.
•编程式事务管理: 将事务管理代码嵌入到业务方法中来控制事务的提交和回滚.在编程式管理事务时,必须在每个事务操作中包含额外的事务管理代码.
•声明式事务管理: 大多数情况下比编程式事务管理更好用.它将事务管理代码从业务方法中分离出来, 以声明的方式来实现事务管理.事务管理作为一种横切关注点,可以通过AOP方法模块化.Spring 通过 SpringAOP 框架支持声明式事务管理.
Spring声明式事务
1.配置xml文件
2.在方法前加@Transactional注解
事务传播属性
•当事务方法被另一个事务方法调用时,必须指定事务应该如何传播.例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行.
•事务的传播行为可以由传播属性指定.Spring 定义了 7 种类传播行为.
并发事务所导致的问题
•当同一个应用程序或者不同应用程序中的多个事务在同一个数据集上并发执行时,可能会出现许多意外的问题
•并发事务所导致的问题可以分为下面三种类型:
–脏读: 对于两个事物T1,T2, T1 读取了已经被T2更新但还没有被提交的字段. 之后,若T2回滚,T1读取的内容就是临时且无效的.
–不可重复读:对于两个事物T1,T2, T1 读取了一个字段,然后T2更新了该字段.之后,T1再次读取同一个字段, 值就不同了.
–幻读:对于两个事物T1,T2, T1 从一个表中读取了一个字段,然后T2在该表中插入了一些新的行.之后,如果T1再次读取同一个表,就会多出几行.
事务的隔离级别
•从理论上来说, 事务应该彼此完全隔离,以避免并发事务所导致的问题.然而,那样会对性能产生极大的影响,因为事务必须按顺序运行.
•在实际开发中, 为了提升性能,事务会以较低的隔离级别运行.
•事务的隔离级别可以通过隔离事务属性指定
Spring 支持的事务隔离级别
•事务的隔离级别要得到底层数据库引擎的支持,而不是应用程序或者框架的支持.
•Oracle支持的2种事务隔离级别:READ_COMMITED, SERIALIZABLE
•Mysql支持4中事务隔离级别.
设置回滚事务属性
•默认情况下只有未检查异常(RuntimeException和Error类型的异常)会导致事务回滚.而受检查异常不会.
•事务的回滚规则可以通过 @Transactional注解的rollbackFor和noRollbackFor属性来定义.这两个属性被声明为Class[]类型的,因此可以为这两个属性指定多个异常类.
–rollbackFor: 遇到时必须进行回滚
–noRollbackFor:一组异常类,遇到时必须不回滚
超时和只读属性
•由于事务可以在行和表上获得锁, 因此长事务会占用资源,并对整体性能产生影响.
•如果一个事物只读取数据但不做修改,数据库引擎可以对这个事务进行优化.
•超时事务属性: 事务在强制回滚之前可以保持多久.这样可以防止长期运行的事务占用资源.
•只读事务属性: 表示这个事务只读取数据但不更新数据,这样可以帮助数据库引擎优化事务.