Spring
一.Spring基于xml的IOC环境搭建和入门
1.示例
-
pom中导入坐标
<packaging>jar</packaging> <dependencies> <!-- https://mvnrepository.com/artifact/org.springframework/spring-context --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.0.20.RELEASE</version> </dependency> </dependencies>
-
resource中创建xml文件,创建相关依赖,在Spring文档中搜索相关依赖(xmlns)
<?xml version="1.0" encoding="UTF-8"?>
<!--把对象的创建交给Spring来管理 id:获取时的唯一标志,class:全限定类名--> <bean id="accountService" class="com.hmk.service.impl.AccountServiceImpl"></bean> <bean id="accountDao" class="com.hmk.dao.impl.AccountDaoImpl"></bean> </beans>
-
根据id获取IOC核心容器
public static void main(String[] args) {
//1.获取核心容器对象
ApplicationContext ac=new ClassPathXmlApplicationContext(“bean.xml”);
//2.根据id获取bean对象(两种方式)
IAccountService as=(IAccountService) ac.getBean(“accountService”);
IAccountDao adao=ac.getBean(“accountDao”,IAccountDao.class);
System.out.println(as);
System.out.println(adao);
}
}
2.ApplicationContext三种常用实现类
ClassPathXmlApplicationContext: 它是从类的根路径下加载配置文件 推荐使用这种 FileSystemXmlApplicationContext: 它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置。 AnnotationConfigApplicationContext: 当我们使用注解配置容器对象时,需要使用此类来创建 spring 容器。它用来读取注解。
3.核心容器的两个接口
1.ApplicationContext:(单例对象使用)在构建核心容器是,创建的对象采取的策略是立即加载方式,只要已读取配置文件,马上就创建配置文件中的对象。
2.BeanFactory :(多例对象使用)在构建核心容器是,创建对象擦去的策略是采用延迟加载的方式,什么时候根据id获取对象,什么是才真正的创建对象。
4.创建bean对象的三种方式
第一种方式:使用默认无参构造函数 :在默认情况下它会根据默认无参构造函数来创建类对象。如果 bean 中没有默认无参构造函数,将会创建失败。
(如果想创建jar包中的对象,使用第二中和第三种方式)
第二种方式:spring 管理静态工厂-使用静态工厂的方法创建对象:
使用 StaticFactory 类中的静态方法 createAccountService 创建对象,并存入 spring 容器
id 属性:指定 bean 的 id,用于从容器中获取
class 属性:指定静态工厂的全限定类名
factory-method 属性:指定生产对象的静态方法
eg:
<bean id="accountService"
class="com.itheima.factory.StaticFactory"
factory-method="createAccountService"></bean>
第三种方式:spring 管理实例工厂-使用实例工厂的方法创建对象(使用某个类中的方法创建对象)
先把工厂的创建交给 spring 来管理。
然后在使用工厂的 bean 来调用里面的方法
factory-bean 属性:用于指定实例工厂 bean 的 id。
factory-method 属性:用于指定实例工厂中创建对象的方法。
eg:
<bean id="instancFactory" class="com.itheima.factory.InstanceFactory"></bean>
<bean id="accountService"
factory-bean="instancFactory"
factory-method="createAccountService"></bean>
5.bean的作用范围
1.bean标签
作用: 用于配置对象让 spring 来创建的。 默认情况下它调用的是类中的无参构造函数。如果没有无参构造函数则不能创建成功。
属性: id:给对象在容器中提供一个唯一标识。用于获取对象。
class:指定类的全限定类名。用于反射创建对象。默认情况下调用无参构造函数。
scope:指定对象的作用范围。
singleton :单例的(默认值).
prototype :多例的.
request :WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 request 域中.
session :WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 session 域中.
global session :WEB 项目中,应用在 Portlet (集群)环境.如果没有 Portlet 环境那么
globalSession 相当session。
init-method:指定类中的初始化方法名称。
destroy-method:指定类中销毁方法名称。
6.生命周期
1.单例对象:scope=“singleton”
一个应用只有一个对象的实例。它的作用范围就是整个引用。
生命周期:
对象出生:当应用加载,创建容器时,对象就被创建了。
对象活着:只要容器在,对象一直活着。
对象死亡:当应用卸载,销毁容器时,对象就被销毁了。
2.多例对象:scope=“prototype”
每次访问对象时,都会重新创建对象实例。
生命周期:
对象出生:当使用对象时,创建新的对象实例。
对象活着:只要对象在使用中,就一直活着。
对象死亡:当对象长时间不用时,被 java 的垃圾回收器回收了。
7.Spring依赖注入(Dependency Injection )
在当前类中使用到其他类的对象,由spring为我们提供,只需要配置
1.构造函数的方式,给 service 中的属性传值
要求:
类中需要提供一个对应参数列表的构造函数。
涉及的标签:constructor-arg
属性:
index:指定参数在构造函数参数列表的索引位置
type:指定参数在构造函数中的数据类型
name:指定参数在构造函数中的名称 用这个找给谁赋值(常用)
!上面三个都是找给谁赋值,下面两个指的是赋什么值的
value:它能赋的值是基本数据类型和 String 类型
ref:它能赋的值是其他 bean 类型,也就是说,必须得是在配置文件中配置过的 bean
eg:
<!--构造函数方式-->
<bean id="accountService" class="com.hmk.service.impl.AccountServiceImpl">
<constructor-arg name="name" value="test"></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.set方式注入(实际开发中使用较多)
通过配置文件给 bean 中的属性传值:使用 set 方法的方式
涉及的标签:
property属性:
name:找的是类中 set 方法的set后的名称
ref:给属性赋值是其他 bean 类型的
value:给属性赋值是基本数据类型和 string 类型的
<bean id="accountService2" class="com.hmk.service.impl.AccountServiceImpl2">
<property name="name" value="test"></property>
<property name="age" value="19"></property>
<property name="birthday" ref="now"></property>
</bean>
3.注入集合数据
顾名思义,就是给类中的集合成员传值,它用的也是set方法注入的方式,只不过变量的数据类型都是集合。 我们
这里介绍注入数组,List,Set,Map,Properties。
注入集合数据 List 结构的:
array,list,set
Map 结构的
map,entry,props,prop
<!--集合类型注入-->
<bean id="accountService3" class="com.hmk.service.impl.AccountServiceImpl3">
<property name="myStrs">
<array>
<value>aaa</value>
<value>bbb</value>
<value>ccc</value>
</array>
</property>
<property name="myList">
<list>
<value>aaa2</value>
<value>bbb2</value>
<value>ccc2</value>
</list>
</property>
<property name="mySet">
<set>
<value>aaa2</value>
<value>bbb2</value>
<value>ccc2</value>
</set>
</property>
<property name="myMap">
<map>
<entry key="testA" value="aaa"></entry>
<entry key="testB" value="baa"></entry>
<entry key="testC" value="caa"></entry>
</map>
</property>
<property name="myProps">
<props>
<prop key="test1">123</prop>
<prop key="test2">234</prop>
<prop key="test3">345</prop>
</props>
</property>
</bean>
二.Spring基于注解的IOC以及案例
1.IOC中的常用注解
1.用于创建对象的注解
作用相同,为了区分
@Component:全能,把当前类对象存入Spring容器中
@controller:一般用在表现层
@service:一般用在业务层
@repository:一般用在持久层
- 在bean.xml中导入坐标
*红框部分在Spring相关文件中复制
-
注解(value可省略)
-
value用于指定bean的id,如果不指定value,默认类名首字母小写
2.用于注入数据的注解
@Autowired
自动按照类型注入。当使用注解注入属性时,set 方法可以省略。它只能注入其他 bean 类型。当有多个 类型
匹配时,使用要注入的对象变量名称作为 bean 的 id,在 spring 容器查找,找到了也可以注入成功。找不到 就报
错。
@Qualifier
作用: 在自动按照类型注入的基础之上,再按照 Bean 的 id 注入。它在给字段注入时不能独立使用,必须和
@Autowire 一起使用;但是给方法参数注入时,可以独立使用。
属性: value:指定 bean 的 id。
@Resource
作用: 直接按照 Bean 的 id 注入。它也只能注入其他 bean 类型。
属性: name:指定 bean 的 id。
@Value
作用: 注入基本数据类型和 String 类型数据的
属性: value:用于指定值
取值: singleton prototype request session globalsession
写法: ${表达式},去spring指定位置获取数据
*集合类型的数据只能通过xml配置文件实现
3.用于改变作用范围的
@Scope
作用: 指定 bean 的作用范围。
属性: value:指定范围的值。
取值: singleton prototype request session globalsession
4.和生命周期相关(了解)
@PostConstruct
作用: 用于指定初始化方法。
@PreDestroy
作用: 用于指定销毁方法。
2.使用xml的方式实现单表的CRUD操作
1.编写Account实体类
2.编写IAccountService接口,并写入操作方法
3.编写IAccountDao接口,并写入操作方法
4.编写实现类AccountServiceImpl和AccountDaoImpl
5.编写bean.xml文件如下
<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">
<!--配置accountService对象-->
<bean id="accountService" class="com.hmk.service.Impl.AccountServiceImpl">
<!--注入dao-->
<property name="accountDao" ref="accountDao"></property>
</bean>
<!--配置accountDao对象,scope防止单例模式下,多线程调用对数据的干扰-->
<bean id="accountDao" class="com.hmk.dao.Impl.AccountDaoImpl" scope="prototype">
<property name="runner" ref="runner"></property>
</bean>
<bean id="runner" class="org.apache.commons.dbutils.QueryRunner">
<!--注入数据源-->
<constructor-arg name="ds" ref="dataSource"></constructor-arg>
</bean>
<!--配置数据源-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--链接数据库必备信息-->
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/eesy"></property>
<property name="user" value="root"></property>
<property name="password" value="1234"></property>
</bean>
</beans>
6.编写测试类
3.基于注解的IOC案例
1.修改AccountDaoImpl
2.修改AccountServiceimpl
3.修改bean.xml
<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">
<!--告知Spring在创建容器是要扫描的包-->
<context:component-scan base-package="com.hmk"></context:component-scan>
<bean id="runner" class="org.apache.commons.dbutils.QueryRunner">
<!--注入数据源-->
<constructor-arg name="ds" ref="dataSource"></constructor-arg>
</bean>
<!--配置数据源-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--链接数据库必备信息-->
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/eesy"></property>
<property name="user" value="root"></property>
<property name="password" value="1234"></property>
</bean>
</beans>
4.Spring和Junit整合
1.新的注解(解决所有xml文件)
@Configuration
作用: 用于指定当前类是一个 spring 配置类,当创建容器时会从该类上加载注解。获取容器时需要使用
AnnotationApplicationContext(有@Configuration 注解的类.class)。
属性: value:用于指定配置类的字节码
细节:当配置类作为AnnotationConfigApplicationContext对象创建的参数时,该注解可以不写。
@ComponentScan
作用: 用于指定 spring 在初始化容器时要扫描的包。作用和在 spring 的 xml 配置文件中的:
<context:component-scan base-package=“com.hmk”>/context:component-scan
是一样的。
eg: @ComponentScan(“com.hmk”)
属性: basePackages:用于指定要扫描的包。和该注解中的 value 属性作用一样。
@Bean
作用: 该注解只能写在方法上,表明使用此方法创建一个对象,并且放入 spring 容器。
属性: name:给当前@Bean 注解方法创建的对象指定一个名称(即 bean 的 id),默认值当前方法名。
细节: 当我们使用注解配置方法是,如果方法有参数,Spring框架回去容器中查找有没有可用的bean对象。
查找方式和autowired注解的作用一样的
@PropertySource
作用: 用于加载.properties 文件中的配置。例如我们配置数据源时,可以把连接数据库的信息写到 properties
配置文件中,就可以使用此注解指定 properties 配置文件的位置。
属性: value[]:用于指定 properties 文件位置。如果是在类路径下,需要写上 classpath:
eg: @PropertySource(“calsspath:jdbcConfig.properties”)
@Import
作用: 用于导入其他配置类,在引入其他配置类时,可以不用再写@Configuration 注解。当然,写上也没问 题。
属性: value[]:用于指定其他(子)配置类的字节码。
eg; @Import(JdbcConfig.class)
2.Junit整合
@RunWith(SpringJunit4ClassRunner.class)
替换main函数,为了能够创建容器
@ContextConfiguration(classes=SpringConfiguration.class)
@ContextConfiguration(locations=“calsspath:bean.xml”)
classes:指定注解类所在位置
locations:指定xml文件的位置,加上classpath关键字,表示在类路径下
*当使用spring5版本是,要求Junit的jar包必须是4.11以上
三.事务
1.编写工具类
1.ConnectionUtils类
连接的工具类,它用于从数据中获取一个链接,并且实现和线程的绑定
public class ConnectionUtils {
private ThreadLocal<Connection> tl=new ThreadLocal<Connection>();
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
/**
* @Description 获取当前线程上的链接
* @param
* @return java.sql.Connection
*/
public Connection getThreadConnection() {
//1.先从ThreadLocal上获取
Connection conn=tl.get();
try {
//2.判断当前线程上是否有链接
if (conn == null) {
//3.从数据源中获取一个链接,并且存入ThreadLocal中
conn = dataSource.getConnection();
tl.set(conn);
}
}catch (Exception e){
throw new RuntimeException(e);
}
//4.返回当前线程
return conn;
}
/**
* @Description 把链接和线程解绑
* @param
* @return void
*/
public void removeConnection(){
tl.remove();
}
}
2.TransactionManager类
和事务管理想关的工具类,它包括了,开启事务,提交事务,回滚事务和释放链接
public class TransactionManager {
private ConnectionUtils connectionUtils;
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
/**
* @Description 开启事务
* @param
* @return void
*/
public void beginTransaction(){
try {
connectionUtils.getThreadConnection().setAutoCommit(false);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* @Description 提交事务
* @param
* @return void
*/
public void commit(){
try {
connectionUtils.getThreadConnection().commit();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* @Description 回滚事务
* @param
* @return void
*/
public void rollback(){
try {
connectionUtils.getThreadConnection().rollback();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* @Description 释放链接
* @param
* @return void
*/
public void release(){
try {
connectionUtils.getThreadConnection().close();//还回链接池中
connectionUtils.removeConnection();//解绑
} catch (Exception e) {
e.printStackTrace();
}
}
2.更改业务层代码
以findAllAccount为例
public class AccountServiceImpl_OLD implements IAccountService {
private IAccountDao accountDao;
private TransactionManager txManger;
public void setTxManger(TransactionManager txManger) {
this.txManger = txManger;
}
public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public List<Account> findAllAccount() {
try {
//1.开启事务
txManger.beginTransaction();
//2.执行操作
List<Account> accounts = accountDao.findAllAccount();
//3.提交事务
txManger.commit();
//4.返回结果
return accounts;
}catch (Exception e){
//5.回滚操作
txManger.rollback();
throw new RuntimeException();
}finally {
//6.释放链接
txManger.release();
}
}
3.更改持久层
因为为QureyRunner注入数据源,每次都会去连接池中获取连接,为了防止获取,将注入数据源删除
并且在AccountDaoImpl类中创建connectionUtils属性替代(Spring注入),在SQL语句中加上参数
connectionUtils.getThreadConnection()
public class AccountDaoImpl implements IAccountDao {
private QueryRunner runner;
private ConnectionUtils connectionUtils;
public void setRunner(QueryRunner runner) {
this.runner = runner;
}
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
@Override
public List<Account> findAllAccount() {
try {
return runner.query(connectionUtils.getThreadConnection(), "select * from account",new BeanListHandler<Account>(Account.class));
} catch (Exception e) {
throw new RuntimeException();
}
}
4.更改bean.xml文件
注入相关依赖
<!--配置connection的工具类connectionUtils-->
<bean id="connectionUtils" class="com.hmk.utils.ConnectionUtils">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置事务管理器-->
<bean id="txManger" class="com.hmk.utils.TransactionManager">
<!--注入ConnectionUtils-->
<property name="connectionUtils" ref="connectionUtils"></property>
</bean>
<!--配置Dao-->
<bean id="accountDao" class="com.hmk.dao.Impl.AccountDaoImpl" scope="prototype">
<!--注入QueryRunner-->
<property name="runner" ref="runner"></property>
<!--注入ConnectionUtils-->
<property name="connectionUtils" ref="connectionUtils"></property>
</bean>
<!--配置accountService对象-->
<bean id="accountService" class="com.hmk.service.Impl.AccountServiceImpl">
<!--注入dao-->
<property name="accountDao" ref="accountDao"></property>
<!--事务管理器-->
<property name="txManger" ref="txManger"></property>
</bean>
四.动态代理
1.分析
/**
* 动态代理:
* 特点:字节码随用随创建,随用随加载
* 分类:基于接口
* 基于子类
*/
2.基于接口的动态代理
1.生产类
必须实现接口
2.模拟客户类
/**
* 基于接口的动态代理
* 设计了类:Proxy
* 提供者:jdk官方
* 如何创建代理对象:
* 使用Proxy类中的newProxyInstance犯法
* 创建代理对象的要求:
* 被代理类最少实现一个接口,如果没有则不能使用
* newProxyInstance方法的参数:
* ClassLoader:类加载器
* 他是用于加载代理对象字节码的。和被代理对象使用相同的类加载器。固定写法
* Class[]:字节码数组
* 他是用于让代理对象和被代理对象有相同的方法。固定写法
* InvocationHandler:用于提供增强代码
* 他是让我们写如何代理。我们一般都是写一个接口的实现类,通常情况下都是匿名内部类,但不是必须的。
* 此接口的实现类,谁用谁写
*
*/
public class Client {
public static void main(String[] args) {
final Producer producer=new Producer();
IProducer proxyProducer=(IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(),
producer.getClass().getInterfaces(),
new InvocationHandler() {
/**
* @Description 执行被代理对象的任何接口方法都会经过该方法
* @param proxy 代理对象的引用
* @param method 表示当前执行的方法
* @param objects 当前执行方法所需参数
* @return java.lang.Object 和被代理对象有相同的返回值
*/
@Override
public Object invoke(Object proxy, Method method, Object[] objects) throws Throwable {
//提供增强代码
Object returnValue=null;
//1.获取放方法执行的参数
Float money=(Float) objects[0];
//2.判断当前方法是不是销售
if("saleProduct".equals(method.getName())){
returnValue=method.invoke(producer,money*0.8f);
}
return returnValue;
}
});
proxyProducer.saleProduct(10000f);
}
3.基于子类的动态代理
public class Client {
public static void main(String[] args) {
final Producer producer=new Producer();
/**
* 基于接口的动态代理
* 设计了类:Enhancer
* 提供者:三方cglib库
* 如何创建代理对象:
* 使用Enhancer类中的create
* 创建代理对象的要求:
* 被代理类不能是最终类
* create方法的参数:
* Class:字节码
* 他是用于指定被代理对象的字节码
* Callback:用于提供增强代码
* 他是让我们写如何代理。我们一般都是写一个接口的实现类,通常情况下都是匿名内部类,但不是必须的。
* 吃接口的实现类,谁用谁写
* 我们一般写的是该接口的实现类:methodInterceptor
*
*/
Producer cglibProducer=(Producer) Enhancer.create(producer.getClass(), new MethodInterceptor() {
/**
* @Description 执行被代理对象任何方法都会经过该方法
* @param o
* @param method
* @param objects
* @param methodProxy 当前执行的方法的代理对象
* @return java.lang.Object
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//提供增强代码
Object returnValue=null;
//1.获取放方法执行的参数
Float money=(Float) objects[0];
//2.判断当前方法是不是销售
if("saleProduct".equals(method.getName())){
returnValue=method.invoke(producer,money*0.8f);
}
return returnValue;
}
});
cglibProducer.saleProduct(12000f);
4.使用动态代理实现事务控制
在之前转账问题中,如果加上事务控制就会出现很多代码重复,使用动态代理来实现减少代码量
1.编写工厂类
Beanfactory类用于创建service的代理对象工厂,在增强方法中加入事务的控制,删除了在之前AccountService类中的属性txManger,并且添加到了这里
public class BeanFactory {
private IAccountService accountService;
private TransactionManager txManger;
public void setTxManger(TransactionManager txManger) {
this.txManger = txManger;
}
public final void setAccountService(IAccountService accountService) {
this.accountService = accountService;
}
/**
* 获取Service代理对象
*/
public IAccountService getAccountService(){
return(IAccountService) Proxy.newProxyInstance(accountService.getClass().getClassLoader(),
accountService.getClass().getInterfaces(),
new InvocationHandler() {
/**
* 添加事务的支持
*/
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
Object rtValue=null;
try {
//1.开启事务
txManger.beginTransaction();
//2.执行操作
rtValue = method.invoke(accountService,objects);
//3.提交事务
txManger.commit();
//4.返回结果
return rtValue;
}catch (Exception e){
//5.回滚操作
txManger.rollback();
throw new RuntimeException();
}finally {
//6.释放链接
txManger.release();
}
}
});
}
}
2.在bean.xml中添加相关依赖
因为删除了AccountService类中的属性txManger属性,在bean.xml中也要修改
<!--配置BeanFactory-->
<bean id="BeanFactory" class="com.hmk.factory.BeanFactory">
<property name="accountService" ref="accountService"></property>
<property name="txManger" ref="txManger"></property>
</bean>
<!--配置代理的Service-->
<!--常见对象的三种方式3.spring 管理实例工厂-使用实例工厂的方法创建对象
factory-bean 属性:用于指定实例工厂 bean 的 id。
factory-method 属性:用于指定实例工厂中创建对象的方法。
-->
<bean id="proxyAccountService" factory-bean="BeanFactory" factory-method="getAccountService"></bean>
3.测试
修改获取对象的方法
//2.得到业务层对象
IAccountService as=ac.getBean("proxyAccountService",IAccountService.class);
五.AOP(重点)
1.概念
我们学习 spring 的 aop,就是通过配置的方式,实现上一章节的功能。
2.相关术语
Joinpoint(连接点):(业务层接口中所有的方法)
所谓连接点是指那些被拦截到的点。在 spring 中,这些点指的是方法,因为 spring 只支持方法类型的连接点。
Pointcut(切入点):(业务层接口中被增强的方法)
所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义。
Advice(通知/增强):
所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。
通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。
Introduction(引介):
引介是一种特殊的通知在不修改类代码的前提下, Introduction 可以在运行期为类动态地添加一些方法或 Field。
Target(目标对象):
代理的目标对象。
Weaving(织入):
是指把增强应用到目标对象来创建新的代理对象的过程。
spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入。
Proxy(代理):
一个类被 AOP 织入增强后,就产生一个结果代理类。
Aspect(切面):
是切入点和通知(引介)的结合。
3.账户操作模拟实现
1.创建Maven工程后导入坐标(pom.xml)
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.20.RELEASE</version>
</dependency>
<!--负责解析切入点表达式-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
</dependencies>
2.编写业务层模拟实现
此处主要注重类型
3.编写工具类实现方法增强的公共代码
4.编写bean.xml文件
导入坐标
<?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">
<!--配置Spring的ioc,吧service对象配置进来-->
<bean id="accountService" class="com.hmk.service.impl.AccountServiceImpl"></bean>
<!--基于xml的AOP配置步骤
1.把通知bean也交给Spring来管理
2.使用aop:config标签表明开始AOP的配置
3.使用aop:aspect标签表明配置切面
id属性:是给切面提供一个唯一标识
ref属性:是指定通知类bean的id
4.在aop:before:标识配置前置通知
method属性:用于指定Logger类中那个方法是前置通知
pointcut属性:用于指定切入点表达式,改表达式的含义指的是对业务层中哪些方法增强
切入点白表达式写法:
关键字:execution(表达式)
表达式;
访问修饰符 返回值 包名.包名...类名.方法名(参数类表)
标准写法
public void com.hmk.service.impl.AccountServiceImpl.saveAccount()
访问修饰符可以省略
void com.hmk.service.impl.AccountServiceImpl.updateAccount()
全通配写法
* *..*.*(..)
返回值可用*
包名可用*. 有几级包就用几个*.
类名和方法名可用*
参数可用 ..
实际开发中切入点表达式的通用洗发:
切到业务层实现类下的所有方法
-->
<!--配置logger类-->
<bean id="logger" class="com.hmk.utils.Logger"></bean>
<!--配置AOP-->
<aop:config>
<!--配置切面-->
<aop:aspect id="logAdvice" ref="logger">
<!--配置通知类型,并建立通知方法和切入点方法的关联-->
<aop:before method="printLog" pointcut="execution(public * com.hmk.service.impl.*.*(..))"></aop:before>
</aop:aspect>
</aop:config>
4.四种常用通知类型和通用化表达式
1.修改logger
public class Logger {
/**
* 前置通知
*/
public void beforePrintLog(){
System.out.println("前置通知类中的beforeprintLog方法开始记录日志。。。");
}
/**
* 后置通知
*/
public void afterReturningPrintLog(){
System.out.println("后置通知类中的afterReturningPrintLog方法开始记录日志。。。");
}
/**
* 异常通知
*/
public void afterThrowingPrintLog(){
System.out.println("异常通知类中的afterThrowingPrintLog方法开始记录日志。。。");
}
/**
* 最终通知
*/
public void afterPrintLog(){
System.out.println("最终通知类中的afterPrintLog方法开始记录日志。。。");
}
2.修改bean.xml
<!--配置AOP-->
<aop:config>
<!--配置切入点表达式 id属性用于指定表达式的唯一标识 expression表达式
aop:pointcut写在里面,只能当前aspect用
写在外面都能用,但是必须是在aspect前面-->
<aop:pointcut id="pt1" expression="execution(public * com.hmk.service.impl.*.*(..))"/>
<!--配置切面-->
<aop:aspect id="logAdvice" ref="logger">
<!--前置:在切入点方法执行之前执行-->
<aop:before method="beforePrintLog" pointcut-ref="pt1"></aop:before>
<!--后置:在切入点方法执行之后执行-->
<aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt1"></aop:after-returning>
<!--异常:异常后-->
<aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing>
<!--最终:无论切入点方法是否执行,都会执行-->
<aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after>
</aop:aspect>
</aop:config>
</beans>
5.环绕通知
它是 spring 框架为我们提供的一种可以在代码中手动控制增强代码什么时候执行的方式。
1.修改bean.xml
删除其他通知配置
<aop:around method="aroundPrintLog" pointcut-ref="pt1"></aop:around>
2.编写环绕通知方法
spring 框架为我们提供了一个接口:ProceedingJoinPoint,它可以作为环绕通知的方法参数。
在环绕通知执行时,spring 框架会为我们提供该接口的实现类对象,我们直接使用就行。
public Object aroundPrintLog(ProceedingJoinPoint pjp){
Object rtValue=null;
try {
Object[] args=pjp.getArgs();//得到方法执行所需参数
System.out.println("环绕通知类中的aroundPrintLog方法开始记录日志。。。前置");
rtValue =pjp.proceed(args);//明确业务层方法(切入点方法)
System.out.println("环绕通知类中的aroundPrintLog方法开始记录日志。。。后置");
return rtValue;
}catch (Throwable t){
System.out.println("环绕通知类中的aroundPrintLog方法开始记录日志。。。异常");
throw new RuntimeException(t);
}finally {
System.out.println("环绕通知类中的aroundPrintLog方法开始记录日志。。。最终");
}
}
6.基于注解的AOP
1.修改bean.xml
导入坐标且配置
<!--配置spring容器时要扫描的包-->
<context:component-scan base-package="com.hmk"></context:component-scan>
<!--配置Spring开启注解AOP的支持-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
2.修改工具类Logger
package com.hmk.utils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* 用于记录日志的工具类,他里面提供的公共的代码
* @author HMK
* @date 2021/2/19 9:48
*/
@Component("logger")
@Aspect//表示当前类是一个切面类
public class Logger {
@Pointcut("execution(public * com.hmk.service.impl.*.*(..))")
private void pt1(){}
/**
* 前置通知
*/
// @Before("pt1()")
public void beforePrintLog(){
System.out.println("前置通知类中的beforeprintLog方法开始记录日志。。。");
}
/**
* 后置通知
*/
// @AfterReturning("pt1()")
public void afterReturningPrintLog(){
System.out.println("后置通知类中的afterReturningPrintLog方法开始记录日志。。。");
}
/**
* 异常通知
*/
// @AfterThrowing("pt1()")
public void afterThrowingPrintLog(){
System.out.println("异常通知类中的afterThrowingPrintLog方法开始记录日志。。。");
}
/**
* 最终通知
*/
// @After("pt1()")
public void afterPrintLog(){
System.out.println("最终通知类中的afterPrintLog方法开始记录日志。。。");
}
/**
* 环绕通知:
* 它是 spring 框架为我们提供的一种可以在代码中手动控制增强代码什么时候执行的方式。
* spring 框架为我们提供了一个接口:ProceedingJoinPoint,它可以作为环绕通知的方法参数。
* 在环绕通知执行时,spring 框架会为我们提供该接口的实现类对象,我们直接使用就行。
*/
@Around("pt1()")
public Object aroundPrintLog(ProceedingJoinPoint pjp){
Object rtValue=null;
try {
Object[] args=pjp.getArgs();//得到方法执行所需参数
System.out.println("环绕通知类中的aroundPrintLog方法开始记录日志。。。前置");
rtValue =pjp.proceed(args);//明确业务层方法(切入点方法)
System.out.println("环绕通知类中的aroundPrintLog方法开始记录日志。。。后置");
return rtValue;
}catch (Throwable t){
System.out.println("环绕通知类中的aroundPrintLog方法开始记录日志。。。异常");
throw new RuntimeException(t);
}finally {
System.out.println("环绕通知类中的aroundPrintLog方法开始记录日志。。。最终");
}
}
}
*在使用注解AOP时,前置,后置,异常和最终通知的调用顺序会出现问题,慎重使用,但是环绕通知顺序正常
*去除xml中最后的配置
@EnableAspectJAutoProxy
六.JdbcTemplate
1.JdbcTemplate最基本的使用
1.实体类(略)
2.jdbcTemplate类
public class JdbcTemplateDemo1 {
public static void main(String[] args) {
//准备数据源:spring的内置数据源
DriverManagerDataSource ds=new DriverManagerDataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/eesy");
ds.setUsername("root");
ds.setPassword("1234");
//1.创建JdbcTemplate对象
JdbcTemplate jt=new JdbcTemplate();
//给jt设置数据源
jt.setDataSource(ds);
//2.执行
jt.execute("insert into account(name,money)value('ddd',1000)");
}
}
2.jdbcTemplate在Spring的IOC中的使用
1.将在上述类中写的配置到xml文件中即可
<?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">
<!--配置JdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置数据源-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/eesy"></property>
<property name="username" value="root"></property>
<property name="password" value="1234"></property>
</bean>
</beans>
2.修改jdbcTemplate类
//1.获取容器
ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
//2.获取对象
JdbcTemplate jt=ac.getBean("jdbcTemplate",JdbcTemplate.class);
//3.执行操作
jt.execute("insert into account(name,money)value('eee',1000)");
3.jdbcTemplate的CRUD
1.jdbcTemplate类
//1.获取容器
ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
//2.获取对象
JdbcTemplate jt=ac.getBean("jdbcTemplate",JdbcTemplate.class);
//3.执行操作
// jt.execute("insert into account(name,money)value('eee',1000)");
//保存
// jt.update("insert into account(name,money)values(?,?)","fff","2000");
//更新
// jt.update("update account set name =?,money=? where id=?","test",4567,6);
//删除
// jt.update("delete from account where id=?",6);
//查询所有
// List<Account> accounts= jt.query("select * from account where money=?",new AccountRowMapper(),1000f);
// List<Account> accounts= jt.query("select * from account where money=?",new BeanPropertyRowMapper<Account>(Account.class),1000f);
// for (Account account : accounts) {
// System.out.println(account);
// }
//查询一个
// List<Account> account= jt.query("select * from account where money=?",new BeanPropertyRowMapper<Account>(Account.class),100f);
// System.out.println(account.isEmpty()?"没有内容":account.get(0));
//查询返回一行一列(使用聚合函数,但不加group by子句)
int count=jt.queryForObject("select count(*) from account where money >?",Integer.class,100);
System.out.println(count);
}
}
2.注
*第一个所有版本都可以用
*第二个jdk1.5后才能用
4.JdbcTemplate在Dao中的使用
1.eg1
1.创建dao接口以及实现类
public class AccountDaoImpl implements IAccountDao {
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Override
public Account findAccountById(Integer accountId) {
List<Account> accounts = jdbcTemplate.query("select * from account where id=?", new BeanPropertyRowMapper<Account>(Account.class), accountId);
return accounts.isEmpty()?null:accounts.get(0);
}
@Override
public Account findAccountByName(String accountName) {
List<Account> accounts = jdbcTemplate.query("select * from account where name =?", new BeanPropertyRowMapper<Account>(Account.class), accountName);
if(accounts.isEmpty()){
return null;
}
if(accounts.size()>1){
throw new RuntimeException("结果集不唯一");
}
return accounts.get(0);
}
@Override
public void updateAccount(Account account) {
jdbcTemplate.update("update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
}
}
2.bean.xml加入配置
<!--配置账户的持久层-->
<bean id="accountDao" class="com.hmk.dao.impl.AccountDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate"></property>
</bean>
2.多个dao实现类时
存在重复代码,需要抽取公共代码
两种解决方法:
1.提供JdbcDaoSupport父类,让其他dao实现类继承该类,使用父类的get方法从而替代JdbcTemplate对象
public class JdbcDaoSupport{
private JdbcTemplate jdbcTemplate;
public JdbcTemplate getJdbcTemplate() {
return jdbcTemplate;
}
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
if (jdbcTemplate==null){
jdbcTemplate=createJdbcTemplate(dataSource);
}
}
private JdbcTemplate createJdbcTemplate(DataSource dataSource){
return new JdbcTemplate(dataSource);
}
}
修改bean.xml
<!--配置账户的持久层-->
<bean id="accountDao" class="com.hmk.dao.impl.AccountDaoImpl2">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--已注释-->
<!--配置JdbcTemplate
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>-->
2.JdbcDaoSupport已经存在只需要继承即可使用
*两种方式的使用方式,如果采用注解方式,则使用手写父类,
*如果使用xml方式,则使用继承Spring的类
七.事务控制API
1.基于XML的声明式事务控制-配置步骤
1.导入aop和tx名称空间
2.配置事务管理器
<bean id="transactionManger" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
3.配置事务通知
此时我们需要导入事务的约束 tx名称空间和约束,同时也需要aop的
4.配置aop中的通用切入点表达式
5.建立事务通知和切入点表达式的对应关系
6.配置事务属性
实在事务的通知tx:advice标签内部
指定方法名称:是业务核心方法
read-only:是否是只读事务。默认 false,只有查询时才是true。
isolation:指定事务的隔离级别。默认值是DEFAULT 使用数据库的默认隔离级别。
propagation:指定事务的传播行为,默认值REQUIRED,表示一定会有事务,增删改的选择。查询方法可以选择SUPPORTS。
timeout:指定超时时间单位为秒。默认值为:-1。永不超时。
rollback-for:用于指定一个异常,当执行产生该异常时,事务回滚。产生其他异常,事务不回滚。
没有默认值,任何异常都回滚。
no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时,事务回
滚。没有默认值,任何异常都回滚。
3,4,5,6如下
<tx:advice id="txAdvice" transaction-manager="transactionManger">
<!--事务属性-->
<tx:attributes>
<tx:method name="*" propagation="REQUIRED" read-only="false"></tx:method>
<tx:method name="find*" propagation="SUPPORTS" read-only="true"></tx:method>
</tx:attributes>
</tx:advice>
<!--配置aop-->
<aop:config>
<!--切入点表达式-->
<aop:pointcut id="pt1" expression="execution(* com.hmk.service.impl.*.*(..))"/>
<!--建立事务通知和切入点表达式的对应关系-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor>
</aop:config>
2.基于注解的声明式事务控制-配置步骤
1.修改bean.xml文件
修改名称空间
配置spring创建容器是要扫描的包
配置jdbctemplate
开启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:tx="http://www.springframework.org/schema/tx"
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/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
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--配置spring创建容器是要扫描的包-->
<context:component-scan base-package="com.hmk"></context:component-scan>
<!--配置jdbctemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置数据源-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/eesy"></property>
<property name="username" value="root"></property>
<property name="password" value="1234"></property>
</bean>
<!--配置事务管理器-->
<bean id="transactionManger" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--开启spring对注解事务的支持-->
<tx:annotation-driven transaction-manager="transactionManger"></tx:annotation-driven>
</beans>
2.在需要事务支持的类上加上注解
2.1.默认读写
//业务层
@Transactional
2.2.如果全局是只读,方法就要重新配置读写