笔记9:spring
程序的耦合
耦合:程序间的依赖关系
- 类间依赖
- 方法间依赖
解耦:降低程序间的依赖关系
- 实际开发中应做到:编译期不依赖,运行时才依赖
- 第一步:使用反射创建对象,而避免使用new关键字
- 第二步:通过读取配置文件来获取要创建的对象全限定类名
工厂模式解耦
工厂:一个创建Bean对象的工厂
Bean:在计算机英语中,有可从用组件的含义
JavaBean:用java英语编写的可重用组件
javabean > 实体类
第一个:需要一个配置文件来配置我们的service和dao
- 配置的内容:1-唯一标识=全限定类名(key=value)
第二个:通过读取配置文件中配置的内容,反射创建对象
- 配置文件可以是xml也可以是properties
- 单例对象:对象被创建一次,类内成员只初始化一次,从始至终只有一个对象
- 线程问题:多个线程访问修改同一对象容易出问题
实现单例即在通过反射创建对象时只能调用一次
- 线程问题:多个线程访问修改同一对象容易出问题
- 多例对象:对象被创建多次,
效率问题:执行效率没有单例对象高
静态代码块随着类加载只执行一次,后续通过类来创建对象时静态代码块不再执行,
即在静态代码块中为类属性赋值后,每次创建的对象对应的属性都会先有相同的值。
工厂模式
- 配置文件
accountService=com.xiaoai.t2.service.impl.AccountServiceImpl
accountDao=com.xiaoai.t2.dao.impl.AccountDaoImpl
- 工厂类
public class BeanFactory {
//定义一个properties对象
private static Properties props;
//定义一个map,用于存放我们要创建的对象,我们称之为容器
private static Map<String, Object> beans;
//使用静态代码块为properties对象赋值
static {
try {
//实例化对象
props = new Properties();
//获取配置文件流对象
InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
props.load(in);
//实例化容器
beans = new HashMap<String, Object>();
//取出properties配置文件中所有的key
Enumeration keys = props.keys();
//遍历枚举
while (keys.hasMoreElements()) {
String key = keys.nextElement().toString();
//根据key获取value
String beanPath = props.getProperty(key);
//反射创建对象
Object value = Class.forName(beanPath).newInstance();
//把key和value存入容器中
beans.put(key, value);
}
} catch (Exception e) {
throw new ExceptionInInitializerError("初始化properties失败!");
}
}
// /**
// * 根据bean的名称获取bean对象(反射创建bean)即多例
// * @param beanName
// * @return
// */
// public static Object getBean(String beanName) {
// Object bean = null;
// try {
// String beanPath = props.getProperty(beanName);
// bean = Class.forName(beanPath).newInstance();
// } catch (Exception e) {
// e.printStackTrace();
// }
// return bean;
// }
/**
* 根据bean的名称获取bean对象(工厂(容器)获取bean)即每次获取的都是同一对象,单例
* @param beanName
* @return
*/
public static Object getBean(String beanName) {
return beans.get(beanName);
}
}
ioc(控制反转)
Inversion of Control 把创建对象的权利交给框架或交给工厂
- 作用:降低程序间的依赖关系也叫削减计算机程序的耦合
通过new 来创建对象即本身创建具有独立自主控制权,而通过工厂创建(即不通过new创建)对象,工厂是通过所给予的key所对应全限定类名控制,无法独立自主控制,控制权发生了转移则成为控制反转(ioc),把控制权交个了工厂来帮忙创建对象,带来好处:降低程序间的依赖关系也叫削减计算机程序的耦合
spring中Ioc入门
基于XML的ioc配置
<!--创建spring的xmp配置文件引入依赖-->
<?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">
</beans>
- 配置bean的id(即key)class(即value==全限定类名)
<!-- 把对象的创建交给spring来管理 -->
<bean id="accountService" class="com.xiaoai.t3spring1.service.impl.AccountServiceImpl"></bean>
<bean id="accountDao" class="com.xiaoai.t3spring1.dao.impl.AccountDaoImpl"></bean>
- 加载配置文件
/*
获取核心容器对象
ApplicationContext三个常用实现类:
【ClassPathXmlApplicationContext()】 可加载类路径下配置文件,要求配置文件必须在类路径下,不在则无法加载
【FileSystemXmlApplicationContext()】 可加载磁盘任意路径下的配置文件(必须有访问权限)
【AnnotationConfigApplicationContext()】 用于读取注解创建容器的
*/
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//通过bean的id获取对象
IAccountService as = (AccountServiceImpl) ac.getBean("accountService");//获取对象方法一
IAccountDao adao = ac.getBean("accountDao",IAccountDao.class);//获取对象方法二
配置文件扫描
1、spring的xml配置文件中添加标签
<context:property-placeholder ignore-unresolvable="true" location="classpath:jdbcConfig.properties"/>
2、通过 util:properties 标签实现配置文件加载
<util:properties id="util_Spring" local-override="true" location="classpath:jdbcConfig.properties"/>
3、通过注解 @PropertySource 注解实现配置文件加载
@PropertySource("classpath:jdbcConfig.properties")
....等等
基于注解的ioc配置
为需要管理的类加上相应的注解
例如:@Component
在spring配置文件中扫描对应的包
<!--
告知spring在创建容器时要扫描的包,即扫描注解。
配置所需要的标签不是在beans的约束中,而是一个 名称为context名称控件和约束中,
所以需要先添加对应约束,然后在扫描。
-->
如:<context:component-scan base-package="com.xiaoai.t4annotation"></context:component-scan>
加载配置文件
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
通过bean的id获取对象
IAccountService as = (AccountServiceImpl) ac.getBean("accountService");
如果注解value配置相应的值(即相当于id)则要通过其来获取,没有配置默认为类名首字母小写
核心容器两个接口引发的问题
ApplicationContext:构建核心容器时,创建对象采取策略时采用立即加载的方式,也就是说,只要一读取完配置文件马上就创建配置文件中配置的bean
- 单例对象适用 开发中更多是采用此接口
BeanFactory:构建核心容器时,创建对象采取的策略时采用延迟加载的方式。也就是说,什么时候根据id获取对象时才真正创建对象
- 多例对象适用
创建bean的三种方法
【类默认构造函数创建对象】
spring配置文件中用bean标签配以id和class属性且没有其他属性和标签时。采用的就是默认构造函数创建bean对象,此时如果类中没有默认构造函数,则对象无法创建
例子:<bean id="accountService" class="com.xiaoai.t3spring1.service.impl.AccountServiceImpl"></bean>
【工厂中普通方法创建对象】
即使用某类中方法创建对象,并存入spring容器。
例子:
<bean id="instanceFacroty" class="com.xioaai.factory.InstanceFacroty"></bean>
<bean id="accountService"
factory-bean="instanceFacroty"
factory-method="getAccountService">
</bean>
【工厂中静态方法创建对象】
使用某个类中的静态方法创建对象并存入srping容器
例子:
<bean id="accountService"
class="com.xioaai.factory.StaticFacroty"
factory-method="getAccountService">
</bean>
bean的作用范围
bean标签scope属性:用于指定bean的作用范围,scope属性取值
属性值 | 说明 |
---|---|
singleton | 单例的(默认值) |
prototype | 多例的(创建多个对象) |
request | 作用于web应用的请求范围 |
session | 作用于web应用的会话范围 |
global-session | 作用于集群环境的会话范围(全局会话范围),当不是集群环境时,它就是session |
bean对象的生命周期
单例对象:
- 出生==当容器创建时
- 活着==重要容器还活着,对象一直活着
- 死亡==容器销毁,对象死亡
总结:单例对象的生命周期和容器相同
多例对象:
- 出生==当我们使用对象时spring创建对象
- 活着==对象只要是在使用过程中就一直活着
- 死亡==当对象长时间不用且没有别的对象引用时,由java垃圾回收机制销毁
spring依赖注入
(Dependency Injection)。依赖关系(在当前类需要用到其他类对象)的管理都交给spring来维护,由spring为我们提供,我们只需在配置文件中说明。依赖关系的维护即称为依赖注入,
三类能注入的数据
- 1-基本类型和String
- 2-其他bean类型(在配置文件中或者注解配置过的bean)
- 3-复杂类型/集合类型
复杂类型/集合类型例:
//set方法
public void setMyStrs(String[] myStrs) {
this.myStrs = myStrs;
}
public void setMyList(List<String> myList) {
this.myList = myList;
}
public void setMySet(Set<String> mySet) {
this.mySet = mySet;
}
public void setMyMap(Map<String, String> myMap) {
this.myMap = myMap;
}
public void setMyProps(Properties myProps) {
this.myProps = myProps;
}
spring配置文件配置:
<!--
复杂数据类型(数组/集合)的注入
用于给lis结构集合注入的标签:list array set
用于给map结构集合注入的标签:map props
结论:结构相同,标签可互换。
即:
-->
<property name="mySet"><!--list标签给set类型注入-->
<list>
<value>setAAA</value>
<value>setBBB</value>
<value>setCCC</value>
</list>
</property>
<property name="myProps"> <!--map标签可以给Properties类型注入-->
<map>
<prop key="prop1"><value>propAAA</value></prop>
<prop key="prop2"><value>propBBB</value></prop>
</map>
</property>
<bean id="accountService3" class="com.xiaoai.t3spring1.service.impl.AccountServiceImpl3">
<property name="myStrs">
<!-- 数组数据注入 -->
<array>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</array>
</property>
<property name="myList">
<!-- 集合 -->
<list>
<value>listAAA</value>
<value>listBBB</value>
<value>listCCC</value>
</list>
</property>
<property name="mySet">
<set>
<value>setAAA</value>
<value>setBBB</value>
<value>setCCC</value>
</set>
</property>
<property name="myMap">
<map>
<entry key="map1" value="mapAAA"></entry>
<entry key="map2" value="mapBBB"></entry>
<entry key="map3" value="mapCCC"></entry>
</map>
</property>
<property name="myProps">
<props>
<prop key="prop1">propAAA</prop>
<prop key="prop2">propBBB</prop>
</props>
</property>
</bean>
三种注入方式
- 1-使用构造函数提供
//提供构造方法
public AccountServiceImpl(String name, Integer age, Date birthday) {
this.name = name;
this.age = age;
this.birthday = birthday;
}
<!-- 配置文件 -->
<!--一、 构造函数注入
使用标签:bonstructor-arg 属性如下:
type:用于指定要注入的数据的数据类型,该数据类型也是构造函数中某个或某些参数的类型
index:用于指定要注入的数据给构造函数中指定索引位置的参数赋值。索引从0开始
name:用于指定给构造函数中指定名称的参数赋值 常用
===========以上三个用于指定给构造中那个参数赋值============================
value:用于提供基本类型和string类型的数据
ref:用于指定其他的bean类型数据,它指的就是在spring的Ioc核心容器中出现过的bean对象
标签出现位置:bean标签内
优势:在获取bean对象时,注入数据时必须的操作,否则对象无法创建成功
弊端:改变了bean对象的实例化方式,使我们在创建对象时,如果用不到这些数据,也必须提供
-->
<bean id="accountService" class="com.xiaoai.t3spring1.service.impl.AccountServiceImpl">
<constructor-arg type="java.lang.String" value="泰斯特" ></constructor-arg>
<constructor-arg name="age" value="18" ></constructor-arg>
<constructor-arg name="birthday" ref="now" ></constructor-arg>
</bean>
<!-- 配置一个日期对象 -->
<bean id="now" class="java.util.Date"></bean>
- 2-使用set方法提供
//对象中的set方法
public void setName(String name) {
this.name = name;
}
public void setAge(Integer age) {
this.age = age;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
<!--
二、set方法注入 更常用该方式注入数据
使用标签:property 属性如下:
name:用于指定注入数据时所调用的set方法名称
value:用于提供基本类型和String类型数据
ref:用于指定其他bean类型数据。它指的是在spring的Ioc核心容器中出现过的bean对象
出现位置:bean标签内部
优势:创建对象时没有明确的限制,可以直接使用默认的构造函数
弊端:如果有某个成员必须有值,则获取对象时有可能set方法没有执行
-->
<bean id="accountService2" class="com.xiaoai.t3spring1.service.impl.AccountServiceImpl2">
<property name="name" value="TEST"></property>
<property name="age" value="19"></property>
<property name="birthday" ref="now"></property>
</bean>
<!-- 配置一个日期对象 -->
<bean id="now" class="java.util.Date"></bean>
- 3-使用注解提供
注解:@Value
spring中Ioc常用注解
ioc容器中保存类型:map类型。key==id,value=全限定类名(即对象)
曾经xml配置:
<bean id="accountDao"
class="com.xiaoai.t3spring1.dao.impl.AccountDaoImpl"
scope=""
init-method=""
destroy-method="">
<property name="" value="" ref=""></property>
</bean>
1-创建对象
作用和xml配置文件中编写一个
注解 | 说明 |
---|---|
@Component | 用于把当前类对象存入spring容器中。 |
@Controller | 一般用于表现层 |
@Service | 一般用于服务层 |
@Repository | 一般用于持久层 |
- 以上三个注解作用和属性与Component一摸一样。
- 它们三个时spring框架为我们提供明确的三层使用的注解。
- 使我们的三层对象更加清晰
2-注入数据
作用和
【@Autowired】
自动按照类型注入。只要容器中有唯一的bean对象类型和要注入的变量类型匹配,就可以注入成功。
- 出现位置:可以是成员变量,也可以是方法上。
- 多匹配问题:如果ioc容器中有多个匹配的bean类型时,先按照类型圈定出匹配的bean对象,然后根据变量名称在圈定出的bean对象查找,如果找到也能注入,如果找不到则报错。
//使用注解将类交个ioc管理
@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {}
//通过注解注入对象
//当有多个bean匹配时,由于上面有bean名称叫accountDao,变量名也叫accountDao,所以可以注入
@Autowired
private IAccountDao accountDao;
【@Qualifier】
作用为按照类中注入的基础之上再按照名称注入。它在给类成员注入时不能单独使用,要和@Autowired一起用。但是在给方法参数注入时可以(给方法参数注入相当于@Resource一样可通过名称直接)
- 属性value:指定注入bean对象的id
@Autowired
@Qualifier("accountDao2")
private IAccountDao accountDao;
@Bean(name = "runner")
@Scope("prototype")
public QueryRunner createQueryRunner(@Qualifier("ds2")DataSource dataSource) {
return new QueryRunner(dataSource);
}
【@Resource】
直接按照bean的id给某对象注入数据,它可独立使用。
- 属性name:用于指定注入bean对象的id
以上三个注解都只能注入其他bean类型的数据,而基本类型和String类型无法使用上述注解实现。
另外集合类型的注入只能通过xml来实现。
【@value】
用于注入基本类型和string类型的数据
- 属性value:用于指定数据的值。它可以使用spring中SpEl(也就是spring的el表达式)
- 使用SpEl时可以先把配置文件加入容器:如在配置类上加注解@PropertySource("classpath:jdbcConfig.properties")
- SpEl的写法:$
3-改变作用范围
作用和在
【@Scope】:用于指定的bean的作用范围
- 位置:可写在类上面、配置类方法上
- 属性value:指定范围的取值。常用取值:singleton(默认)、prototype
4-生命周期有关
作用和在
- 位置:写在方法上
【@PostConstruct】:用于指定初始化方法
【@ProDestroy】:用于指定销毁方法
Spring中的新注解
【@Configuration】
指定当前类为一个配置类
- 细节:当配置类作为AnnotationConfigApplicationContext对象创建的参数时,该注解可以不写
//此时SpringConfiguration类上的@Configuration可省略不写
ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
【@ComponentScan】
用于通过注解指定spring在创建容器时要扫描的包
- 属性basePackages:它和配置文件扫描包中属性basePackages的作用一样,都是用于指定创建容器时要扫描的包。
使用此注解就等同于在xml中配置了:
<context:component-scan base-package="com.xiaoai.t5dbutils"></context:component-scan>
【@Bean】 用于把当前方法的返回值作为bean对象存入spring的ioc容器中
-
属性name:用于指定bean的id,当不指定时默认值为当前方法名称
-
细节:当我们使用注解配置方法时,如果方法有参数,spring框架会去容器中查找有没有可用的bean对象
- 查找方式和@Autowired注解一样
【@Import】 导入其他配置类
即在主配置类上面通过该注解导入其他小块配置类,这样在创建AnnotationConfigApplicationContext对象时,既不用把其他小块配置类当参数传入,也不需要在其他小块配置类上添加@Configuration注解
- 使用@Import注解后,有@Import注解的类就是父类,导入的都是子配置类
//其他小块配置类
public class JdbcConfig {
@Bean(name = "runner")
@Scope("prototype")
public QueryRunner createQueryRunner(DataSource dataSource) {
return new QueryRunner(dataSource);
}
@Bean(name = "dataSource")
public DataSource createDataSource() {
ComboPooledDataSource ds;
try {
ds = new ComboPooledDataSource();
ds.setDriverClass("com.mysql.jdbc.Driver");
ds.setJdbcUrl("jdbc:mysql://localhost:3306/jfs_mybatis");
ds.setUser("root");
ds.setPassword("root");
} catch (Exception e) {
throw new RuntimeException();
}
return ds;
}
}
// 导入配置
@Configuration
@ComponentScan(basePackages = {"com.xiaoai.t5dbutils"})
@Import(JdbcConfig.class)
public class SpringConfiguration {}
//获取容器
ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
【@PropertySource】 用于指定properties文件的位置
- 属性value:指定文件的名称和路径
- 关键字classpath:表示类路径下
//有其他包路径可以这样写:classpath:conf/spring/jdbcConfig.properties
@PropertySource("classpath:jdbcConfig.properties")
public class SpringConfiguration {}
spring整合junit
1-导入spring整合junit的jar
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
2-使用junit提供的一个注解把junit原有的main方法替换了,替换成spring提供的
【@RunWith】把junit原有的main方法替换了,替换成spring提供的
例如:@RunWith(SpringJUnit4ClassRunner.class)
3-告知spring的运行器,spring和ioc创建是基于xml还是注解的,并说明位置。
【@ContextConfiguration】
- 属性locations:指定xml文件位置,加上classpath关键字,表示在类路径下
- 属性classes:指定注解类所在地位置
例如:@ContextConfiguration(classes = SpringConfiguration.class)
当使用spring5.x版本的时候,要求junit的jar必须是4.12及以上
aop( 面向对象编程)
(Aspect Oriented Programming) 面向对象编程,类似使用动态代理增强方法
- 实现方式:动态代理
spring配置中可以手动控制是通过基于接口还是基于子类的动态代理
词:
- 连接点(Joinpoint):指那些被拦截到的点。指方法,因为spring只支持方法类型的连接点
- 切入点(Pointcut):被动态代理增强的方法(所有的切入点都是连接点,但不是所有连接点都是切入点)
- 通知/增强(Advice):指拦截到Joinpoint之后要做的事情就是通知
通知类型:
- 前置通知
- 后置通知
- 异常通知
- 最终通知
- 环绕通知
public IAccountService getAccountService(){
IAccountService proxyAccountService = (IAccountService) Proxy.newProxyInstance(
accountService.getClass().getClassLoader(),
accountService.getClass().getInterfaces(), new InvocationHandler() {
@Override //整个invoke方法在执行就是环绕通知
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object rtValue = null;
try {
//开启事务
txManager.beginTransaction(); //即前置通知
//执行操作
//切入点方法具体执行:环绕通知中有明确的切入点方法调用
rtValue = method.invoke(accountService,args);
//提交事务
txManager.commit(); //后置通知
//返回结果
return rtValue;
}catch (Exception e){
//回滚事务
txManager.rollback(); //异常通知
throw new RuntimeException(e);
}finally {
//关闭连接
txManager.close(); //最终通知
}
}
});
return proxyAccountService;
}
后置通知和异常通知永远只能执行一个
引介(Introduction):特殊的通知,在不修改代码前提下,引介可以在运行期为类动态地添加一些方法Field。
目标对象(Target):代理的目标对象
织入(Weaving):指把增强应用到目标对象来创建新的代理对象的过程
代理(Proxy):一个类被aop织入增强后,就产生一个结果代理类。
切面(Asect):切入点和通知(引介)的结合
明确:
- 开发阶段 我们 编写核心业务代码(开发主线) 公用代码抽取,制作成通知 配置文件中,声明切入点与通知关系,即切面
- 运行阶段 spring框架监控切入点方法执行,一执行代理机制创建对象,根据通知类别,在代理对象对应位置将通知织入,完成完整代码逻辑
基于xml配置aop
spring中基于xml的aop配置步骤:
- 把通知的Bean交给spring来管理
- 使用aop:config标签名开始aop配置
- 使用aop:aspect标签名配置切面
- 属性id:是给切面提供一个唯一标识
- 属性ref:指定通知类bean的id
- aop:aspect标签内部使用对应标签配置通知类型并绑定切入点方法
- 属性method:用于指定通知bean那个方法时相应的通知
- 属性pointcut:用于指定切入点表达式,该表达式含义指的是对代理对象中那些方法增强
- jar包:org.aspectj可以用来帮我们解析切入点表达式
切入点表达式写法:
- 格式:execution(表达式[写法:访问修饰符 返回值 包名.包名...类名.方法名(参数列表)])
- 如表达式:public void com.xiaoai.t8aop.service.impl.AccountServiceImpl.saveAccount()
- 访问修饰符可省
void com.xiaoai.t8aop.service.impl.AccountServiceImpl.saveAccount()
- 返回值可以使用通配符*,表示任意返回值
com.xiaoai.t8aop.service.impl.AccountServiceImpl.saveAccount()
- 包名可以使用通配符*,但有几级包就需要.几个*
* *.*.*.*.*.AccountServiceImpl.saveAccount()
- 包名可以使用..表示当前包及其子包
* *..AccountServiceImpl.saveAccount()
- 类名和方法名都可以使用通配符*来表示
* *..*.*()
- 参数列表可以直接写类型:基本类型直接写名称(如:int) 引用类型写包名.类名 (如:java.lang.String)
* *..*.*(int)或* *..*.*(java.lang.String)
- 可以使用通配符表示任意类型参数,但必须有参数(即*无法匹配没有参数的方法)
* *..*.*(int)或* *..*.*(*)
- 可以使用..表示有无参数都可以
* *..*.*(int)或* *..*.*(..)
- 全通配写法
* *..*.*(..)
实际开发中切入点表达式的通常写法:切到代理对象类下的所有方法
如:* com.xiaoai.t8aop.service.impl..(..)
例:
<!--配置spirng的ioc,把service对象配置进来-->
<bean id="accountService" class="com.xiaoai.t8aop.service.impl.AccountServiceImpl"></bean>
<!--配置Logger类-->
<bean id="logger" class="com.xiaoai.t8aop.utils.Logger"></bean>
<!--配置aop-->
<aop:config>
<!--配置切面-->
<aop:aspect id="logAdvice" ref="logger">
<!--配置前置通知,并且建立通知方法和切入点方法的关联-->
<aop:before method="printLog"
pointcut="execution(public void com.xiaoai.t8aop.service.impl.AccountServiceImpl.saveAccount())">
</aop:before>
</aop:aspect>
</aop:config>
可以在切面内配置切入点表达式并通过pointcut-ref属性引用
<!--配置aop-->
<aop:config>
<!--
配置切入点表达式
属性id:用于指定表达式唯一标识
属性expression:用于指定表达式内容
此标签写在 <aop:aspect>标签内部只能当前切面使用。
它还可以写在<aop:aspect>标签外面,此时变成了所有切面都可使用,但必须放置在切面前面
-->
<aop:pointcut id="pt1" expression="execution(* com.xiaoai.t8aop.service.impl.*.*(..))"/>
<!--配置切面-->
<aop:aspect id="logAdvice" ref="logger">
<!--通过pointcut—ref属性引用切入点表达式-->
<aop:before method="before" pointcut-ref="pt1"></aop:before>
<!--后置通知-->
<aop:after-returning method="afterReturning" pointcut-ref="pt1"></aop:after-returning>
<!--异常通知-->
<aop:after-throwing method="afterThrowing" pointcut-ref="pt1"></aop:after-throwing>
<!--最终通知-->
<aop:after method="after" pointcut-ref="pt1"></aop:after>
</aop:aspect>
</aop:config>
环绕通知
<aop:pointcut id="pt1" expression="execution(* com.xiaoai.t8aop.service.impl.*.*(..))"/>
<!--配置切面-->
<aop:aspect id="logAdvice" ref="logger">
<!--环绕通知-->
<aop:around method="around" pointcut-ref="pt1"></aop:around>
</aop:aspect>
问题:配置了环绕通知后,切入点方法没有执行,而通知方法执行了
分析:通过对比动态代理中的环绕通知,发现动态代理的环绕通知有明确的切入点方法调用,而环绕通知方法没有
解决:
- spring框架提供了一个接口,ProceedingJoinPoint。
- 该接口有一个方法proceed(),此方法就相当于明确调用切入点方法。
- 该接口可以作为环绕通知的方法参数,在程序执行时,spirng框架会为我们提供该接口的实现类供我们使用
spring中环绕通知:是spring框架为我们提供一种可以在代码中手动控制增强方法如何执行的方式
/**
* 环绕通知
*/
public Object around(ProceedingJoinPoint pjp){
System.out.println("环绕通知开始。。。。。。。。。。。。");
Object rtValue = null;
try {
System.out.println("前置通知。。。。。。。。。。。。");//写这里表示前置通知
Object[] args = pjp.getArgs();//得到方法运行时所需的参数
rtValue = pjp.proceed(); //明确调用代理方法(切入点方法)
System.out.println("后置通知。。。。。。。。。。。。");//写这里表示后置通知
return rtValue;
} catch (Throwable throwable) {
System.out.println("异常通知。。。。。。。。。。。。");//写这里表示异常通知
throw new RuntimeException();
}finally {
System.out.println("最终通知。。。。。。。。。。。。");//写这里表示最终通知
}
}
基于注解配置aop
spring配置中配置扫描注解的包以及开启注解支持
<!--配置spring创建容器时要扫描的包-->
<context:component-scan base-package="com.xiaoai.t9aopAnnotation"></context:component-scan>
<!--配置spring开启注解aop的支持-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
把相应Bean配置进ioc容器,
- @Aspect注解配置aop切面类,定义切入点表达式以及相应通知方法配置相应注解
@Component("logger")
@Aspect//表示当前类是一个切面类
public class Logger {
//定义一个切入点表达式
@Pointcut("execution(* com.xiaoai.t9aopAnnotation.service.impl.*.*(..))")
private void pt1(){}
@Before("pt1()")//前置通知 引用切入点表达式时必须带有括号
public void before(){System.out.println("前置通知。。。。。。。。。。。");}
@AfterReturning("pt1()")//后置通知
public void afterReturning(){System.out.println("后置通知。。。。。。。。。。。");}
@AfterThrowing("pt1()")//异常通知
public void afterThrowing(){System.out.println("异常通知。。。。。。。。。。。");}
@After("pt1()")//最终通知
public void after(){System.out.println("最终通知。。。。。。。。。。。");}
//@Around("pt1()")//环绕通知
public Object around(ProceedingJoinPoint pjp){
System.out.println("环绕通知开始。。。。。。。。。。。。");
Object rtValue = null;
try {
System.out.println("前置通知。。。。。。。。。。。。");
Object[] args = pjp.getArgs();//得到方法运行时所需的参数
rtValue = pjp.proceed();//明确调用代理方法(切入点方法)
System.out.println("后置通知。。。。。。。。。。。。");
return rtValue;
} catch (Throwable throwable) {
System.out.println("异常通知。。。。。。。。。。。。");
throw new RuntimeException();
}finally {
System.out.println("最终通知。。。。。。。。。。。。");//写这里表示最终通知
}
}
}
问题:基于注解配置的前置通知、后置通知、异常通知、最终通知调用顺序有点问题,需要慎重考虑是否直接注解配置。而基于注解配置的环绕通知是没有该问题的。
纯注解配置
即扫描包和开启aop支持也用注解配置
//扫描包:
@ComponentScan(basePackages = {"com.xiaoai.t9aopAnnotation"})
//开启aop支持:
@EnableAspectJAutoProxy
spring中的JdbcTemplate
当有多个dao,代码重复部分很多时可以继承一个类,该类中把dao中公共代码提取出来。
例如:
-
dao类获取template对象:可以在dao类中继承相关获取template的类,在相关类中创建template对象
-
template对象在spring配置中dao类的Bean可以不再注入template而是直接注入DataSource
-
比如dao实现类继承JdbcDaoSupper类,通过该类获取jdbctempte对象,
-
继承和xml配置两者区别:通过继承,由于该类为jar包中的类,再想通注解进行则变得麻烦了
spring自带事务管理
spring中基于xml的声明式事务控制配置步骤
-
1-配置事务管理器(自己写或spring提供的)
-
2-配置事务通知
导入事务约束 tx名称空间和约束,
使用tx:advice标签配置事务通知 -
属性id:事务通知起唯一的标识
-
属性transaction-manager:给事务通知提供一个事务管理器引用
例如: <tx:advice id="txAdvice" transaction-manager="transactionManager"></tx:advice>
-
3-配置AOP中的通用切入点表达式
-
4-建立事务通知和切入点表达式的对应关系
-
5-配置事务属性
- 是在事务的通知tx:advice标签的内部
<!--1-配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--2-配置事务通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!--5-配置事务属性-->
<tx:attributes>
<!--
<tx:method>标签属性
name 表示服务层的方法即切入点
isolation="" 用于指定事务的隔离级别,默认值为DEFAULT,表示数据库的默认隔离级别
propagation="" 用于指定事务的传播行为。默认值为REQUIRED,表示一定会有事务,增删改的选择,查询方法可以选择SUPPORTS.
read-only="" 用于指定事务是否只读,只有查询方法才能设置为true,默认值为false,表示读写
timeout="" 用于指定事务的超时时间,默认值时-1,表示永不超时。如果指定了数值,以秒为单位
rollback-for="" 用于指定一个异常,当产生该异常时,事务回滚,产生其他异常时,事务不回滚。无默认值,表示任何异常都回滚
no-rollback-for="" 用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时,事务回滚。无默认值,表示任何异常都回滚
-->
<tx:method name="transfer" propagation="REQUIRED" read-only="false"/>
<tx:method name="find*" propagation="SUPPORTS" read-only="true"/>
</tx:attributes>
</tx:advice>
<!--3-配置aop-->
<aop:config>
<!--3-1-配置切入点表达式-->
<aop:pointcut id="pt1" expression="execution(* com.xiaoai.t2tx.service.impl.*.*(..))"/>
<!--4-建立切入点表达式和事务通知的对应关系-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor>
</aop:config>
spring中基于注解的声明式事务控制配置步骤
- 1-配置事务管理器
- 2-开启spring对注解事务的支持
- 3-在需要事务支持的地方使用@Transactional注解(比如服务层类上面,配置在方法上也行)
- 如果要配置事务属性可以在@Transactional注解后面跟上参数即可
<!--配置spring创建容器时扫描的包,以便把注解bean放入容器-->
<context:component-scan base-package="com.xiaoai.t3txAnnotation"></context:component-scan>
<!--1-配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--开启spring对注解事务的支持-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
//使用注解
@Transactional
public class AccountServiceImpl implements IAccountService {.....}
//配置事务属性例子
@Service("accountService")
@Transactional(propagation = Propagation.SUPPORTS,readOnly = true)
public class AccountServiceImpl implements IAccountService {
@Autowired
private IAccountDao accountDao;
public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public Account findAccountById(Integer accoountId) {
Account account = accountDao.findAccountById(accoountId);
return account;
}
//由于该方法的属性与类上配置的属性不同,所以要单独配置,其他不一致的方法也一样要单独配
@Transactional(propagation = Propagation.REQUIRED,readOnly = false)
@Override
public void transfer(String sourceName, String targetName, Float money) {
//执行操作
//根据名称查询转出账户
Account source = accountDao.findAccountByName(sourceName);
//根据名称查询转入账户
Account target = accountDao.findAccountByName(targetName);
//转出账户减钱
source.setMoney(source.getMoney()-money);
//转入账户加钱
target.setMoney(target.getMoney()+money);
//更新转出账户
accountDao.updateAccount(source);
int i = 2/0; //出现错误,由于每次连接不同所以下面的操作会失败
//更新转入账户
accountDao.updateAccount(target);
}
}
如果需要通过注解开启注解事务的支持,注解为:@EnableTransactionManagement //开启事务注解的支持
spring基于编程式事务控制
在实际开发过程中,使用非常少
spring5新特性
......