1 Spring概述
1.1 Spring是什么
Spring是分层的Java SE/EE应用 full-stack轻量级开源框架,以IoC(Inverse Of Control:反转控制)和AOP(Aspect Oriented Programming:面向切面编程)为内核,提供了展现层Spring MVC和持久层Spring JDBC以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多著名的第三方框架和类库,逐渐成为使用最多的Java EE企业应用开源框架。
1.2 Spring优势
方便解耦,简化开发
通过Spring提供的IoC容器,可以将对象间的依赖关系交由Spring进行控制,避免硬编码所造成的过度程序耦合。用户也不必再为单例模式类、属性文件解析等这些很底层的需求编写代码,可以更专注于上层的应用。
1、AOP编程的支持
通过Spring的AOP功能,方便进行面向切面的编程,许多不容易用传统OOP实现的功能可以通过AOP轻松应付。
2、声明式事务的支持
可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活的进行事务的管理,提高开发效率和质量。
3、方便程序的测试
可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随手可做的事情。
4、方便集成各种优秀框架
Spring可以降低各种框架的使用难度,提供了对各种优秀框架(Struts、Hibernate、Hessian、Quartz等)的直接支持。
5、降低JavaEE API的使用难度
Spring对JavaEE API(如JDBC、JavaMail、远程调用等)进行了薄薄的封装层,使这些API的使用难度大为降低。
1.3 spring的体系结构
2 IoC的概念和作用
2.1 程序的耦合和解耦
程序的耦合
程序间的依赖
方法间的依赖
解耦
降低程序间的依赖关系
实际开发中
应该做到,编译器不依赖,运行时才依赖
解耦的思路
第一步,使用反射来创建对象,而避免使用new关键字。
第二步,读取配置文件来获取要创建的对象全限定类名
2.1.1 什么是程序的耦合
耦合性(Coupling),也叫耦合度,是对模块间关联程度的度量。耦合的强弱取决于模块间接口的复杂性、调用模块的方式以及通过界面传送数据的多少。模块间的耦合度是指模块之间的依赖关系,包括控制关系、调用关系、数据传递关系。模块间联系越多,其耦合性越强,同时表明其独立性越差( 降低耦合性,可以提高其独立性)。耦合性存在于各个领域,而非软件设计中独有的,但是我们只讨论软件工程中的耦合。 在软件工程中,耦合指的就是就是对象之间的依赖性。对象之间的耦合越高,维护成本越高。因此对象的设计应使类和构件之间的耦合最小。软件设计中通常用耦合度和内聚度作为衡量模块独立程度的标准。划分模块的一个准则就是高内聚低耦合。
2.1.2 耦合分类
(1) 内容耦合。当一个模块直接修改或操作另一个模块的数据时,或一个模块不通过正常入口而转入另一个模块时,这样的耦合被称为内容耦合。内容耦合是最高程度的耦合,应该避免使用之。
(2) 公共耦合。两个或两个以上的模块共同引用一个全局数据项,这种耦合被称为公共耦合。在具有大量公共耦合的结构中,确定究竟是哪个模块给全局变量赋了一个特定的值是十分困难的。
(3) 外部耦合 。一组模块都访问同一全局简单变量而不是同一全局数据结构,而且不是通过参数表传递该全局变量的信息,则称之为外部耦合。
(4) 控制耦合 。一个模块通过接口向另一个模块传递一个控制信号,接受信号的模块根据信号值而进行适当的动作,这种耦合被称为控制耦合。
(5) 标记耦合 。若一个模块A通过接口向两个模块B和C传递一个公共参数,那么称模块B和C之间存在一个标记耦合。
(6) 数据耦合。模块之间通过参数来传递数据,那么被称为数据耦合。数据耦合是最低的一种耦合形式,系统中一般都存在这种类型的耦合,因为为了完成一些有意义的功能,往往需要将某些模块的输出数据作为另一些模块的输入数据。
(7) 非直接耦合 。两个模块之间没有直接关系,它们之间的联系完全是通过主模块的控制和调用来实现的。
总结: 耦合是影响软件复杂程度和设计质量的一个重要因素,在设计上我们应采用以下原则:如果模块间必须存在耦合,就尽量使用数据耦合,少用控制耦合,限制公共耦合的范围,尽量避免使用内容耦合。
内聚与耦合 内聚标志一个模块内各个元素彼此结合的紧密程度,它是信息隐蔽和局部化概念的自然扩展。内聚是从功能角度来度量模块内的联系,一个好的内聚模块应当恰好做一件事。它描述的是模块内的功能联系。耦合是软件结构中各模块之间相互连接的一种度量,耦合强弱取决于模块间接口的复杂程度、进入或访问一个模块的点以及通过接口的数据。 程序讲究的是低耦合,高内聚。就是同一个模块内的各个元素之间要高度紧密,但是各个模块之间的相互依存度却要不那么紧密。
内聚和耦合是密切相关的,同其他模块存在高耦合的模块意味着低内聚,而高内聚的模块意味着该模块同其他模块之间是低耦合。在进行软件设计时,应力争做到高内聚,低耦合。
2.1.3 解决程序耦合的思路
Bean:在计算机英语中,又可重复组件的含义
JavaBean:用java语言编写的可重复组件
JavaBean >实体类 它就是创建我们的service和dao对象的。
第一个:需要配置文件来配置我们的service和dao
配置的内容:唯一标志=全限定类名 (key=value)
配置文件可以是xml也可以是properties(配置简单)
第二个:通过读取配置文件中配置的你内容,反射创建对象。
2.1.4 控制反转 IOC
控制反转(Inversion Of Control) 把创建对象的权利交给框架,是框架的重要特征,并非面向对象编程的专用术语。它包括依赖注入
3 使用spring的IOC解决程序耦合
3.1 spring的安装
官网:http://spring.io/
下载地址: http://repo.springsource.org/libs-release-local/org/springframework/spring
解压:(Spring目录结构:)
* docs :API和开发规范.
`* libs :jar包和源码. * schema :约束.`
我们上课使用的版本是spring5.0.2。 特别说明: spring5版本是用jdk8编写的,所以要求我们的jdk版本是8及以上。 同时tomcat的版本要求8.5及以上。
3.2 基于XML的配置
3.2.1 安装设置
1、拷贝必备的jar包到工程的lib目录中。
2、在类的根路径下(src目录下)创建一个任意名称的xml文件(不能是中文,可以是bean.xml),给配置文件导入约束:/spring-framework-5.0.2.RELEASE/docs/spring-framework-reference/html5/core.html
<?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>
3、让spring管理资源,在配置文件中配置service和dao
<!-- bean标签:用于配置让spring创建对象,并且存入ioc容器之中
id属性:对象的唯一标识。
class属性:指定要创建对象的全限定类名
-->
<!-- 配置service -->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"> </bean>
<!-- 配置dao -->
<bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl"></bean>
4、测试配置是否成功
public static void main(String[] args) {
//1.使用ApplicationContext接口,就是在获取spring容器
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.根据bean的id获取对象
IAccountService aService = (IAccountService) ac.getBean("accountService");
System.out.println(aService);
IAccountDao aDao = (IAccountDao) ac.getBean("accountDao"); System.out.println(aDao); } }
3.2.3 BeanFactory 和ApplicationContext 的区别
BeanFactory 才是Spring 容器中的顶层接口。
ApplicationContext 是它的子接口。
BeanFactory 和ApplicationContext 的区别:创建对象的时间点不一样。
ApplicationContext:只要一读取配置文件,默认情况下就会创建对象。
BeanFactory:什么使用什么时候创建对象。
3.2.4 ApplicationContext 接口的实现类
1、ClassPathXmlApplicationContext: 它是从类的根路径下加载配置文件 推荐使用这种
2、FileSystemXmlApplicationContext: 它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置。
3、AnnotationConfigApplicationContext: 当我们使用注解配置容器对象时,需要使用此类来创建spring 容器。它用来读取注解。
3.2.5 IOC中bean标签和管理对象细节
3.2.5.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:指定类中销毁方法名称。
3.2.5.2 bean的作用范围和生命周期
单例对象:scope="singleton"
一个应用只有一个对象的实例。它的作用范围就是整个引用。
生命周期: 对象出生:当应用加载,创建容器时,对象就被创建了。 对象活着:只要容器在,对象一直活着。 对象死亡:当应用卸载,销毁容器时,对象就被销毁了。
多例对象:scope="prototype"
每次访问对象时,都会重新创建对象实例。
生命周期: 对象出生:当使用对象时,创建新的对象实例。 对象活着:只要对象在使用中,就一直活着。 对象死亡:当对象长时间不用时,被java的垃圾回收器回收了。
3.2.5.3 实例化Bean的三种方式
第一种方式:使用默认无参构造函数
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"/>
第二种方式:spring管理静态工厂-使用静态工厂的方法创建对象
模拟一个静态工厂,创建业务层实现类
public class StaticFactory { public static IAccountService createAccountService(){ return new AccountServiceImpl(); } }
<bean id="accountService" class="com.itheima.factory.StaticFactory" factory-method="createAccountService"></bean>
第三种方式:spring管理实例工厂-使用实例工厂的方法创建对象
模拟一个实例工厂,创建业务层实现类
此工厂创建对象,必须现有工厂实例对象,再调用方法
public class InstanceFactory {
public IAccountService createAccountService(){
return new AccountServiceImpl();
}
}
这种方法先把工厂的创建交给spring来管理。然后在使用工厂的bean来调用里面的方法
factory-bean属性:用于指定实例工厂bean的id。
factory-method属性:用于指定实例工厂中创建对象的方法。
二 使用spring的IoC的实现账户的CRUD
2.2 环境搭建
1 拷贝jar包
2 创建数据库和编写实体类
3 编写持久层代码
4 编写业务层代码
2.2.1 配置对象
<?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">
<!-- 配置service -->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"> </property>
</bean>
<!-- 配置dao -->
<bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
<property name="dbAssit" ref="dbAssit"> </property>
</bean>
<!-- 配置dbAssit 此处我们只注入了数据源,表明每条语句独立事务-->
<bean id="dbAssit" class="com.itheima.dbassit.DBAssit">
<property name="dataSource" ref="dataSource"></property> </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:///spring_day02"></property>
<property name="user" value="root"></property> <property name="password" value="1234"></property>
</bean>
</beans>
5 测试类代码
3 基于注解的IOC配置
注解配置和xml配置要实现的功能都是一样的,都是要降低程序间的耦合。只是配置的形式不一样。 关于实际的开发中到底使用xml还是注解,每家公司有着不同的使用习惯。所以这两种配置方式我们都需要掌握。
环境搭建
1、拷贝必备jar包到工程的lib目
2、使用@Component注解配置管理的资源
3、创建spring的xml配置文件并开启对注解的支持
3.1 常用注解
3.1.1 用于创建对象的
相当于:
1、@Component作用: 把资源让spring来管理。相当于在xml中配置一个bean。 属性: value:指定bean的id。如果不指定value属性,默认bean的id是当前类的类名。首字母小写。
**2、 @Controller @Service @Repository **
他们三个注解都是针对一个的衍生注解,他们的作用及属性都是一模一样的。 他们只不过是提供了更加明确的语义化。
@Controller:一般用于表现层的注解。
@Service:一般用于业务层的注解。
@Repository:一般用于持久层的注解。 细节:如果注解中有且只有一个属性要赋值时,且名称是value,value在赋值是可以不写。
3.1.2 用于注入数据的
相当于:
@Autowired作用: 自动按照类型注入。当使用注解注入属性时,set方法可以省略。它只能注入其他bean类型。当有多个类型匹配时,使用要注入的对象变量名称作为bean的id,在spring容器查找,找到了也可以注入成功。找不到就报错。
@Qualifier作用: 在自动按照类型注入的基础之上,再按照Bean的id注入。它在给字段注入时不能独立使用,必须和@Autowire一起使用;但是给方法参数注入时,可以独立使用。 属性: value:指定bean的id。
**@Resource ** 作用: 直接按照Bean的id注入。它也只能注入其他bean类型。 属性: name:指定bean的id。
**@Value ** 作用: 注入基本数据类型和String类型数据的 属性: value:用于指定值
3.1.3 用于改变作用范围的:
相当于:
@Scope
作用: 指定bean的作用范围。** 属性: value:指定范围的值。 取值:singleton prototype request session globalsession**
3.1.4 和生命周期相关的:(了解)
相当于:<bean id="" class="" init-method="" destroy-method="" />
@PostConstruct 作用: 用于指定初始化方法。
**@PreDestroy **作用: 用于指定销毁方法。
其他注解
**@Configuration **
作用: 用于指定当前类是一个spring配置类,当创建容器时会从该类上加载注解。获取容器时需要使用AnnotationApplicationContext(有@Configuration注解的类.class)。
@ComponentScan
作用: 用于指定spring在初始化容器时要扫描的包。作用和在spring的xml配置文件中的: <context:component-scan base-package="com.itheima"/>是一样的。
属性: basePackages:用于指定要扫描的包。和该注解中的value属性作用一样。
@Bean
作用: 该注解只能写在方法上,表明使用此方法创建一个对象,并且放入spring容器。
属性: name:给当前@Bean注解方法创建的对象指定一个名称(即bean的id)。
@PropertySource‘
作用:用于加载.properties文件中的配置。例如我们配置数据源时,可以把连接数据库的信息写到properties配置文件中,就可以使用此注解指定properties配置文件的位置。
属性: value[]:用于指定properties文件位置。如果是在类路径下,需要写上classpath:
属性: value:用于指定配置类的字节码
@Import
作用: 用于导入其他配置类,在引入其他配置类时,可以不用再写@Configuration注解。当然,写上也没问题。
属性: value[]:用于指定其他配置类的字节码。
3.2 关于Spring注解和XML的选择问题
注解的优势: 配置简单,维护方便(我们找到类,就相当于找到了对应的配置)。
XML的优势: 修改时,不用改源码。不涉及重新编译和部署。
4 代码优化
4.1 问题
程序运行获取了好多个连接,有一部分代码执行了后面的代码没执行,导致整个账号余额对不上。
`public void transfer(String sourceName, String targetName, Float money) {`
//1. 根据名称查询转出账户
Account source = accountDao.findAccountByName(sourceName);
//2. 根据名称查询转入账户
Account target = accountDao.findAccountByName(targetName);
//3. 转出账户减钱
source.setMoney(source.getMoney()-money);
//4. 转入账户加钱
target.setMoney(target.getMoney()+money);
//5. 更新转出账户
accountDao.updateAccount(source);
int i= 1/0;
//6. 更新转入账户
accountDao.updateAccount(target);
}
}
4.2 解决办法
需要使用ThreadLocal对象把Connection和当前线程绑定,从而使一个线程中只有一个能控制事务的对象。
创建一个ConnectionUtils类。连接的工具类,它用于从数据源中获取一个连接,并且实现和线程的绑定
1 连接上的工具类,他用于从数据源中获取一个连接,并且实现和线程的绑定
public class ConnectionUtils {
private ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
private DataSource dataSource;
public DataSource getDataSource() {
return dataSource;
}
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
// 获取当前线程上的连接
public Connection getThreadConnection(){
try {
//1. 先从ThreadLocal 上获取
Connection conn = tl.get();
//2 判断当前线程上是否有连接
if(conn == null) {
//3 从数据中获取一个连接,并且存入ThreadLocal中(和线程绑定)
conn = dataSource.getConnection();
tl.set(conn);
}
// 4 返回当前线程上的连接
return conn;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public void removeConnection(){
tl.remove();
}
}
2 建立和管理相关的工具类,他包含了:开启事务,提交事务,回滚事务和释放事务
public class TransactionManager {
private ConnectionUtils connectionUtils;
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
//开启事务
public void beginTrancaction(){
try {
connectionUtils.getThreadConnection().setAutoCommit(false);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
//提交事务
public void commit(){
try {
connectionUtils.getThreadConnection().commit();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
//回滚事务
public void rollback(){
try {
connectionUtils.getThreadConnection().rollback();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
//开启事务
public void release(){
try {
connectionUtils.getThreadConnection().close();// 还回连接池中
connectionUtils.removeConnection(); // 解绑
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
4.3 动态代理
4.3.1 动态代理
特点:字节码随用随创建,随用随加载
作用:不修改源码的基础上对方法增强
4.3.1 分类
-
基于接口的动态代理
-
基于子类的动态代理
4.3.3 基于接口的动态代理
涉及的类:Proxy
提供者:JDK官方
如何创建代理对象:使用Proxy类中的newProxyInstance方法
创建代理对象的要求:
被代理类最少实现一个接口,如果没有则不能使用
newProxyInstance方法的参数:
ClassLoader:类加载器
*它是用于加载代理对象字节码的。和被代理对象使用相同的类加载器。固定写法。
Class[]:字节码数组
*它是用于让代理对象和被代理对象有相同方法。固定写法。
InvocationHandler:用于提供增强的代码
*它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。
此接口的实现类都是谁用谁写。
IProducer proxyProducer = (IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(),
producer.getClass().getInterfaces(),
new InvocationHandler() {
/**
* 作用:执行被代理对象的任何接口方法都会经过该方法
* 方法参数的含义
* @param proxy 代理对象的引用
* @param method 当前执行的方法
* @param args当前执行方法所需的参数
* @return和被代理对象方法有相同的返回值
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//提供增强的代码
Object returnValue = null;
//1.获取方法执行的参数
Float money = (Float)args[0];
//2.判断当前方法是不是销售
if("saleProduct".equals(method.getName())) {
returnValue = method.invoke(producer, money*0.8f);
}
return returnValue;
}
});
proxyProducer.saleProduct(10000f);
}
}
4.3.4 基于子类的动态代理
动态代理的作用:
基于子类的动态代理:
涉及的类:Enhancer
提供者:第三方cglib库
如何创建代理对象:
使用Enhancer类中的create方法
创建代理对象的要求:
被代理类不能是最终类
create方法的参数:
Class:字节码
*它是用于指定被代理对象的字节码。
Callback:用于提供增强的代码
*它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。
此接口的实现类都是谁用谁写。
我们一般写的都是该接口的子接口实现类:MethodInterceptor
代码
Producer cglibProducer = (Producer)Enhancer.create(producer.getClass(), new MethodInterceptor() {
/**
* 执行北地阿里对象的任何方法都会经过该方法
* @param proxy
* @param method
* @param args
以上三个参数和基于接口的动态代理中invoke方法的参数是一样的
* @param methodProxy :当前执行方法的代理对象
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//提供增强的代码
Object returnValue = null;
//1.获取方法执行的参数
Float money = (Float)args[0];
//2.判断当前方法是不是销售
if("saleProduct".equals(method.getName())) {
returnValue = method.invoke(producer, money*0.8f);
}
return returnValue;
}
});
cglibProducer.saleProduct(12000f);
}
}
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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 配置代理的service-->
<bean id="proxyAccountService" factory-bean="beanFactory" factory-method="getAccountService">
</bean>
<!-- 配置BeanFactrory-->
<bean id = "beanFactory" class="com.itheima.factory.BeanFactrory">
<!-- 注入service-->
<property name="accountService" ref="accountService"></property>
<!-- 注入事务管理器-->
<property name="txManager" ref="txManager"></property>
</bean>
<!-- 配置Service -->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
<!-- 注入dao -->
<property name="accountDao" ref="accountDao"></property>
<!-- 注入connectionUtils
<property name="txManager" ref="txManager"></property>
-->
</bean>
<!--配置Dao对象-->
<bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
<!-- 注入QueryRunner -->
<property name="runner" ref="runner"></property>
<!-- 注入connectionUtils -->
<property name="connectionUtils" ref="connectionUtils"></property>
</bean>
<!--配置QueryRunner-->
<bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
<!--注入数据源-->
<!-- <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/account"></property>
<property name="user" value="root"></property>
<property name="password" value="root"></property>
</bean>
<!--配置Connection的工具类 ConnectionUtils-->
<bean id="connectionUtils" class = "com.itheima.utils.ConnectionUtils">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置事务管理器-->
<bean id="txManager"class="com.itheima.utils.TransactionManager">
<property name="connectionUtils" ref="connectionUtils"></property>
</bean>
</beans>
5 AOP
5.1 AOP概述
5.1.1 什么是AOP
AOP:全称是Aspect Oriented Programming即:面向切面编程。
是把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的基础上,对我们的已有方法进行增强。
作用: 在程序运行期间,不修改源码对已有方法进行增强。
优势: 减少重复代码 提高开发效率 维护方便
5.2 spring中基于XML的AOP配置步骤
1、把通知bean也交给spring来管理
2、使用aop:config标签表明开始AOP的配置
3、使用aop:aspect标签表明配置切面 id 属性:是给切面提供一个唯一标识 ref属性:是指定通知类bean的Id.
4、在aop:aspect标签的内部使用对应标签来配置通知的类型 我们现在示例是让printlog方法在切入点方法执行之前执行:所以是前置通知 aop:before 标识配置前置通知 method属性:用于指定Logger类中的哪个方法时前置通知 pointcut属性:用于指定切入点表达式,该表达式的含义指的是对业务层中哪些方法增强
切入点表达式写法:
关键字:exceution(表达式)
表达式:
修饰符 返回值 包名.包名...类名.方法名(参数列表)
标准的表达式写法:
public void com.itheima.service.impl.AccountServiceImpl.saveAccount()
访问修饰符可以省略
void com.itheima.service.impl.AccountServiceImpl.saveAccount()
返回值可以使用通配符,表示任意返回值
* com.itheima.service.impl.AccountServiceImpl.saveAccount()
包名可以同通配符,表示任意包。但是有几级包,就需要写几个 *.
* *.*.*.*.AccountServiceImpl.saveAccount()
包名可以使用.. 表示当前包及子包
* *.. AccountServiceImpl.saveAccount()
类名和方法名可以使用*来实现通配
* *.. *.* ()
参数列表:
可以直接写数据类型:
基本类型写名称 int
引用类型写包名.类名的方式 java.langString
可以使用通配符表示任意类型,但是必须有参数
可以使用.. 表示有无参数均可,有参数可以是任意类型
实际开发中切入点表达式的通常写法: * com.itheima.service.iml..(..)
全通配写法: * *...(..)
5.2.1 四种常用通知类型
**aop:before **
作用: 用于配置前置通知。指定增强的方法在切入点方法之前执行
属性: method:用于指定通知类中的增强方法名称
ponitcut-ref:用于指定切入点的表达式的引用
poinitcut:用于指定切入点表达式 执行时间点: 切入点方法执行之前执行
<aop:before method="beginTransaction" pointcut-ref="pt1"/>
aop:after-returning
作用: 用于配置后置通知
属性: method:指定通知中方法的名称。
pointct:定义切入点表达式
pointcut-ref:指定切入点表达式的引用
执行时间点: 切入点方法正常执行之后。它和异常通知只能有一个执行
<aop:after-returning method="commit" pointcut-ref="pt1"/>
**aop:after-throwing **
作用: 用于配置异常通知
属性: method:指定通知中方法的名称。
pointct:定义切入点表达式
pointcut-ref:指定切入点表达式的引用
执行时间点: 切入点方法执行产生异常后执行。它和后置通知只能执行一个
<aop:after-throwing method="rollback" pointcut-ref="pt1"/>
aop:after
作用: 用于配置最终通知
属性: method:指定通知中方法的名称。
pointct:定义切入点表达式
pointcut-ref:指定切入点表达式的引用
执行时间点: 无论切入点方法执行时是否有异常,它都会在其后面执行。
<aop:after method="release" pointcut-ref="pt1"/>
环绕通知
配置方式:
<aop:config> <aop:pointcut expression="execution(* com.itheima.service.impl..(..))" id="pt1"/> <aop:aspect id="txAdvice" ref="txManager">
<aop:around method="transactionAround" pointcut-ref="pt1"/> </aop:aspect> </aop:config>
aop:around: 作用: 用于配置环绕通知
属性: method:指定通知中方法的名称。
pointct:定义切入点表达式
pointcut-ref:指定切入点表达式的引用
说明: 它是spring框架为我们提供的一种可以在代码中手动控制增强代码什么时候执行的方式。
注意: 通常情况下,环绕通知都是独立使用的
spring框架为我们提供了一个接口:ProceedingJoinPoint,它可以作为环绕通知的方法参数。 * 在环绕通知执行时,spring框架会为我们提供该接口的实现类对象,我们直接使用就行。
spring中的环绕通知:它是spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式。
public Object transactionAround(ProceedingJoinPoint pjp) {
//定义返回值 Object rtValue = null;
try { //获取方法执行所需的参数
Object[] args = pjp.getArgs();
//前置通知:开启事务
beginTransaction();
//执行方法
rtValue = pjp.proceed(args);
//后置通知:提交事务
commit();
}catch(Throwable e) {
//异常通知:回滚事务
rollback();
e.printStackTrace();
}finally {
//最终通知:释放资源
release(); }
return rtValue; }
5.3 基于注解的AOP配置
5.3.1 准备代码和jar包配置文件设置:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" 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 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">
<!-- 配置数据库操作对象 -->
<bean id="dbAssit" class="com.itheima.dbassit.DBAssit"> <property name="dataSource" ref="dataSource"></property> <!-- 指定connection和线程绑定 --> <property name="useCurrentConnection" value="true"></property>
</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:///spring_day02"></property>
<property name="user" value="root"></property>
<property name="password" value="1234"></property> </bean> </beans>
5.3.2 把资源使用注解配置
@Service("accountService")
public class AccountServiceImpl implements IAccountService { @Autowired private IAccountDao accountDao;
@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao { @Autowired private DBAssit dbAssit ; }
5.3.3 在增强的方法上使用注解配置通知
@Before("execution(* com.itheima.service.impl.*.*(..))")
@AfterReturning("execution(* com.itheima.service.impl.*.*(..))")
@Around("execution(* com.itheima.service.impl.*.*(..))")
5.3.4 在spring配置文件中开启spring对注解AOP的支持
<aop:aspectj-autoproxy/>
5.3.5 切入点表达式注解
@Pointcut("execution(* com.itheima.service.impl.*.*(..))")
private void pt1() {}
不使用XML的配置方式:
@Configuration @ComponentScan(basePackages="com.itheima") @EnableAspectJAutoProxy public class SpringConfiguration { }