首发日期:2018-08-25
修改日期:
1. 2018-08-29:针对排版问题进行了修改。主要是最前面那部分排版出了问题--一些序号排序失效了。
Spring的介绍
- Spring框架是目前java应用最广的框架。
- Spring是一个分层的JavaSE/EE full-stack(一站式) 轻量级开源框架 ,它为每一个层次(表现层、业务层、持久层)都提供了解决方案
- web层:springMVC【此文不涉及springMVC】
- service层:IoC,DI,AOP
- dao层:jdbc模板、Hibernate模板
- spring有几个特点功能【后面将讲这几个功能的好处】:
- IoC控制反转:将对象的生成权限交给spring,当一个对象需要其他对象时,spring会帮你生成对应的对象。
- DI:如果使用spring生成的类需要其他类或属性来协助运行,可以使用DI来进行注入。
- AOP切面编程:通过代理模式,spring可以对某个业务进行强化,而不影响原有的运行。
- spring提供了与其他框架整合的组件,比如支持Hibernate开发的HibernateTemplate、支持MyBatis开发的SqlSessionTemplate。
基本运行环境搭建
本次环境基于spring4.3.4
1.在官网下载:
下载地址:http://repo.spring.io/libs-release-local/org/springframework/spring/
2.解压压缩包:
- 重要文件夹:
- docs:spring的开发规范文档和api文档
- libs:spring的依赖包、文档(后缀javadoc)和源码(后缀source)
- schema:spring的配置文件的约束(xsd)。
3.在libs中提取依赖包:【如果你会maven,那么你可以百度一下尝试使用maven来管理依赖】
- spring核心容器包(spring的所有模块都构建在核心容器之上,所以导入核心容器包构建的就是最基础的运行环境)
- spring-beans-4.3.4.RELEASE.jar:
- spring-context-4.3.4.RELEASE.jar:
- spring-core-4.3.4.RELEASE.jar:
- spring-expression-4.3.4.RELEASE.jar:
- 日志接口包:commons-logging-1.2.jar【apache提供的,需要自己下载】【必须的】
- 【如果需要扩展日志功能,才需要导入,这里使用log4j】日志包:log4j-1.2.16.jar【apache提供的,需要自己下载】
ps:上面所引入的依赖包仅能实现基本功能,但比如spring的事务管理、aop这些还需要引入其他的包,这些包将在下面的各个模块中讲。
由于spring很多时候都是提出解决方案,所以下面介绍spring将根据解决方案来讲。
- IOC【解决了对象的创建问题】
- DI 【解决了属性注入问题】
- AOP【解决了业务对象解耦问题】
- 模板代码【解决了jdbc代码太繁琐的问题】
IoC
介绍:
- IoC全称Inversion of Control(控制反转)。
- IoC就是把对象的生成权限交给spring(原本情况是对象需要我们手动去new,现在声明+配置,spring就可以帮我们把需要的对象自动生成)
示例使用:
1.导入依赖包,IoC只需要基础的依赖包。【这里省去扩展日志依赖包】
2.创建一个用于被spring管理的类(由于仅作演示,所以创建一个简单的实体类):
package work.domain;
public class User {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User [name=" + name + ", age=" + age + "]";
}
}
3.在src目录下创建applicationContext.xml,在applicationContext.xml中把类配置给spring,让spring管理这个类的创建,:
- applicationContext.xml需要xsd约束,xsd在解压文件夹spring-framework-4.2.4.RELEASE\\docs\\spring-framework-reference\\html\\xsd-configuration.html的the beans schema中。【xsd-configuration.html是spring的配置文件的所有xsd配置的参考文件,可以根据不同配置来查找到不同的xsd配置】
- applicationContext.xml需要放置在classpath可搜索路径下,通常可以放在src目录下。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 配置bean,让spring管理这个类的创建,id用于标识这个类,class是类全限定名 -->
<bean id="user" class="work.domain.User"></bean>
</beans>
4.创建测试类,获取spring创建的对象:
package work.test;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import work.domain.User;
public class Demo1 {
@Test
public void test1() {
//读取上下文
ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");
//根据id来获取spring实例化对象
User user = (User) context.getBean("user");
user.setName("李雷");
user.setAge(9);
//能够使用就说明已经被实例化了
System.out.println(user);
}
}
上面的示例使用可以说明spring帮我们创建了对象。
使用说明:
-
如何在applicationContext.xml中配置:
-
xsd约束:
- xsd在解压文件夹
spring-framework-4.2.4.RELEASE\\docs\\spring-framework-reference\\html\\xsd-configuration.html
的the beans schema
中。【xsd-configuration.html是spring的配置文件的所有xsd配置的参考文件,可以根据不同配置来查找到不同的xsd配置】
- xsd在解压文件夹
-
bean
-
属性
- id:唯一标志性属性,可以用于标识spring管理的类。【在代码中,我们可以根据这个属性来获取对象】
- name:不是唯一标志性属性,可以用于标识spring管理的类。虽然可以重复,但为了不引起歧义,通常开发中也不能重复。
- class:需要实例化的类的路径,指明根据id或name获取类对象的时候,获取的是哪个类的。
-
示例:
<bean id="user" name="name_user" class="work.domain.User"></bean>
-
-
-
如何在代码中获取spring创建的对象:
- 先读取上下文,然后利用上下文的getBean方法获取实例化对象:
-
public void test1() { //读取上下文 ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml"); //获取方法1:根据id来获取spring实例化对象 User user = (User) context.getBean("user"); //获取方法2:也可以根据name来获取spring对象 User user2 = (User) context.getBean("name_user"); user.setName("李雷"); user.setAge(9); //能够使用就说明已经被实例化了 System.out.println(user); System.out.println(user2); }
-
- 先读取上下文,然后利用上下文的getBean方法获取实例化对象:
使用注意:
-
默认情况下,生成的类是单例的,并且spring一开启就会实例化对象。也就是说,默认情况下,在不同代码中多次使用一个类的对象都是同一个对象。【可以通过配置来改变,bean的scope属性可以配置对象是单例的还是多例的】
-
applicationContext.xml的文件名是可以改变的(但默认获取的时候文件名就是这个),如果你的文件名不是这个,创建工厂的时候需要传入其他的文件名:
new ClassPathXmlApplicationContext("文件名");
。 -
spring创建类对象的本质是通过工厂来获取类对象。
- 老版本的工厂类:BeanFactory
- BeanFactory:调用getBean的时候,才会生成类的实例。
- 新版本的工厂类:ApplicationContext【现在都用这个】
- ApplicationContext:加载配置文件的时候,就会将Spring管理的类都实例化
- ApplicationContext常见实现类主要有两个:
- ClassPathXmlApplicationContext :加载类路径下的配置文件
- FileSystemXmlApplicationContext :加载文件系统下的配置文件
- 还有一些例如XmlWebApplicationContext,AnnotationConfigApplicationContext,AnnotationConfigWebApplicationContext这些虽然也有用,但这里不涉及,所以不讲。
- 老版本的工厂类:BeanFactory
-
由于本质是工厂模式,所以它是有利于进行接口化编程的,我们可以在配置文件中很方便地更改它的实现类。比如:
-
修改前:(获取bean的时候通过id获取,存储的类型是接口,那么可以很方便地更改它的实现类)
<bean id="calc" class="work.domain.Calc">
-
修改后(只需要修改配置文件,就能给接口提供不同的实现类):
<!-- 假如功能升级了!给它一个新的实现类。 --> <bean id="calc" class="work.domain.SuperCalc">
-
Bean的实例化方式
-
spring创建的对象也可以称为bean
-
spring帮我们实例化的有几种方式,默认是调用无参构造方法来实例化。
-
默认无参实例化(所以如果没有无参构造函数,将报错):
-
<bean id="user" name="name_user" class="work.domain.User"></bean>
-
-
注入构造方法参数,带参数实例化:(这个涉及DI内容,具体后面讲)
-
<bean id="user" name="name_user" class="work.domain.User"> <!-- 向 User(String name, int age)构造方法注入参数,然后实例化 --> <constructor-arg name="name" value="lilei" ></constructor-arg> <constructor-arg name="age" value="18" ></constructor-arg> </bean>
-
-
调用静态实例工厂方法得到对象:
-
静态工厂编写
public class MyBeanFactory { public static User createUser() { return new User(); } }
-
applicationContext.xml编写
<!-- class是工厂类路径,factory-method是静态获取对象的方法 --> <bean id="factory_user" class="work.factory.MyBeanFactory" factory-method="createUser"></bean>
-
-
调用非静态实例工厂方法得到对象:
-
工厂编写
public class MyBeanFactory { public User createU() { return new User(); } }
-
applicationContext.xml编写
<!-- 先实例化工厂 --> <bean id="myBeanFactory" class="work.factory.MyBeanFactory" ></bean> <!-- 利用工厂对象来获取对象,factory-bean是上面工厂bean的id,factory-method是获取对象的方法名 --> <bean id="user" factory-bean="myBeanFactory" factory-method="createU"></bean>
-
-
Bean的作用范围的配置:
在上面说过了,默认情况下,Bean的创建是单例的,但是可以通过bean中的scope属性来配置Bean的创建方式(同时也有作用范围)。
- scope
- 取值:
- singleton:单例模式,只生成一个对象。
- prototype:多例的,每次通过上下文获取都是获取一个新的对象。
- request:一次请求创建一个实例。
- session:一次会话创建一个实例。
- 示例:
-
<bean id="userAction" class="work.action.UserAction" scope="prototype"></bean>
-
- 取值:
补充:
- 这里没有讲述基于p名称空间的属性注入和spEL的属性注入【其实挺重要的,但讲细的话太占空间】,有兴趣的可以自查。
DI:
- 依赖注入。依赖是指某些功能可能依赖某些属性,只有有了对应的属性才能执行功能。当使用了IOC之后,可能需要把某些属性(也包括对象)注入给生成的对象,那么可以使用DI。
- 比如你利用spring帮你新建了一个类,但是这个类需要另一个类来协助运行(好比CD机需要CD,没有CD的CD机用起来没意思),那么这时候怎么把另一个类传给这个类呢?DI就是解决这类问题的。
- DI是spring的一个很好的属性注入解决方案,如果你不使用DI的话,你可能需要使用接口或者通过继承类来完成属性注入。
- 其他方案探究:让这个类管理他的协助类,在构造函数中传参进来或者调用某些方法设置属性,但这会造成代码耦合性很高以及难以测试(因为要确保使用协助类之前,协助类要被初始化了)。
- 使用DI之后,当通过IoC来创建对象的时候(此时,依赖对象和被依赖对象都创建了),IoC也会同时处理依赖管理,帮助我们把协助类注入到目标类中(有构造器注入、set注入等方法)。
- 像IoC一样,依赖注入也是有利于接口化编程的,当我们注入的属性是一个对象时,如果使用接口名来管理注入,那么我们可以很轻易地更改注入的接口实现类。
属性注入:
属性注入是创建对象的时候,为对象注入属性。
-
构造方法方式注入:要求类中必须要有与注入参数对应的构造方法
-
类的编写:
package work.domain; public class User { private String name; private int age; private Account account; public User() { super(); } public User(String name, int age) { super(); this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public Account getAccount() { return account; } public void setAccount(Account account) { this.account = account; } }
-
applicationContext.xml的编写:
<bean id="account" class="work.domain.Account"></bean> <bean id="user" class="work.domain.User"> <!-- 向 User(String name, int age)构造方法注入参数,然后实例化 --> <!-- name是属性名,value是属性值,如果属性需要一个类对象,使用ref来注入,值为bean中的id或name --> <constructor-arg name="name" value="lilei" ></constructor-arg> <constructor-arg name="age" value="18" ></constructor-arg> <constructor-arg name="account" ref="account" ></constructor-arg> </bean>
-
-
set方法方式,要求类中必须提供注入参数对应的setter方法
-
类的编写:
package work.domain; public class User { private String name; private int age; private Account account; public User() { super(); } public User(String name, int age) { super(); this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public Account getAccount() { return account; } public void setAccount(Account account) { this.account = account; } }
-
applicationContext.xml的编写:
<bean id="account" class="work.domain.Account"></bean> <bean id="user" class="work.domain.User"> <!-- 使用setter来注入参数 --> <!-- name是属性名,value是属性值,如果属性需要一个类对象,使用ref来注入,值为bean中的id或name --> <property name="name" value="织女"></property> <property name="age" value="1000"></property> <property name="account" ref="account"></property> </bean>
-
-
上面提到了基本类型属性和对象类型属性的注入,由于集合类型多,所以留到这里再讲:
-
类的编写(包含了数组,list,set,map):需要提供setter
-
package work.domain; import java.util.List; import java.util.Map; import java.util.Set; public class CollectionBean { private String[] array; private List list; private Set set; private Map map; public void setArray(String[] array) { this.array = array; } public void setList(List list) { this.list = list; } public void setSet(Set set) { this.set = set; } public void setMap(Map map) { this.map = map; } public String[] getArray() { return array; } public List getList() { return list; } public Set getSet() { return set; } public Map getMap() { return map; } }
-
-
applicationContext.xml编写
-
<bean id="collecionBean" class="work.domain.CollectionBean" > <!--数组类型的属性注入,name是属性名,数组的元素使用list中的value包裹,也可以使用array中的value包裹;注入的是对象的时候,可以使用ref包裹 --> <property name="array"> <list> <value>牛郎</value> <value>织女</value> </list> </property> <!-- List类型的属性注入,name是属性名,数组的元素使用list中的value包裹 --> <property name="list"> <list> <value>二郎神</value> <value>华山</value> </list> </property> <!-- Set类型的属性注入,name是属性名,数组的元素使用set中的value包裹 --> <property name="set"> <set> <value>孙悟空</value> <value>白骨精</value> </set> </property> <!-- Map类型的属性注入,name是属性名,数组的元素使用entry包裹,如果注入的是对象,那么可以使用key-ref和value-ref来注入 --> <property name="map"> <map> <entry key="爱" value="520" ></entry> <entry key="一生一世" value="1314" ></entry> </map> </property> </bean>
-
-
-
注入其他bean对象:上面的都是一个类对象中的普通属性,但没有说到注入一个其他bean对象时怎么注入。
-
<bean id="productDao" class="cn.dao.ProductDao"></bean> <bean id="productService" class="cn.service.ProductService"> <!-- 注入其他bean对象,使用ref,ref是bean的id --> <property name="productDao" ref="productDao"/> </bean>
-
-
属性注入方式还有一种接口注入,接口注入通常使用于资源来自于外界的情况,比如当数据库连接资源来自于外部的时候就可以使用接口注入。这个知识点这里不讲,有兴趣自查。
补充:
- 还可以从Properties中读取数据来进行注入 ,这里不讲,有兴趣的可以自查【在事务管理中讲述了可以利用一种方式来读取】。
<context:property-placeholder location="classpath:jdbc.properties" /> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <!-- 引用properties文件中的数据 --> <property name="driverClass" value="${jdbc.driverClass}" ></property> <property name="jdbcUrl" value="${jdbc.url}" ></property> <property name="user" value="${jdbc.username}" ></property> <property name="password" value="${jdbc.password}" ></property> </bean> ```
-
IoC的注解方式:
- 现在已经更趋向于使用注解来配置IOC了。
- 注解可以减少XML配置。
- 但XML的配置方式也有好处:
- 统一管理、方便维护
- 可以配置第三方组件
依赖包
- 除了导入基础的核心容器包,还需要
spring-aop-4.3.4.RELEASE.jar
示例使用
-
applicationContext.xml配置
-
导入xsd:
- 在原来的xsd的基础上,加上context的,context在spring-framework-4.3.4.RELEASE/docs/spring-framework-reference/html/xsd-configuration.html的the context schema中
-
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
</beans>
-
applicationContext.xml中配置组件扫描:
- 在xml配置context:component-scan这个标签后,spring可以自动去扫描base-pack下面或者子包下面的java文件,如果扫描到有@Component @Controller@Service等这些注解的类,则把这些类注册为bean 。如果有多个包,可以使用
,
分隔。
<!-- 配置组件扫描,base-package是扫描的包,扫描的包下类的相关注解会把类注册成bean --> <context:component-scan base-package="work.domain"></context:component-scan>
- 在xml配置context:component-scan这个标签后,spring可以自动去扫描base-pack下面或者子包下面的java文件,如果扫描到有@Component @Controller@Service等这些注解的类,则把这些类注册为bean 。如果有多个包,可以使用
-
给类加上注解
@Component("person")
:
package work.domain;
import org.springframework.stereotype.Component;
//添加注解
@Component("person") //相当于在applicationContext.xml中配置<bean id="person" class="work.domain.Person" />
public class Person {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
上面配置完毕,就可以像XML配置方式一样使用Context上下文获取对象了。
bean配置注解介绍:
-
@Component:修饰一个类,代表这个类的对象交给spring去生成
- @Component("person") //相当于在applicationContext.xml中配置
<bean id="person" class="....." />
- @Component的衍生注解,下面三个注解与@Component的功能一样,不过多了层次的意义,可以使用下面的来表明是什么层次的bean。【听说后面版本下面的三个注解加了一些功能,没去了解】
- @Controller:web层
- @Service:service层
- @Responsitory:dao层
- @Component("person") //相当于在applicationContext.xml中配置
Bean的作用范围的配置
在类上使用@Scope注解指定Bean的作用范围。
-
@Scope
-
属性:
- singleton:单例模式
- prototype:多例
- request:存入到request中
- session:存入到session中
-
示例:
-
@Component @Scope("prototype")
-
-
DI的注解方式
注解方式的属性注入:
对于类的注解,需要开启IoC上面的组件扫描【组件扫描的时候也会扫描到用于属性注入的注解】;如果仅仅使用注解来进行属性注入,则不需要组件扫描,直接使用下面的。【很多时候,我们可能会使用xml来配置bean,注解来配置属性注入】
<!--只会扫描属性上的注解-->
<context:annotation-config></context:annotation-config>
-
对于普通类型的属性:
- @value("属性值")
- 例如:
-
对于对象类型的属性:
- @Autowired:根据类名去进行注入
- 歧义性问题:部分时候都是采用接口化编程,使得一个接口可能有多个实现类,那么需要使用告诉spring使用哪个类。
- @Primary用在接口的实现类中,表明使用@Autowired来进行注入的时候,如果有多个实现类,那么优先使用这个。
- @Qualifier与@Autowired一起使用,@Qualifier("xxx")的时候,代表使用xxx实现类注入。
- 歧义性问题:部分时候都是采用接口化编程,使得一个接口可能有多个实现类,那么需要使用告诉spring使用哪个类。
- @Resource(name = "....") :根据id去进行注入(对于注解式的,@Component("xxx")中的xxx就是id)【由于@Autowired有歧义问题,所以通常使用@Resource】
- @Autowired:根据类名去进行注入
-
对于集合类型的属性,我觉得使用注解就不太清晰了。有兴趣的可以自查,它通常使用@Resource来注入。
-
DI注解的使用:如果有setter方法,就把注解添加到set方法上;如果没有,那么添加到属性上;两者都有的时候建议添加到set方法上。
AOP
- AOP全称Aspect Oriented Programming (面向切面编程)。
- AOP可以做到在不改变程序原代码的情况下对程序进行增强。
- 诸如日志、事务管理和安全这种服务经常用来配合业务,如果把这些服务的代码写在各个业务中,当需要修改,你需要修改很多地方,除此还会可能造成业务逻辑混乱。(业务混乱是指影响了业务实体,增加了不必要的概念,好比某个业务的运行需要先初始化日志对象,这样就影响了本来业务的执行。)
- AOP通过声明把这类服务应用到它们需要影响的业务中(不需要改变业务代码)。
- AOP有点类似于拦截器,拦截器是发起请求之后先到拦截器,再到业务逻辑,出去再经过拦截器,拦截器也是脱离了业务逻辑的。
- 在声明之后,AOP会对目标方法所在的类生成一个代理类对象,这个代理类对象中的目标方法是使用AOP加强过了的。
- 以事务的理念来谈一下AOP:通常来说,我们的增删改操作需要事务的帮助,我们可能需要在各处代码中都写下开启事务的代码。这样代码可能就很赘余了,而且事务的代码变成了必须品。如果使用了AOP,那么调用方法之前,AOP帮你开启事务,这样你就不必在代码里写下开启事务了。
动态代理技术简单演示
spring AOP的底层实现技术就是动态代理技术。为了帮助了解AOP底层原理,这里基于jdk动态代理对动态代理技术简单演示。【仅作理解,不写太严谨的代码】
1.创建一个接口
public interface SchoolPerson {
public void read();
public void save();
}
2.定义一个实现接口的类
package work.domain;
public class Student implements SchoolPerson {
public void read() {
System.out.println("read");
}
public void save() {
System.out.println("save");
}
}
3.创建一个代理器,实现InvocationHandler接口,创建createProxy方法--负责获取代理对象,创建invoke方法--负责对方法进行增强。
package work.utils;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import work.domain.SchoolPerson;
public class JDKProxy implements InvocationHandler {
//被代理的对象
private SchoolPerson sp;
//把对象传给代理器,让它生成一个代理对象
public JDKProxy(SchoolPerson sp) {
this.sp = sp;
}
//生成代理对象,并绑定代理方法
public SchoolPerson createProxy() {
SchoolPerson proxyStu = (SchoolPerson) Proxy.newProxyInstance(sp.getClass().getClassLoader(), sp.getClass().getInterfaces(), this);
return proxyStu;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(method.getName().equals("save")) {
System.out.println("增强功能执行---。");
return method.invoke(sp, args);
}
return method.invoke(sp, args);
}
}
4.测试:
package work.test;
import org.junit.Test;
import work.domain.SchoolPerson;
import work.domain.Student;
import work.utils.JDKProxy;
public class Demo2 {
@Test
public void test1() {
SchoolPerson sp=new Student();
JDKProxy proxy = new JDKProxy(sp);
SchoolPerson createProxy = proxy.createProxy();
//调用方法,测试是否被增强
createProxy.save();
/*测试结果:
* 增强功能执行---。
save
*/
}
}
上述例子可以看出,student对象原有的save方法被增强了,而且我们并没有修改过student的代码。
AOP相关术语:
-
Aspect:切面,切面是指贯穿多个业务的环境。比如拦截器就贯穿了业务逻辑。
-
Advice:通知、增强。是指增强的方法,根据逻辑和顺序可以区分成多个通知类型(下面讲)
-
Joinpoint:连接点,可拦截点,判断一个点是否是切点。
-
Pointcut:切点,被切面拦截的点就是一个拦截点。
-
Target:目标,被增强的对象
-
Weaving:织入,生成代理对象的过程
-
Proxy:代理对象
通知类型:
-
前置通知before:在目标方法之前执行通知里面的操作
-
正常返回通知after-returning:目标方法正常执行完成之后,进行通知里面的操作
-
环绕通知around:目标方法执行之前和目标方法执行完成之后,进行通知里面的操作
-
异常抛出通知after-throw:发生异常时,进行通知里面的操作
-
后置(最终)通知after:运行完成时,无论是否发生异常,都进行通知里面的操作
-
五种通知的执行顺序为: 前置通知→环绕通知→后置通知→正常返回通知/异常抛出通知
使用示例:
-
导入依赖包:
- 基本包:
- AOP依赖包
- 两个spring自带的:
- spring-aop-4.3.4.RELEASE.jar
- spring-aspects-4.3.4.RELEASE.jar
- 三个需要下载的:
- aopalliance-1.0.jar
- aspectjrt-1.7.2.jar
- aspectjweaver-1.7.2.jar
- 两个spring自带的:
-
编写一个接口:
-
package work.dao; public interface StudentDao { public void read(); public void save(); }
-
-
编写一个接口的实现类:
-
package work.dao.impl; import work.dao.StudentDao; public class StudenDaoImpl implements StudentDao { @Override public void read() { System.out.println("read"); } @Override public void save() { System.out.println("save"); } }
-
-
编写一个切面类,用于调用切面类的方法对目标对象增强:
-
package work.utils; import org.aspectj.lang.ProceedingJoinPoint; public class MyAspect { public void before() { System.out.println("before。。。"); } public void after() { System.out.println("after。。。"); } public void afterReturn() { System.out.println("afterReturn。。。"); } public void around(ProceedingJoinPoint pj) throws Throwable { System.out.println("around in。。。"); pj.proceed(); System.out.println("around out。。。"); } public void afterThrow() { System.out.println("afterThrow。。。"); } }
-
-
配置applicationContext.xml文件
-
导入xsd,他在xsd-configuration.html的the aop schema中(如果你有其他功能,注意进行保留,在原来的基础上增加aop的xsd)
-
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> </beans>
-
-
把接口实现类交给Spring去管理
-
<!-- 把目标实现类交给spring管理 --> <bean id="studentDao" class="work.dao.impl.StudenDaoImpl"></bean>
-
-
-
把切面交给Spring去管理
-
<!-- 把切面类交给spring管理 --> <bean id="myAspect" class="work.utils.MyAspect"></bean>
-
-
配置AOP
-
<!-- 配置AOP --> <aop:config> <!-- 配置切点,配置哪些类的哪些方法需要增强 --> <aop:pointcut expression="execution(* work.dao.impl.StudenDaoImpl.*(..))" id="pointcut1"/> <!-- 配置切面,配置通知与切点的关系 --> <aop:aspect ref="myAspect"> <aop:before method="before" pointcut-ref="pointcut1"/> <aop:after method="after" pointcut-ref="pointcut1" /> <aop:after-returning method="afterReturn" pointcut-ref="pointcut1" /> <aop:around method="around" pointcut-ref="pointcut1" /> </aop:aspect> </aop:config>
-
-
编写测试方法:
-
//测试AOP @Test public void test2() { ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml"); StudentDao studentDao = (StudentDao) context.getBean("studentDao"); studentDao.read(); }
-
使用详解:
AOP配置结构
-
<!-- aop:config是配置AOP的顶层标签 --> <aop:config> <!-- aop:pointcut配置切点,配置哪些类的哪些方法需要增强 --> <aop:pointcut expression="..." id="..."/> <!-- aop:aspect配置切面 --> <aop:aspect ref="myAspect"><!-- ref引用的是切面类的bean --> <!--配置对于切点怎么增强 --> <aop:before method="..." pointcut-ref="..."/> <aop:after method="..." pointcut-ref="..." /> <aop:after-returning method="..." pointcut-ref="..." /> <aop:around method="..." pointcut-ref="..." /> </aop:aspect> </aop:config>
切点:
在xml中,用<aop:pointcut expression="..." id="..."/>
来声明一个切点,使得增强可以针对一个切点来进行。其中id是用于标识一个切点的;expression是用来表示切点的位置的,表示增强用于哪个类的哪个方法上。
- expression的写法
- 语法格式:
expression="execution(访问修饰符 返回类型 包名.类名.方法名(参数) )"
- execution()代表方法执行时触发
- 访问修饰符(public之类的)是可选的。
- 返回类型是必需的,可以使用
*
来代表任意返回值类型的 - 包名.类名 ,可以使用
*
来代表任意包任意类,但注意要符合层次比如work.demo.dao可以用*.*.*
代替 - 方法名,可以使用
*
来代表任意方法 - 参数中填写的是参数类型,(..)表示任意个数任意类型参数,(.)表示有一个参数 ,(*,String)表示第一个参数为任意,第二个为String类型
- 语法格式:
- expression的示例:
public void com.domain.Custom.save(..)
、* *.*.*.save(..)
- expression的语法类似于正则表达式,这里仅仅讲了一些基础的,足够你基本使用的。如果想了解更多,可以自查。
切面:
在xml中,用<aop:aspect ref="..."></aop:aspect>
来声明一个切面,声明切面怎么对切点增强。
- aop:aspect 的属性:
- ref用来引用spring管理的切面类的bean。
- order:用与多个切面处理同一个切点的时候,决定它们之间的顺序。没有的时候,多个切面处理一个切点的时候,顺序是乱的,如果想有顺序的,需要使用order。
- 通知类型【aop:aspect 下使用各种通知类型来定义什么时候对切点进行增强】
- 前置通知:
<aop:before method="调用的方法" pointcut-ref="切点id"/>
- 后置通知:
<aop:after method="调用的方法" pointcut-ref="切点id" />
- 环绕通知:
<aop:around method="调用的方法" pointcut-ref="切点id" />
- 返回通知:
<aop:after-returning method="调用的方法" pointcut-ref="切点id" />
- 异常抛出通知:
<aop:after-throwing method="调用的方法" pointcut-ref="切点id" />
- 前置通知:
切面类:
切面类是用来对切点增强的,在xml中配置的通知类型的method就是切面类中的方法名。
通知类型有不同的作用,所以切面类也需要规范写法。
-
前置通知before可以用来获取切点信息,在方法中需要定义一个形参
JoinPoint pj
,这个形参会自动传入。-
public void before(JoinPoint jp) { System.out.println("before。。。"+jp); }
-
-
正常通知after-returning既可以用来获取切点信息,也可以获取目标方法的返回值。如果在目标方法中进行了返回,可以在xml中使用returning属性来指明存储到哪个变量中。
- xml中:
<aop:after-returning method="afterReturn" pointcut-ref="pointcut1" returning="result" />
【returning中的值是通知中的形参的名字。通知中通过形参来获取目标方法的返回值】 - 当获取返回值的时候要注意环绕通知的使用,获取返回值需要在环绕通知中使用
return pj.proceed();
- xml中:
-
环绕通知可以用来拦截方法,所以它需要“放行”才能正常环绕,在方法中需要定义一个形参
ProceedingJoinPoint pj
(会自动传入值),然后pj.proceed()就可以放行了。-
public void around(ProceedingJoinPoint pj) throws Throwable { System.out.println("around in。。。"); Object obj=pj.proceed(); System.out.println("around out。。。"); return obj; }
-
-
异常抛出通知可以获取目标方法的异常信息
补充
- 还可以对通知类型传入参数,这样在切面的类的方法中可以根据参数来执行。这里不讲,有兴趣自查。
AOP的注解方式:
- AOP的注解方式不需要引入额外的其他依赖包,只需要引入前面的核心容器包和aop包即可。
使用示例
开启自动代理
- 在xml中开启AOP自动代理【其实也可以全注解配置--可以创建一个java类来进行配置,但这里不讲。】
-
<aop:aspectj-autoproxy></aop:aspectj-autoproxy><!-- 这个标签与bean同级,不要配错了。 -->
-
创建切面并定义通知
使用@Aspect来注解一个类,Spring就会认为这个类是一个切面类。使用@Before来注解一个方法,Spring就会把这个方法当成切面类的前置通知。
package work.utils;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
//注解,标明这个是一个切面类
@Aspect
public class MyAspect {
//前置通知
@Before(value="execution(* work.dao.impl.StudenDaoImpl.*(..))")
public void before() {
System.out.println("before。。。");
}
}
这样之后,获取的对象就是被AOP前置增强了的。
使用详解:
通知类型:
- 前置通知:@Before
- 正常返回通知:@AfterReturning
- 环绕通知:@Around
- 异常抛出通知:@AfterThrowing
- 后置通知:@After
### 切点:
-
当在通知中同时声明一个切点的时候,使用的是切点表达式,可以参考上面XML中的切点表达式的写法。
-
除了切点表达式,还可以使用注解声明切点,然后在通知是使用切点名即可。
-
使用@Pointcut注解来声明切点,它使用在方法上,这个方法是没有意义的,可以为空方法,方法名相当于切点的ID。Pointcut的值为切点表达式。可以在通知中使用 类名.方法名() 来指向一个切点。
-
package work.utils; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; //注解,标明这个是一个切面类 @Aspect public class MyAspect { @Pointcut(value="execution(* work.dao.impl.StudenDaoImpl.*(..))") public void pointcut1() {} // @Before(value="execution(* work.dao.impl.StudenDaoImpl.*(..))") @Before(value="MyAspect.pointcut1()") public void before() { System.out.println("before。。。"); } }
-
-
补充:
- 多个切面对一个切点增强时,如果想要按顺序处理,可以在切面类上使用注解@Order,Order的值影响它们的顺序。
Spring与数据库:
- 对于非框架式的JDBC数据库编程,Spring提供jdbc模板来简化jdbc的使用
- 对于Hibernate框架,Spring提供了Hibernate模板来简化Spring整合Hibernate时对Hibernate的操作。
- 对于MyBatis框架,Spring没有提供很好的MyBatis模板,通常使用MyBatis社区开发了MyBatis-Spring整合包,其中SqlSessionTemplate就相当于MyBatis模板。
- 对于Hibernate和MyBatis框架,这里不涉及框架整合知识,所以这里只介绍一下jdbc模板。[整合框架的内容迟一点写]
JDBC模板使用示例
- JDBC模板可以说是Spring进行jdbc的封装,它的使用就像DbUtils。
1. 引入依赖包:
- spring核心容器基本包
- 数据库驱动包:mysql-connector-java-5.1.7-bin.jar
- Spring的JDBC包:spring-jdbc-4.3.4.RELEASE.jar
- Spring的事务包(需要事务时才导入):spring-tx-4.3.4.RELEASE.jar
2. 创建数据连接资源(这里以简单数据库连接池为例)
SimpleDriverDataSource dataSource = new SimpleDriverDataSource();
dataSource.setDriverClass(com.mysql.jdbc.Driver.class);
dataSource.setUrl("jdbc:mysql://localhost:3306/spring");
dataSource.setUsername("root");
dataSource.setPassword("123456");
3. 获取Jdbc模板
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
4.使用jdbc模板操作数据库
这里以保存数据为例:
@Test
public void test3() {
//创建简单连接池
SimpleDriverDataSource dataSource = new SimpleDriverDataSource();
dataSource.setDriverClass(com.mysql.jdbc.Driver.class);
dataSource.setUrl("jdbc:mysql://localhost:3306/spring");
dataSource.setUsername("root");
dataSource.setPassword("123456");
//获取jdbc模板
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
//操作数据库
jdbcTemplate.update("insert into userinfo values(null,?,?) ","李雷",20);
}
使用详解
jdbc模板的使用
-
新增、修改、删除:jdbc模板的的增删改主要是通过update方法来与数据库交互的。
-
public void test4() { //获取jdbc模板 ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); JdbcTemplate jdbcTemplate = (JdbcTemplate) context.getBean("jdbcTempldate"); //操作数据库 jdbcTemplate.update("insert into userinfo values(null,?,?) ","韩雷",20); jdbcTemplate.update("delete from userinfo where id=3"); jdbcTemplate.update("update userinfo set name=\'地雷\' where id=?", 4); }
-
-
查询:
-
查询主要依靠query方法和queryForObject方法,他们的使用与DbUtils中的查询使用类似,都是需要传入一个结果集处理对象
-
下面只给出一个小例子(这个例子是使用了spring注入生成template的结果,配置方法在下下份代码中),基本跟DbUtils的用法没什么区别,所以你也可以自己把它封装到对象中
@Test public void test5() { //获取jdbc模板 ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); JdbcTemplate jdbcTemplate = (JdbcTemplate) context.getBean("jdbcTempldate"); //操作数据库 jdbcTemplate.query("select * from userinfo", new RowMapper() { @Override public Object mapRow(ResultSet rs, int rowNum) throws SQLException { String name=rs.getString("name"); int age=rs.getInt("age"); System.out.println(name+":"+age); return null; } }); }
-
queryForObject可以用来获取单个数据,也可以用来像query一样用结果集处理对象来处理数据
@Test public void test5() { //获取jdbc模板 ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); JdbcTemplate jdbcTemplate = (JdbcTemplate) context.getBean("jdbcTempldate"); Long num = jdbcTemplate.queryForObject("select count(*) from userinfo",Long.class); System.out.println(num); }
-
-
上面讲了jdbc模板的使用涉及spring管理jdbc模板,了jdbc模板也可以交给spring去管理。
-
<!-- class里面写dbcp的类,使用dataSource获取的时候就是dbcp连接池对象 --> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"> <!-- 由于里面有很多setter,可以直接属性注入 --> <property name="driverClassName" value="com.mysql.jdbc.Driver" ></property> <property name="url" value="jdbc:mysql://localhost:3306/spring" ></property> <property name="username" value="root" ></property> <property name="password" value="123456" ></property> </bean> <bean id="jdbcTempldate" class="org.springframework.jdbc.core.JdbcTemplate"> <!-- jdbc模板需要数据库连接池,这里注入一个dbcp的连接池对象 --> <property name="dataSource" ref="dataSource"></property> </bean>
-
配置数据库资源
如果你之前学过第三方数据库连接池的配置的话,你应该了解到他们都是DataSource接口的实现类,再想想上面的IoC,所以其实Spring中配置数据库资源就是将连接池类交给Spring去管理。(如果是在代码中创建数据库连接资源的话,像普通的编写即可,跟Spring没关系)
- 配置DBCP连接池:
- 引入dbcp依赖包:
- commons-dbcp-1.4.jar
- commons-pool-1.5.6.jar
- 配置DBCP:
- 让spring管理dataSource对象。
- 引入dbcp依赖包:
<!-- class里面写dbcp的类,使用dataSource获取的时候就是dbcp连接池对象 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<!-- 由于里面有很多setter,可以直接属性注入 -->
<property name="driverClassName" value="com.mysql.jdbc.Driver" ></property>
<property name="url" value="jdbc:mysql://localhost:3306/spring" ></property>
<property name="username" value="root" ></property>
<property name="password" value="123456" ></property>
</bean>
- 配置C3P0连接池:
- 引入依赖包:
- c3p0-0.9.1.2.jar
- 配置连接池,把连接池交给spring管理:
- 引入依赖包:
<!-- class里面写c3p0的类,使用dataSource获取的时候就是c3p0连接池对象 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!-- 由于里面有很多setter,可以直接属性注入 -->
<property name="driverClass" value="com.mysql.jdbc.Driver" ></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring" ></property>
<property name="user" value="root" ></property>
<property name="password" value="123456" ></property>
</bean>
引入外部配置文件:
在上面的连接池配置过程中,你可能会发现一个问题,以往会使用配置文件来解决在代码中把参数写成固定值的问题,在上面中也是使用了固定值,那么怎么使用外部配置文件来解决这个问题呢?
1.引入Property配置文件:【注意一下下面标签是以context开头的,所以它需要什么xsd,你懂的】
<context:property-placeholder location="classpath:jdbc.properties" />
2.根据配置文件中的key【要求key是点分的多单词,测试过单个单词无法获取】,引用配置文件中的参数:
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driverClass}" ></property>
<property name="jdbcUrl" value="${jdbc.url}" ></property>
<property name="user" value="${jdbc.username}" ></property>
<property name="password" value="${jdbc.password}" ></property>
</bean>
给一下配置文件的写法:
jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring
jdbc.username=root
jdbc.password=123456
事务管理
- 在上面中讲述了Spring对数据库操作的支持,但没有涉及事务管理,并且实质上jdbc模板是没有自带事务管理的。
- Spring对事务的管理是通过PlatformTransactionManager管理的。
Spring用于管理事务的几个类
-
PlatformTransactionManager:一个接口,spring用于管理事务
- DataSourceTransactionManager:PlatformTransactionManager的实现类,可用于jdbc模板和MyBatis框架
- HibernateTransactionManager:PlatformTransactionManager的实现类,主要针对Hibernate框架
-
TransactionDefinition:事务定义信息,包含了一些关于事务管理的变量(隔离级别、传播行为等等),spring会把方法\\类中配置的事务相关属性装载到TransactionDefinition中。
-
TransactionStatus:事务的状态
-
平台事务管理器PlatformTransactionManager会根据事务定义信息TransactionDefinition的信息来进行事务管理。事务管理中产生的状态信息存储到TransactionStatus中。
声明式事务管理
xml方式【XML的方式有很多种,例如拦截器方式、通知方式,这里介绍通知方式】:
1.导入事务管理的xsd:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
</beans>
2.配置事务管理器
<!--使用事务管理器,必须注入一个dataSource -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
3.配置事务的增强,把事务管理器当成切面类来对方法增强,相当于给方法配置一个事务管理增强。
-
<!-- 把spring管理的transactionManager赋给transaction-manager属性 --> <tx:advice id="txAdvice" transaction-manager="transactionManager" > <tx:attributes> <!-- name写要进行事务管理的方法名 --> <tx:method name="transfer" /> </tx:attributes> </tx:advice>
4.AOP配置,把上面的事务增强配置给哪个切点,这些切点就是需要事务管理的地方:
<aop:config>
<!-- 这里的切点注意要写把多个数据库操作集合起来的方法 -->
<aop:pointcut expression="execution(* work.service.impl.AccountServiceImpl.*(..))" id="pointcut1"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut1"/>
</aop:config>
5.测试类的编写:
注解式
1.导入事务管理tx和aop的xsd
2.配置事务管理器
-
<!--使用事务管理器,必须注入一个dataSource --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean>
3.开启注解式事务
-
<tx:annotation-driven transaction-manager="transactionManager"/>
4.在业务层增加@Transactional注解:
- 使用在类上,代表类的所有方法都使用事务管理
- 也可以仅使用在某个方法上。
使用详解:
-
事务管理器配置
-
spring管理事务依靠事务管理器,所以事务管理器是必须的,无论注解式还是XML式都需要。对于MyBatis和JDBC可以使用DataSourceTransactionManager作为实现类,对于Hibernate可以使用HibernateTransactionManager作为实现类
-
事务管理器必须注入一个dataSource
-
<!--使用事务管
以上是关于Spring从认识到细化了解的主要内容,如果未能解决你的问题,请参考以下文章
如何从 recyclerview 片段传递到另一个 recyclerview 片段
-