一、spring配置和简介
1. spring概述
前置:创建spring项目
1.导入空项目
2.创建maven模块
3.将模块添加为web project structure—>
4.修改目录
1.1 Spring是什么(理解)
Spring是分层的 Java SE/EE应用 full-stack 轻量级开源框架,以 IoC(Inverse Of Control:反转控制)和 AOP(Aspect Oriented Programming:面向切面编程)为内核。
提供了展现层 SpringMVC和持久层 Spring JDBCTemplate以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多著名的第三方框架和类库,逐渐成为使用最多的Java EE 企业应用开源框架
1.2 Spring发展历程 (了解)
Rod Johnson ( Spring 之父)
2017 年
9 月份发布了 Spring 的最新版本 Spring5.0
通用版(GA)
1.3 Spring的优势(理解)
1)方便解耦,简化开发
2)AOP 编程的支持
3)声明式事务的支持
4)方便程序的测试
1.4 Spring的体系结构(了解)
![image-20201005235643035](https://i-blog.csdnimg.cn/blog_migrate/3e223884db992a40c5986a50877a7928.png)
2.spring快速入门
2.1 spring开发步骤
①导入 Spring 开发的基本包坐标
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<!--junit用于单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
②编写 Dao 接口和实现类
③创建 Spring 核心配置文件
④在 Spring 配置文件中配置 UserDaoImpl(通过类的全限定名,反射原理)
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"></bean>
⑤使用 Spring 的 API 获得 Bean 实例
通过配置文件中的类的全限定名创建类对象,
从而操作这个类(创建实例等),
不需要开发人员手动创建。
//根据配置文件获取ApplicationContext对象
ApplicationContext app=new ClassPathXmlApplicationContext("applicationContext.xml");
//调用ApplicationContext对象getBean方法,根据bean id获取类实例
UserDao userDao=(UserDao) app.getBean("userDao");//父类引用指向子类对象
userDao.save();
3. spring配置文件
3.1 Bean标签基本配置
1)默认情况下,反射是通过无参构造创建对象的,spring也是
2)每个类都有一个无参数的默认构造函数
基本属性:
id:Bean实例在容器中的唯一标识
class:Bean的全限定名称
<bean>标签
id属性:在容器中Bean实例的唯一标识,不允许重复
class属性:要实例化的Bean的全限定名
scope属性:Bean的作用范围,常用是Singleton(默认)和prototype
<property>标签:属性注入
name属性:属性名称
value属性:注入的普通属性值
ref属性:注入的对象引用值
<list>标签
<map>标签
<properties>标签
<constructor-arg>标签
<import>标签:导入其他的Spring的分文件
3.2 Bean标签范围配置
scope:指对象的作用范围,取值如下:
取值范围 | 说明 |
---|---|
singleton | 默认值,单例的 |
prototype | 多例的,每次获得新对象 |
request | WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 request 域中 |
session | WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 session 域中 |
global session | WEB 项目中,应用在 Portlet 环境,如果没有 Portlet 环境那么globalSession 相当于 session |
重点研究singleton和prototype:
1)当scope的取值为singleton时
Bean的实例化个数:1个
Bean的实例化时机:当Spring核心文件被加载时,实例化配置的Bean实例
Bean的生命周期:
对象创建:当应用加载,创建容器时,对象就被创建了,(饿汉式)
对象运行:只要容器在,对象一直活着
对象销毁:当应用卸载,销毁容器时,对象就被销毁了
2)当scope的取值为prototype时
Bean的实例化个数:多个
Bean的实例化时机:当调用getBean()方法时实例化Bean
对象创建:当使用对象时,创建新的对象实例 懒加载
对象运行:只要对象在使用中,就一直活着
对象销毁:当对象长时间不用时,被 Java 的垃圾回收器回收了
验证单例、多例:
创建两个对象,打印其地址观察
验证创建时机:
打断点观察无参构造执行时机
3.3 Bean生命周期配置
**init-method:**指定类中的初始化方法名称
**destroy-method:**指定类中销毁方法名称
3.4 Bean实例化三种方式
1) 使用无参构造方法实例化(重点)
它会根据默认无参构造方法来创建类对象,如果bean中没有默认无参构造函数,将会创建失败
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>
2) 工厂静态方法实例化
工厂的静态方法返回Bean实例
public class StaticFactoryBean {
public static UserDao createUserDao(){
return new UserDaoImpl();
}
}
<bean id="userDao" class="com.itheima.factory.StaticFactoryBean" factory-method="createUserDao" />
3) 工厂实例方法实例化
工厂的非静态方法返回Bean实例
非静态:不能直接调用,先要有工厂对象
public class DynamicFactoryBean {
public UserDao createUserDao(){
return new UserDaoImpl();
}
}
<bean id="factoryBean" class="com.itheima.factory.DynamicFactoryBean"/>
<bean id="userDao" factory-bean="factoryBean" factory-method="createUserDao"/>
借助工厂类创建对象,一个是工厂类直接创建(创建方法是静态,直接调用),一个是工厂类实例创建(调用非静态)。
3.5 Bean的依赖注入入门
1)程序开发中,我们需要在service层操作dao层,那么必须通过dao层的实例对象操作。
2)此时,可以把dao层的实例对象设置到service层内部,而不需要在service层通过spring容器获取dao层的实例对象。
![image-20201007104235749](https://i-blog.csdnimg.cn/blog_migrate/64abe3ace68640088a66e5dc0d00b0e3.png)
3.6 Bean的依赖注入概念
依赖注入(Dependency Injection):它是 Spring 框架核心 IOC 的具体实现。
在编写程序时,通过控制反转,把对象的创建交给了 Spring,但是代码中不可能出现没有依赖的情况。
IOC 解耦只是降低他们的依赖关系,但不会消除。例如:业务层仍会调用持久层的方法。
那这种业务层和持久层的依赖关系,在使用 Spring 之后,就让 Spring 来维护了。
简单的说,就是坐等框架把持久层对象传入业务层,而不用我们自己去获取
3.7 Bean的依赖注入方式
将持久层对象作为一个属性注入到业务层
1)构造方法
//有参构造
public UserServiceImpl(UserDao userDao) {
this.userDao = userDao;
}
<!--构造方法注入依赖-->
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"></bean>
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl">
<!--name是指构造方法中属性名称,ref是要注入的对象id-->
<constructor-arg name="userDao" ref="userDao"></constructor-arg>
</bean>
2)set方法
//通过set方法注入dao
private UserDao userDao;
public void setUserDao(UserDao userDao){
this.userDao=userDao;
}
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"></bean>
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl">
<!--name指的是注入给哪个方法,即set方法后面的名称-->
<!--ref是要注入的对象id-->
<property name="userDao" ref="userDao"></property>
</bean>
优化:p命名空间注入
引入命名空间
xmlns:p="http://www.springframework.org/schema/p"
修改注入方法
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl" p:userDao-ref="userDao">
</bean>
3.8 Bean的依赖注入的数据类型
上面的操作,都是注入的引用Bean,处了对象的引用可以注入,普通数据类型,集合等都可以在容器中进行注入。
注入数据的三种数据类型
普通数据类型
引用数据类型
集合数据类型
只演示set方法注入
1)普通数据类型
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl">
name指的是注入给哪个方法,即set方法后面的名称
<property name="username" value="张三"/>
<property name="age" value="18"/>
</bean>
private String username;
private int age;
public void setUsername(String username) {
this.username = username;
}
public void setAge(int age) {
this.age = age;
}
2)集合数据类型
<property name="strList">
<list>
<value>1</value>
<value>2</value>
<value>3</value>
</list>
</property>
<property name="userMap">
<map>
<entry key="user1" value-ref="user1"></entry>
<entry key="user2" value-ref="user2"></entry>
</map>
</property>
<property name="properties" >
<props>
<prop key="1">p1</prop>
<prop key="2">p2</prop>
</props>
</property>
3.9 引入其他配置文件(分模块开发)
实际开发中,Spring的配置内容非常多,这就导致Spring配置很繁杂且体积很大,所以,可以将部分配置拆解到其他配置文件中,而在Spring主配置文件通过import标签进行加载
基本是引入mapper
<import resource="applicationContext-xxx.xml"/>
4. spring相关API
4.1 ApplicationContext的继承体系
applicationContext:接口类型,代表应用上下文,可以通过其实例获得 Spring 容器中的 Bean 对象
4.2 ApplicationContext的实现类
1)ClassPathXmlApplicationContext
它是从类的根路径下加载配置文件 推荐使用这种
2)FileSystemXmlApplicationContext
它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置。
3)AnnotationConfigApplicationContext
当使用注解配置容器对象时,需要使用此类来创建 spring 容器。它用来读取注解。
4.3 getBean()方法使用
public Object getBean(String name) throws BeansException {
assertBeanFactoryActive();
return getBeanFactory().getBean(name);
}
public <T> T getBean(Class<T> requiredType) throws BeansException { assertBeanFactoryActive();
return getBeanFactory().getBean(requiredType);
}
其中,当参数的数据类型是字符串时,表示根据Bean的id从容器中获得Bean实例,返回是Object,需要强转。
当参数的数据类型是Class类型时,表示根据类型从容器中匹配Bean实例,当容器中相同类型的Bean有多个时,则此方法会报错
getBean()方法使用
ApplicationContext applicationContext = new
ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService1 = (UserService) applicationContext.getBean("userService");
UserService userService2 = applicationContext.getBean(UserService.class);
二、spring注解开发
1. spring配置数据源
1.1 数据源的开发步骤
①导入数据源的坐标和数据库驱动坐标
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.32</version>
</dependency>
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
②创建数据源对象
③设置数据源的基本连接数据
④使用数据源获取连接资源和归还连接资源
1.2 手动创建数据源
c3p0
//创建数据源对象并设置连接参数
ComboPooledDataSource dataSource=new ComboPooledDataSource();
dataSource.setDriverClass("com.mysql.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
dataSource.setUser("root");
dataSource.setPassword("huangyan123");
//获取连接对象
Connection connection = dataSource.getConnection();
System.out.println(connection);
connection.close();
druid
//创建数据源对象并设置连接参数
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/test");
dataSource.setUsername("root");
dataSource.setPassword("huangyan123");
//获取连接对象
Connection connection = dataSource.getConnection();
System.out.println(connection);
connection.close();
1.3 抽取jdbc.properties文件
将连接参数和数据源解耦
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test
jdbc.username=root
jdbc.password=huangyan123
通过反射加载properties文件
//读取配置文件
//获取class目录下的字节文件
ClassLoader classLoader = DataSourceTest.class.getClassLoader();
InputStream is = classLoader.getResourceAsStream("jdbc.properties");
//加载
Properties pro= new Properties();
pro.load(is);
//创建数据源对象并设置连接参数
ComboPooledDataSource dataSource=new ComboPooledDataSource();
dataSource.setDriverClass(pro.getProperty("jdbc.driver"));
dataSource.setJdbcUrl(pro.getProperty("jdbc.url"));
dataSource.setUser(pro.getProperty("jdbc.username"));
dataSource.setPassword(pro.getProperty("jdbc.password"));
通过ResourceBundle加载properties文件
ResourceBundle:专门加载properties文件
//读取配置文件
ResourceBundle rb=ResourceBundle.getBundle("jdbc");
//创建数据源对象并设置连接参数
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(rb.getString("jdbc.driver"));
dataSource.setUrl(rb.getString("jdbc.url"));
dataSource.setUsername(rb.getString("jdbc.username"));
dataSource.setPassword(rb.getString("jdbc.password"));
1.4 spring配置数据源
可以将DataSource的创建创交给spring容器去创建
无论是第三方的bean还是自己定义的bean,都可以交给spring创建
配置DataSource对象,通过set方法输入属性(相当于依赖注入)
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"/>
<property name="user" value="root"/>
<property name="password" value="huangyan123"/>
</bean>
ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
DataSource dataSource = app.getBean(DataSource.class);//直接获取DataSource对象
Connection connection = dataSource.getConnection();
System.out.println(connection);
spring记载properties配置文件
applicationContext.xml加载jdbc.properties配置文件获得连接信息。
通过context命名空间加载
1)首先,需要引入context命名空间和约束路径:
xmlns:context="http://www.springframework.org/schema/context"
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
2)spring容器加载properties文件
通过spl(spring expression language)引入 ${}
<!--加载外部properties文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driver}"/>
<property name="jdbcUrl" value="${jdbc.url"/>
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
bug:No suitable driver
解决:context标签中,引入property-placeholder后,在location确定位置时没有加上classpath:
2. spring注解开发
Spring是轻代码而重配置的框架,配置比较繁重,影响开发效率,所以注解开发是一种趋势,注解代替xml配置文件可以简化配置,提高开发效率。
2.1 spring原始注解
原始注解主要代替bean标签配置
实例化+注入(告诉spring我在使用注解了----组件扫描)
注意:
使用注解进行开发时,需要在applicationContext.xml中配置组件扫描,作用是指定哪个包及其子包下的Bean需要进行扫描以便识别使用注解配置的类、字段和方法。
<!--注解的组件扫描-->
<context:component-scan base-package="com.itheima"></context:component-scan>
实例化:
1)@Component:类上实例化bean
2)@Controller、@Service、@Reponsitory:实例化指定一层得bean,增加可读性
注入Bean:
注入时,可以省略set方法
1)==@Autowired+@Qualifier:==根据类型和名称注入,两个注解配合使用
@Autowired
@Qualifier("userDao")
private UserDao userDao;
2)==@Resource:==相当于@Autowired+@Qualifier
@Resource(name="userDao")
private UserDao userDao;
注入普通属性:
可以使用spl表达式从容器中注入键值对
//注入普通数据类型
@Value("${jdbc.driver}")
private String driver;
标注Bean范围:
使用在类上
@Component("userService")
@Scope("singleton")
public class UserServiceImpl implements UserService {
标注初始化方法和销毁方法:
@PostConstruct
public void init() {
System.out.println("init");
}
@PreDestroy
public void destroy() {
System.out.println("destroy");
}
2.2 spring新注解
注解 | 说明 |
---|---|
@Configuration | 用于指定当前类是一个 Spring 配置类,当创建容器时会从该类上加载注解 |
@ComponentScan | 用于指定 Spring 在初始化容器时要扫描的包。 作用和在 Spring 的 xml 配置文件中的 <context:component-scan base-package=“com.itheima”/>一样 |
@Bean | 使用在方法上,标注将该方法的返回值存储到 Spring 容器中 |
@PropertySource | 用于加载.properties 文件中的配置 |
@Import | 用于导入其他配置类 |
主配置类:
//标注该类是配置类
@Configuration
//<context:component-scan base-package="com.itheima"></context:component-scan>主件扫描
@ComponentScan("com.itheima")//组件扫描
@Import(DateSourceConfiguration.class)//加载分配置类
public class SpringConfiguration {
}
分配置类:
//<!--加载外部properties文件-->
//<context:property-placeholder location="classpath:jdbc.properties"/>
@PropertySource("classpath:jdbc.properties")
public class DateSourceConfiguration {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean("dataSource")//将方法返回值放入容器中
public DataSource getDataSource() throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass(driver);
dataSource.setJdbcUrl(url);
dataSource.setUser(username);
dataSource.setPassword(password);
return dataSource;
}
测试:
ApplicationContext app=new AnnotationConfigApplicationContext(SpringConfiguration.class);
UserService userService = (UserService)app.getBean("userService");
userService.save();
核心配置类–>组件扫描–>注入bean
加载外部properties文件–>注入私有属性–>构造一个方法,返回值获取DataSource使用@Bean放入容器中
3. Spring整合Junit
问题:
每个测试类都要创建应用上下文对象和获取bean
解决:
•让SpringJunit负责创建Spring容器,但是需要将配置文件的名称告诉它
•将需要进行测试Bean直接在测试类中进行注入
步骤:
①导入spring集成Junit的坐标
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
②使用@Runwith注解替换原来的运行期
③使用@ContextConfiguration指定配置文件或配置类
④使用@Autowired注入需要测试的对象
⑤创建测试方法进行测试
实现:
Junit+配置文件
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringJunitTest {
@Autowired
private UserService userService;
@Test
public void test1(){
userService.save();
}
Junit+全注解
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
区别是一个通过配置文件加载容器
一个通过配置类加载容器
三、AOP
1. Spring 的 AOP 简介
1.1 什么是 AOP
AOP 为 Aspect Oriented Programming 的缩写,意思为面向切面编程,是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
AOP 是 OOP 的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
1.2 AOP 的作用及其优势
作用:在程序运行期间,在不修改源码的情况下对方法进行功能增强
优势:减少重复代码,提高开发效率,并且便于维护
1.3 AOP 的底层实现
实际上,AOP 的底层是通过 Spring 提供的的动态代理技术实现的。在运行期间,Spring通过动态代理技术动态的生成代理对象,代理对象方法执行时进行增强功能的介入,在去调用目标对象的方法,从而完成功能的增强。
常用的动态代理技术有:
JDK 代理 : 基于接口的动态代理技术
cglib 代理:基于父类的动态代理技术
都是为了保证目标对象和代理对象有相同的方法
JDK 的动态代理:
①目标类接口:
//JDK 代理 : 基于接口的动态代理技术
public interface TargetInterface {
public void save();
}
②目标类:
public class Target implements TargetInterface {
public void save() {
System.out.println("save running");
}
}
③动态代理代码:
Target target = new Target(); //创建目标对象
//创建代理对象
TargetInterface proxy = (TargetInterface) Proxy.newProxyInstance(target.getClass()
.getClassLoader(),target.getClass().getInterfaces(),new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("前置增强代码...");
Object invoke = method.invoke(target, args);
System.out.println("后置增强代码...");
return invoke;
}
}
);
④ 调用代理对象的方法测试:
// 测试,当调用接口的任何方法时,代理对象的代码都无序修改
proxy.method();
cglib 的动态代理:
目标类没有实现接口时,需要借助第三方cglib实现动态代理
spring后期版本已经集成cglib
①目标类:
public class Target {
public void save() {
System.out.println("save running");
}
}
②增强类:
public class Advice {
public void Before(){
System.out.println("Before");
}
public void After(){
System.out.println("After");
}
}
③动态代理代码:
//目标对象
final Target target = new Target();
//增强对象
final Advice advice = new Advice();
//返回值是动态生成的代理对象,基于cglib(父类)
//1.创建增强器
Enhancer enhancer = new Enhancer();
//2.设置父类----目标类
enhancer.setSuperclass(Target.class);
//3.设置回调
enhancer.setCallback(new MethodInterceptor() {
//调用代理对象的任何方法,实质上是执行intercept方法
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//执行前置
advice.Before();
//执行目标
Object invoke = method.invoke(target, args);
//执行后置
advice.After();
return invoke;
}
});
//4.创建代理对象,并执行测试
Target proxy = (Target) enhancer.create();
proxy.save();
1.4 AOP相关概念
Spring 的 AOP 实现底层就是对上面的动态代理的代码进行了封装,封装后我们只需要对需要关注的部分进行代码编写,并通过配置的方式完成指定目标的方法增强。
在正式讲解 AOP 的操作之前,我们必须理解 AOP 的相关术语,常用的术语如下:
-
Target(目标对象):代理的目标对象
-
Proxy (代理):一个类被 AOP 织入增强后,就产生一个结果代理类
-
Joinpoint(连接点):所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点(可以被增强的方法)
-
Pointcut(切入点):所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义
-
Advice(通知/ 增强):所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知
-
Aspect(切面):是切入点和通知(引介)的结合(目标方法+增强)------代理对象
-
Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程。spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入
1.5 AOP 开发明确的事项
1)需要编写的内容
-
编写核心业务代码(目标类的目标方法)
-
编写切面类,切面类中有通知(增强功能方法)
-
在配置文件中,配置织入关系,即将哪些通知与哪些连接点进行结合
2)AOP 技术实现的内容
Spring 框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。
监控目标方法是否执行----->创建代理对象
3)AOP 底层使用哪种代理方式
在 spring 中,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式。
1.6 知识要点
-
aop:面向切面编程
-
aop底层实现:基于JDK的动态代理 和 基于Cglib的动态代理
-
aop的重点概念:
Pointcut(切入点):被增强的方法 Advice(通知/ 增强):封装增强业务逻辑的方法 Aspect(切面):切点+通知 Weaving(织入):将切点与通知结合的过程
-
开发明确事项:
谁是切点(切点表达式配置) 谁是通知(切面类中的增强方法) 将切点和通知进行织入配置(创建代理对象)
2. 基于 XML 的 AOP 开发
2.1 快速入门
①导入 AOP 相关坐标
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.4</version>
</dependency>
**aspectj:**aop的一个实现框架,比spring自带的好一些
②创建目标接口和目标类(内部有切点)
③创建切面类(内部有增强方法)
④将目标类和切面类的对象创建权交给 spring
由spring容器管理
<!--目标对象和代理对象-->
<bean id="target" class="com.itheima.aop.Target"></bean>
<bean id="myAspect" class="com.itheima.aop.MyAspect"></bean>
⑤在 applicationContext.xml 中配置织入关系
引入aop命名空间
<!--配置织入-->
<aop:config>
<!--声明切面-->
<aop:aspect ref="myAspect">
<!--切点表达式-->
<aop:before method="before" pointcut="execution(public void com.itheima.aop.Target.save())"></aop:before>
</aop:aspect>
</aop:config>
切点和增强结合
⑥测试代码
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AopTest {
@Autowired
private TargetInterface target;
@Test
public void test1() {
target.save();
}
}
要点:
spring jar包版本号要尽量统一
2.2 XML配置AOP详解
切点表达式写法
表达式语法:
execution([修饰符] 返回值类型 包名.类名.方法名(参数))
-
访问修饰符可以省略
-
返回值类型、包名、类名、方法名可以使用星号* 代表任意
-
包名与类名之间一个点 . 代表当前包下的类,两个点 … 表示当前包及其子包下的类
-
参数列表可以使用两个点 … 表示任意个数,任意类型的参数列表
例如:
execution(public void com.itheima.aop.Target.method())
execution(void com.itheima.aop.Target.*(..))
execution(* com.itheima.aop.*.*(..)) aop包下任意类的任意方法
execution(* com.itheima.aop..*.*(..)) aop包及其子包下任意类的任意方法
execution(* *..*.*(..))
通知的类型
通知的配置语法:
<aop:通知类型 method=“切面类中方法名” pointcut=“切点表达式"></aop:通知类型>
<aop:around method="around" pointcut="execution(* com.itheima.aop.*.*(..))"></aop:around>
<aop:after-throwing method="afterThrow" pointcut="execution(* com.itheima.aop.*.*(..))"></aop:after-throwing>
<aop:after method="after" pointcut-ref="myPointcut"></aop:after>
切点表达式的抽取
当多个增强的切点表达式相同时,可以将切点表达式进行抽取,在增强中使用 pointcut-ref 属性代替 pointcut 属性来引用抽取后的切点表达式
<!--抽取切点表达式-->
<aop:pointcut id="myPointcut" expression="execution(* com.itheima.aop.*.*(..))"/>
<aop:after method="after" pointcut-ref="myPointcut"></aop:after>
2.3 知识要点
- aop织入的配置
<aop:config>
<aop:aspect ref=“切面类”> 可以配多个切面
<aop:before method=“通知方法名称” pointcut=“切点表达式"></aop:before>
</aop:aspect>
</aop:config>
- 通知的类型:前置通知、后置通知、环绕通知、异常抛出通知、最终通知
- 切点表达式的写法:
execution([修饰符] 返回值类型 包名.类名.方法名(参数))
切面类、目标类
通知类型+切点表达式
3. 基于注解的 AOP 开发
3.1 快速入门
基于注解的aop开发步骤:
①创建目标接口和目标类(内部有切点)
②创建切面类(内部有增强方法)
③将目标类和切面类的对象创建权交给 spring
④在切面类中使用注解配置织入关系 关键一步
@Component("myAspect")
@Aspect //标注当前类是切面类
public class MyAspect {
//切点表达式:指定对谁增强
@Before("execution(* com.itheima.anno.*.*(..))")
public void before() {
System.out.println("前置增强");
}
@AfterReturning("execution(* com.itheima.anno.*.*(..))")
public void afterRunning() {
System.out.println("后置增强");
}
@Around("execution(* com.itheima.anno.*.*(..))")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("环绕前");
Object proceed = pjp.proceed();
System.out.println("环绕后");
return proceed;
}
@AfterThrowing("execution(* com.itheima.anno.*.*(..))")
public void afterThrow() {
System.out.println("抛出异常。。。");
}
@After("execution(* com.itheima.anno.*.*(..))")
public void after() {
System.out.println("最终增强");
}
}
⑤在配置文件中开启组件扫描和 AOP 的自动代理
<!--注解的组件扫描-->
<context:component-scan base-package="com.itheima"></context:component-scan>
<!--aop自动代理-->
<aop:aspectj-autoproxy/>
⑥测试
3.2 详解
注解通知的类型:
通知的配置语法:@通知注解(“切点表达式")
切点表达式抽取:
抽取方式是在切面内定义方法,在该方法上使用@Pointcut注解定义切点表达式,然后在在增强注解中进行引用。
//切点表达式抽取
@Pointcut("execution(* com.itheima.anno.*.*(..))")
public void myPoint(){
}
//切点表达式:指定对谁增强
@Before("MyAspect.myPoint()")
public void before() {
System.out.println("前置增强");
}
3.3 总结
注解aop开发步骤
①使用@Aspect标注切面类
②使用@通知注解标注通知方法
③在配置文件中配置aop自动代理
<!--aop自动代理-->
<aop:aspectj-autoproxy/>
四、JdbcTemplate基本使用
1. 概述
JdbcTemplate是spring框架中提供的一个对象,是对原始繁琐的Jdbc API对象的简单封装。spring框架为我们提供了很多的操作模板类。例如:操作关系型数据的JdbcTemplate和HibernateTemplate,操作nosql数据库的RedisTemplate,操作消息队列的JmsTemplate等等。
2. 开发步骤
①导入spring-jdbc和spring-tx坐标
②创建数据库表和实体
③创建JdbcTemplate对象(设置数据源对象)
④执行数据库操作
//创建数据源对象
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass("com.mysql.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/ssm");
dataSource.setUser("root");
dataSource.setPassword("huangyan123");
JdbcTemplate jdbcTemplate = new JdbcTemplate();
//设置数据源对象
jdbcTemplate.setDataSource(dataSource);
int row = jdbcTemplate.update("insert into account values(?,?,?)", null,"zhaoqiang", 5000);
System.out.println(row);
3. Spring产生jdbc对象
①让spring帮我们生成jdbc对象和数据源对象
②将数据源对象注入到jdbc对象中
<!--加载外部properties文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--数据源对象-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driver}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!--jdbc模板对象-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
4.JdbcTemplate增删改查
增删改:
@Test
public void testInsert(){
int row = jdbcTemplate.update("insert into account values(?,?,?)", null,"xiaoming", 5000);
System.out.println(row);
}
@Test
public void testUpdate(){
int row = jdbcTemplate.update("update account set money=? where id=?", 50000,7);
System.out.println(row);
}
@Test
public void testDelete(){
int row = jdbcTemplate.update("delete from account where id=?", 5);
System.out.println(row);
}
查:
①重点是RowMapper,这个接口的实现类帮我们将查询的数据封装为对象返回------BeanPropertyRowMapper
②queryForObject:如果查询实体,则用RowMapper封装
如果查询基本数据类型,则不用。
③**query:**查询多个对象,RowMapper封装起
@Test
public void testQueryAll(){
//RowMapper:行映射,帮我们把查询出来的对象封装好返回给我们
List<Account> accountList = jdbcTemplate.query("select * from account", new BeanPropertyRowMapper<Account>(Account.class));
System.out.println(accountList);
}
@Test
public void testQueryOne(){
Account account = jdbcTemplate.queryForObject("select * from account where id=?", new BeanPropertyRowMapper<Account>(Account.class), 7);
System.out.println(account);
}
@Test
public void testQueryCount(){
Integer count = jdbcTemplate.queryForObject("select count(*) from account", Integer.class);
System.out.println(count);
}
5. 知识要点
①导入spring-jdbc和spring-tx(底层用到事务)坐标
②创建数据库表和实体
③创建JdbcTemplate对象(包括设置数据源对象)
可以自己创建、也可以交由spring容器创建
④执行数据库操作
五、 声名式事务控制
1. 编程式事务控制相关对象
1. PlatformTransactionManager
PlatformTransactionManager 接口是 spring 的事务管理器,它里面提供了我们常用的操作事务的方法。
方法 | 说明 |
---|---|
TransactionStatus getTransaction(TransactionDefination defination) | 获取事务的状态信息 |
void commit(TransactionStatus status) | 提交事务 |
void rollback(TransactionStatus status) | 回滚事务 |
2. TransactionDefinition
TransactionDefinition 是事务的定义信息对象,里面有如下方法:
方法 | 说明 |
---|---|
int getIsolationLevel() | 获得事务的隔离级别 |
int getPropogationBehavior() | 获得事务的传播行为 |
int getTimeout() | 获得超时时间 |
boolean isReadOnly() | 是否只读 |
1. 事务的隔离级别
设置隔离级别,可以解决事务并发产生的问题,如脏读、不可重复读和虚读。
- ISOLATION_DEFAULT
- ISOLATION_READ_UNCOMMITTED
- ISOLATION_READ_COMMITTED
- ISOLATION_REPEATABLE_READ
- ISOLATION_SERIALIZABLE
2. 事务的传播行为
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dMjpxyPd-1602365656837)(C:\Users\nyh\Desktop\框架与项目总结\ssm总结\images\image-20201011025429128.png)]
3. TransactionStatus
TransactionStatus 接口提供的是事务具体的运行状态,方法介绍如下。
方法 | 说明 |
---|---|
boolean hasSavepoint() | 是否存储回滚点 |
boolean isCompleted() | 事务是否完成 |
boolean isNewTransaction() | 是否是新事务 |
boolean isRollbackOnly() | 事务是否回滚 |
状态=管理+定义
2. 声名式事务控制(XML)
2.1 什么是声明式事务控制
Spring 的声明式事务顾名思义就是采用声明的方式来处理事务。这里所说的声明,就是指在配置文件中声明,用在 Spring 配置文件中声明式的处理事务来代替代码式的处理事务。
声明式事务处理的作用
-
==事务管理不侵入开发的组件。==具体来说,业务逻辑对象就不会意识到正在事务管理之中,事实上也应该如此,因为事务管理是属于系统层面的服务,而不是业务逻辑的一部分,如果想要改变事务管理策划的话,也只需要在定义文件中重新配置即可
-
在不需要事务管理的时候,只要在设定文件上修改一下,即可移去事务管理服务,无需改变代码重新编译,这样维护起来极其方便
注意:Spring 声明式事务控制底层就是AOP。
2.2 声明式事务控制的实现
1.转账环境搭建
①dao层
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public void out(String outMan, double money) {
jdbcTemplate.update("update account set money=money-? where name=?", money, outMan);
}
public void in(String inMan, double money) {
jdbcTemplate.update("update account set money=money+? where name=?", money, inMan);
}
②service层
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao){
this.accountDao=accountDao;
}
public void transfer(String outMan, String inMan, double money) {
accountDao.out(outMan,money);
accountDao.in(inMan,money);
}
③控制层
public static void main(String[] args) {
ApplicationContext app=new ClassPathXmlApplicationContext("applicationContext.xml");
AccountService accountService = app.getBean(AccountService.class);
accountService.transfer("zhaoqiang","xiaoming",500);
}
④配置文件
<!--jdbc模板对象-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--dao-->
<bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate"></property>
</bean>
<!--service-->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
声明式事务控制明确事项:
- 谁是切点?
- 谁是通知?
- 配置切面?
2. 实现
①引入tx命名空间
②配置事务增强和织入
<!--service:目标对象,内部的方法就是切点,进行事务控制的方法-->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
<!--配置平台事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--事务的通过connection对象控制的-->
<property name="dataSource" ref="dataSource"/>
</bean>
<!--事务增强-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<!--aop织入-->
<aop:config>
<!--对impl包下所有类所有方法进行织入-->
<aop:advisor advice-ref="txAdvice" pointcut="execution(* com.itheima.service.impl.*.*(..))"></aop:advisor>
</aop:config>
③转账测试
accountDao.out(outMan,money);
int i=1/0;
accountDao.in(inMan,money);
2.3 切点方法的事务参数的配置
<!--事务增强配置-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
其中,< tx:method > 代表切点方法的事务参数的配置,例如:
<tx:method name="transfer" isolation="REPEATABLE_READ" propagation="REQUIRED" timeout="-1" read-only="false"/>
-
name:切点方法名称
-
isolation:事务的隔离级别
-
propogation:事务的传播行为
-
timeout:超时时间
-
read-only:是否只读
<tx:attributes>
<!--可以对多个切点进行不同的事务控制-->
<tx:method name="transfer" isolation="DEFAULT" propagation="REQUIRED" timeout="-1" read-only="false"/>
<tx:method name="update*" isolation="DEFAULT" propagation="REQUIRED" timeout="-1" read-only="false"/>
<tx:method name="find*" isolation="DEFAULT" propagation="REQUIRED" timeout="-1" read-only="true"/>
<tx:method name="*"/>
</tx:attributes>
2.4 总结
声明式事务控制的配置要点:
①平台事务管理器配置
②事务通知的配置
③事务aop织入的配置
3. 声名式事务控制(注解)
一、将自定义的bean换成注解配置(记得加上组件扫描)
二、注解进行事务控制
①使用 @Transactional 在需要进行事务控制的类或是方法上修饰,注解可用的属性同 xml 配置方式,例如隔离级别、传播行为等。
②注解使用在类上,那么该类下的所有方法都使用同一套注解参数配置。
③使用在方法上,不同的方法可以采用不同的事务参数配置。
@Transactional(isolation = Isolation.DEFAULT,propagation = Propagation.REQUIRED)
public void transfer(String outMan, String inMan, double money) {
accountDao.out(outMan,money);
int i=1/0;
accountDao.in(inMan,money);
}
④Xml配置文件中要开启事务的注解驱动<tx:annotation-driven />
<!--事务的注解驱动-->
<tx:annotation-driven/>
第三方bean默认使用xml配置(即数据源对象、jdbc模板对象、配置平台事务管理器)
xml中还有context命名空间的组件扫描和加载外部properties文件、事务的注解驱动(传入平台事务管理器)