第一部分 Spring概述
1 Spring简介
Spring是分层的full-stack(全栈)轻量级开源框架,以IoC和AOP为内核,提供了展示层SpringMVC和业务层事务管理等众多的企业级应用技术。
Spring官方地址:https://spring.io/
2. Spring 发展历程
- 1997-2006年 EJB1.0-EJB3.0发布
- 2002年 Expert One-to-One J2EE Design and Development阐述了J2EE使⽤EJB开发设计的优点及解决⽅案
- 2004年 Expert One-to-One J2EE Development without EJB阐述了J2EE开发不使⽤EJB的解决⽅式(Spring雏形)
3. Spring的优势
-
解耦,简化开发 通过Spring提供的IoC容器,可以将对象间的依赖关系交由Spring进行控制,避免硬编码(new)所造成的过度程序耦合。
-
AOP编程的支持
通过Spring的AOP功能,方便进行面向切面的编程,许多不容易用传统OOP实现的功能可以通过AOP轻松应付 -
声明式事务的支持
@Transactional 可以将我们从单调烦闷的事务管理代码中解脱,通过声明式方式灵活的进行事务的管理,提高开发效率和质量 -
方便程序测试
-
方便集成各种优秀框架 Spring可以降低各种框架的使用难度,提供了对各种优秀框架的直接支持
-
降低JavaEE API的使用难度 Spring对javaEE API进行了薄薄的封装,使这些API的使用难度大为降低
-
源码是经典的Java学习范例 Spring的源代码设计精妙,结构清晰,体现了大事对Java设计模式灵活运用以及对Java技术的高深造诣。
4. Spring的核心结构
第二部分 核心思想
1. IoC
1.1 什么事IoC?
IoC Inversion of Control(控制反转/反转控制),他是一个技术思想,不是一个技术实现
- 传统开发方式:类A依赖于类B,会在类A中new一个B的对象
- IoC思想下开发方式:我们不用自己去new对象,而是由IoC容器(Spring框架)去帮助我们实例化对象并且管理它,我们需要使用哪个对象,去向IoC容器要即可 为什么叫控制反转
- 控制:指的是对象创建(实例化、管理)的权利
- 反转:控制权交给外部环境(Spring框架、IoC容器)
1.2 IoC解决的问题:对象之间的耦合问题
1.3 IoC和DI的区别
- IoC容器是站在
对象的角度,对象实例化机器管理的权利交给(反转)给了容器 - DI是站在
容器的角度,会把对象依赖的其他对象注入
2. AOP
2.1 什么是AOP
AOP:面向切面编程/面向方面编程,AOP是OOP的延续。
横切逻辑代码存在什么问题:
- 横切代码重复
- 横切逻辑代码和业务代码混杂在一起,代码臃肿,维护不方便 AOP提出了横向抽取鸡贼,将横切逻辑代码和业务逻辑代码分离
2.2 AOP在解决什么问题
在不改变原有业务逻辑情况下,增强横切逻辑代码,根本上解耦合,避免横切逻辑代码重复
2.3 为什么叫做面向切面编程
切:指的是横切逻辑,原有业务逻辑代码不能动,只能操作横切逻辑代码,所以面向横切逻辑
面:横切逻辑代码往往要影响的是很多个方法,每个方法都如同一个点,多个点构成面。
第三部分 手写IoC和AOP
基础回顾
- A 简单工厂模式
样例代码
public interface INoodles {
/**
* 描述面条
*/
void desc();
}
public class LaMian implements INoodles {
@Override
public void desc() {
System.out.println("一碗来自兰州的拉面");
}
}
public class PaoMian implements INoodles {
@Override
public void desc() {
System.out.println("一碗来自超市的泡面");
}
}
public class DanDanMian implements INoodles {
@Override
public void desc() {
System.out.println("一碗来自四川的担担面");
}
}
public class SimpleNoodlesFactory {
public static final int LM = 1;
public static final int PM = 2;
public static final int DDM = 1;
public static INoodles createNoodles(int noodleType) {
switch (noodleType) {
case 1:
return new LaMian();
case 2:
return new PaoMian();
case 3:
return new DanDanMian();
default:
return null;
}
}
}
- B 单例模式
- 恶汉模式
public class HungrySingleton {
// 构造方法私有化(单例模式必须的一步)
private HungrySingleton() {}
// 将自身实例化对象设置为一个属性,并用static、final修饰
private static final HungrySingleton instance = new HungrySingleton();
// 静态方法返回该实例
public static HungrySingleton getInstance() {
return instance;
}
}
- 懒汉模式
public class LazySingleton {
// 构造方法私有化
private LazySingleton() {}
// 将自身实例化对象设置为一个属性,并用static修饰
private static LazySingleton instance;
// 静态方法返回该实例,加synchronized关键字实现同步(不然两个线程都进来会有安全问题)
public static synchronized LazySingleton getInstance() {
if(instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
3.1 银行转账案例(关键代码)
- servlet
@WebServlet(name="transferServlet",urlPatterns = "/transferServlet")
public class TransferServlet extends HttpServlet {
// 1. 实例化service层对象
private TransferService transferService = new TransferServiceImpl();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
doPost(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse
resp) throws ServletException, IOException {
// 设置请求体的字符编码
req.setCharacterEncoding("UTF-8");
String fromCardNo = req.getParameter("fromCardNo");
String toCardNo = req.getParameter("toCardNo");
String moneyStr = req.getParameter("money");
int money = Integer.parseInt(moneyStr);
Result result = new Result();
try {
// 2. 调⽤service层⽅法
transferService.transfer(fromCardNo,toCardNo,money);
result.setStatus("200");
} catch (Exception e) {
e.printStackTrace();
result.setStatus("201");
result.setMessage(e.toString());
}
// 响应
resp.setContentType("application/json;charset=utf-8");
resp.getWriter().print(JsonUtils.object2Json(result));
}
- TransferService接口及实现类
public class TransferServiceImpl implements TransferService {
private AccountDao accountDao = new JdbcAccountDaoImpl();
@Override
public void transfer(String fromCardNo, String toCardNo, int money)throws Exception {
Account from = accountDao.queryAccountByCardNo(fromCardNo);
Account to = accountDao.queryAccountByCardNo(toCardNo);
from.setMoney(from.getMoney()-money);
to.setMoney(to.getMoney()+money);
accountDao.updateAccountByCardNo(from);
accountDao.updateAccountByCardNo(to);
}
- AccountDao层接口及基于jdbc实现类
public class JdbcAccountDaoImpl implements AccountDao {
@Override
public Account queryAccountByCardNo(String cardNo) throws Exception {
//从连接池获取连接
Connection con = DruidUtils.getInstance().getConnection();
String sql = "select * from account where cardNo=?";
PreparedStatement preparedStatement = con.prepareStatement(sql);
preparedStatement.setString(1,cardNo);
ResultSet resultSet = preparedStatement.executeQuery();
Account account = new Account();
while(resultSet.next()) {
account.setCardNo(resultSet.getString("cardNo"));
account.setName(resultSet.getString("name"));
account.setMoney(resultSet.getInt("money"));
}
resultSet.close();
preparedStatement.close();
con.close();
return account;
}
@Override
public int updateAccountByCardNo(Account account) throws Exception {
//从连接池获取连接
Connection con = DruidUtils.getInstance().getConnection();
String sql = "update account set money=? where cardNo=?";
PreparedStatement preparedStatement = con.prepareStatement(sql);
preparedStatement.setInt(1,account.getMoney());
preparedStatement.setString(2,account.getCardNo());
int i = preparedStatement.executeUpdate();
preparedStatement.close();
con.close();
return i;
}
}
3.2 银行转账案例问题分析
3.3 问题解决思路
- 思考
- new关键字将service层的实现类TransferServiceImpl和Dao层的具体实现类JdbcAccountDapImpl耦合在一起,除了new以外还可以使用
反射 Class.forName("全限定类名");可以把全限定类名配置早xml中 - 使用工厂来通过反射技术生产对象,工厂模式是解耦合非常好的一种方式
- service层没有添加事务控制,手动控制JDBC的Connection事务,但注意将Connection和当前线程绑定(即保证一个线程只有一个Connection,这样操作才针对的是同一个Connection,进而控制的是同一个事务)
3.4 代码优化
第四部分 Spring IoC应用
4.1 Spring IoC基础
4.1.1 BeanFactory与ApplicationContext区别
BeanFactory是Spring框架中的IoC容器的顶层接口,他只是用来定义一些基本功能,定义一些基础规范,而ApplicationContext是它的一个子接口,所以ApplicationContext是具备BeanFactory提供的全部功能。通常,我们称BeanFactory为SpringIoc的基础容器,ApplicationContext是容器的高级接口,比BeanFactory要拥有更多的功能。
启动IoC容器的方式
- Java环境下启动IoC容器
- ClassPathXmlApplicationContext: 从类的根路径下架子配置文件
- FileSystemXmlApplicationContext: 从磁盘路径上加载配置文件
- AnnotationConfigApplicationContext: 纯注解模式下启动Spring容器
public class IoCTest {
@Test
public void testIoC(){
// 通过读取classpath下的xml文件来启动容器(xml模式SE应用下推荐)
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
// 通过哟磁盘路径加载配置文件
ApplicationContext applicationContext1 = new FileSystemXmlApplicationContext("文件绝对路径");
AccountDao accountDao = (AccountDao) applicationContext.getBean("accountDao");
//System.out.println(accountDao);
Object connectionUtils = applicationContext.getBean("connectionUtils");
System.out.println(connectionUtils);
}
}
- web环境下启动IoC容器
- 从xml启动容器
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<!--配置SpringIoC容器的配置文件-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<!-- 使用监听器启动启动IoC容器-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
</web-app>
4.1.2 纯xml模式
- xml文件头 可配置多个Spring组件标签配置文件,使用前缀区分
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/beans/spring-aop.xsd
">
- 实例化Bean的三种方式
- 使用无参构造函数
在默认情况下,会通过反射调用无参构造函数来创建对象。如果类中没有无参构造函数,将创建失败 applicationContext.xml
<bean id="connectionUtils" class="cn.percent.utils.ConnectionUtils"/>
- 使用静态方法创建
在开发中,我们使用的对象有些时候并不是直接通过构造函数创建出来的,它可能在创建过程中会做很多额外的操作。此时会提供一个创建对象的方法,恰好这个方法是static修饰的方法,就是这种情况。
例如: 我们在做JDBC操作是,会用到java.sql.Connection接口的实现类,如果是mysql数据库,那么用的是JDBC4Connection,但是我们不会去写
JDBC4Connection connection = new JDBC4Connection(),因为我们要注册驱动,还要提供URL和凭证信息,用DriverManager.getConnection方法来获取连接。
applicationContext.xml
<bean id="connectionUtils" class="cn.percent.factory.CreateBeanFactory" factory-method="getInstanceStatic"/>
- 使用实例化方法创建 此种方法和静态方法创建类似,区别是用于获取对象的方法不再是static修饰了,而是类中的一个普通方法。这种方法比静态方法创建的使用几率要高一些。
<bean id="createBeanFactory" class="cn.percent.factory.CreateBeanFactory"></bean>
<bean id="connectionUtils" factory-bean="createBeanFactory" factory-method="getInstance"></bean>
- Bean的生命周期
- 作用范围的改变 Spring框架管理Bean对象的创建时,Bean对象默认都是单例的,但他是支持配置的方式改变作用范围。
<!--配置service对象-->
<bean id="transferService" class="com.lagou.service.impl.TransferServiceImpl" scope="singleton"/>
单例模式: singleton
对象出生:当创建容器时,对象就被创建了。
对象活着:只要容器在,对象就被销毁了。
对象死亡:当销毁容器时,对象就被销毁了。
单例模式的bean对象生命周期与容器相同
多例模式: prototype
对象出生:当使用对象时,创建新的对象实例。
对象活着:只要对象在使用中,就一直活着。
对象死亡:当对象长时间不用时,被Java的垃圾回收器回收了。
多例模式的bean对象,spring框架只负责创建,不负责销毁
- Bean标签属性
| 属性 | 解释 |
|---|---|
| id | ⽤于给bean提供⼀个唯⼀标识。在⼀个标签内部,标识必须唯⼀。 |
| class | ⽤于指定创建Bean对象的全限定类名。 |
| name | ⽤于给bean提供⼀个或多个名称。多个名称⽤空格分隔。 |
| factory-bean | ⽤于指定创建当前bean对象的⼯⼚bean的唯⼀标识。当指定了此属性之后,class属性失效。 |
| factory-method | ⽤于指定创建当前bean对象的⼯⼚⽅法,如配合factory-bean属性使⽤,则class属性失效。如配合class属性使⽤,则⽅法必须是static的。 |
| scope | ⽤于指定bean对象的作⽤范围。通常情况下就是singleton。当要⽤到多例模式时,可以配置为prototype。 |
| init-method | ⽤于指定bean对象的初始化⽅法,此⽅法会在bean对象装配后调⽤。必须是⼀个⽆参⽅法。 |
| destory-method | ⽤于指定bean对象的销毁⽅法,此⽅法会在bean对象销毁前执⾏。它只能为scope是singleton时起作⽤ |
- DI依赖注入的xml配置
- 依赖注入分类
(1). 构造函数注入: 利用带参数构造函数实现对类成员的数据赋值。
<bean id="accountDao" class="cn.percent.dao.impl.JdbcAccountDaoImpl">
<!--name: 按照参数名注入,index按照参数索引位置注入-->
<constructor-arg name="connectionUtils" ref="connectionUtils"></constructor-arg>
<constructor-arg name="name" value="zhangsan"></constructor-arg>
<constructor-arg name="sex" value="1"></constructor-arg>
<constructor-arg name="money" value="100.6"></constructor-arg>
</bean>
(2). set方法注入
- 基本类型和String
<property name="name" value="zhangsan"></property> - 其他Bean类型
<property name="ConnectionUtils" ref="connectionUtils"></property> - 复杂类型(集合类型) 注入的数据类型是Array,List,Set,Map,Properties中的一种类型
<property name="myMap">
<map>
<entry key="key1" value="value1"></entry>
<entry key="key2" value="value2"></entry>
<entry key="key3" value="value3"></entry>
</map>
</property>
4.1.3 xml与注解想结合的模式
| xml形式 | 对应的注解形式 |
|---|---|
| 标签 | @Component("accountDao"),注解加在类上bean的id属性内容直接配置在注解后⾯如果不配置,默认定义个这个bean的id为类的类名⾸字⺟⼩写;另外,针对分层代码开发提供了@Componenet的三种别名@Controller、@Service、@Repository分别⽤于控制层类、服务层类、dao层类的bean定义,这四个注解的⽤法完全⼀样,只是为了更清晰的区分⽽已 |
| 标签的scope属性 | @Scope("prototype"),默认单例,注解加在类上 |
| 标签的initmethod属性 | @PostConstruct,注解加在⽅法上,该⽅法就是初始化后调⽤的⽅法 |
| 标签的destorymethod属性 | @PreDestory,注解加在⽅法上,该⽅法就是销毁前调⽤的⽅法 |
- DI依赖注入的注解方式
@Autowired(推荐使⽤),@Autowired为Spring提供的注解,需要导⼊包org.springframework.beans.factory.annotation.Autowired。@Autowired采取的策略为按照类型注⼊。当一个类型有多个beab值得时候,会造成无法选择具体注入哪一个的情况。这时需配合@Qualifier使用
public class TransferServiceImpl implements TransferService {
//Autowired 按照类型注入,如果按照类型无法唯一锁定对象,可以结合Qualifier指定
@Autowired
@Qualifier("accountDao")
private AccountDao accountDao;
}
4.1.4 纯注解方式
移除xml中的配置,从java配置类启动,对应注解:
@Configuration 注解,表名当前类是⼀个配置类
@ComponentScan 注解,替代 context:component-scan
@PropertySource,引⼊外部属性配置⽂件
@Import 引⼊其他配置类
@Value 对变量赋值,可以直接赋值,也可以使⽤ ${} 读取资源配置⽂件中的信息
@Bean 将⽅法返回对象加⼊ SpringIOC 容器
@Configuration
@ComponentScan({"cn.percent"})
@PropertySource({"classpath:jdbc.properties"})
public class SpringConfig {
@Value("${jdbc.driver}")
private String driverClassName;
@Value(("${jdbc.url}"))
private String url;
@Value("${jdbc.username}")
private String username;
@Value(("${jdbc.password}"))
private String password;
@Bean("dataSource")
public DataSource createDataSource(){
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setDriverClassName(driverClassName);
druidDataSource.setUrl(url);
druidDataSource.setUsername(username);
druidDataSource.setPassword(password);
return druidDataSource;
}
}
4.2 Spring IoC高级特性
4.2.1 lazy-init延迟加载
ApplicationContext容器的默认行为是启动服务器时将所有singleton bean提前进行实例化,提前实例化以为着作为初始化过程的一部分,ApplicationContext实例会创建配置所有的singleton bean。
<bean id="testBean" class="cn.percent.LazyBean" />
该bean默认的设置为:
<bean id="testBean" calss="cn.percent.LazyBean" lazy-init="false" />
设置 lazy-init 为 true 的 bean 将不会在 ApplicationContext 启动时提前被实例化,⽽是第⼀次向容器 通过 getBean 索取 bean 时实例化的。如果⼀个设置了⽴即加载的 bean1,引⽤了⼀个延迟加载的 bean2 ,那么 bean1 在容器启动时被实例化,⽽ bean2 由于被 bean1 引⽤,所以也被实例化,这种情况也符合延时加载的 bean 在第⼀次调⽤时才被实例化的规则。也可以在容器层次中通过在 元素上使⽤ "default-lazy-init" 属性来控制延时初始化。如下⾯配置:
<beans default-lazy-init="true">
<!-- no beans will be eagerly pre-instantiated... -->
</beans>
如果⼀个 bean 的 scope 属性为 scope="pototype" 时,即使设置了 lazy-init="false",容器启动时也不 会实例化bean,⽽是调⽤ getBean ⽅法实例化的。
4.2.2 FactoryBean和BeanFactory
BeanFactory接⼝是容器的顶级接⼝,定义了容器的⼀些基础⾏为,负责⽣产和管理Bean的⼀个⼯⼚,
具体使⽤它下⾯的⼦接⼝类型,⽐如ApplicationContext。
Spring中Bean有两种,⼀种是普通Bean,⼀种是⼯⼚Bean(FactoryBean),FactoryBean可以⽣成
某⼀个类型的Bean实例(返回给我们),也就是说我们可以借助于它⾃定义Bean的创建过程。
FactoryBean接口
// 可以让我们⾃定义Bean的创建过程(完成复杂Bean的定义)
public interface FactoryBean<T> {
@Nullable
// 返回FactoryBean创建的Bean实例,如果isSingleton返回true,则该实例会放到Spring容器的单例对象缓存池中Map
T getObject() throws Exception;
@Nullable
// 返回FactoryBean创建的Bean类型
Class<?> getObjectType();
// 返回作⽤域是否单例
default boolean isSingleton() {
return true;
}
}
Company Class
public class Company {
private String name;
private String address;
private int scale;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public int getScale() {
return scale;
}
public void setScale(int scale) {
this.scale = scale;
}
@Override
public String toString() {
return "Company{" +
"name='" + name + '\'' +
", address='" + address + '\'' +
", scale=" + scale +
'}';
}
}
CompanyFactoryBean class
public class CompanyFactoryBean implements FactoryBean<Company> {
private String companyInfo; // 公司名称,地址,规模
public void setCompanyInfo(String companyInfo) {
this.companyInfo = companyInfo;
}
@Override
public Company getObject() throws Exception {
// 模拟创建复杂对象Company
Company company = new Company();
String[] strings = companyInfo.split(",");
company.setName(strings[0]);
company.setAddress(strings[1]);
company.setScale(Integer.parseInt(strings[2]));
return company;
}
@Override
public Class<?> getObjectType() {
return Company.class;
}
@Override
public boolean isSingleton() {
return true;
}
}
xml配置
<bean id="companyBean" class="com.lagou.edu.factory.CompanyFactoryBean">
<property name="companyInfo" value="percent,市府恒隆,500"/>
</bean>
测试代码
Object companyBean = applicationContext.getBean("companyBean");
System.out.println("bean:" + companyBean);
// 结果如下
bean:Company{name='拉勾', address='中关村', scale=500}
如果想要获取FactoryBean,需要在id之前加”&“ &companyBean
Object companyBean = applicationContext.getBean("&companyBean");
System.out.println("bean:" + companyBean);
// 结果如下
bean:com.lagou.edu.factory.CompanyFactoryBean@53f6fd09
第五部分 SpringIoC源码分析
Spring源码太复杂,好多函数还没搞清除逻辑,大神就是大神,日后在补充注释。
git地址: https://gitee.com/FearlessYMF/spring-source-code-analysis.git
第六部分 Spring AOP应用
1. XML方式
- xml配置
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
">
<!--开启注解扫描,base-pacjage指定扫描的包路径 -->
<context:component-scan base-package="cn.percent"></context:component-scan>
<!--引入第三方bean-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://172.16.3.219:3306/ymf"></property>
<property name="username" value="crawl"></property>
<property name="password" value="crawl123"></property>
</bean>
<!--aop相关的xml配置-->
<!-- 使用config标签表明开始配置,在内部配置切面aspect-->
<bean id="logUtils" class="cn.percent.utils.LogUtils"></bean>
<aop:config>
<aop:aspect id="logAspect" ref="logUtils">
<!--切入点锁定我们感兴趣的方法 使用aspectj表达式,参数全限定类名-->
<aop:pointcut id="pt1" expression="execution(public void cn.percent.service.impl.TransferServiceImpl.transfer(String ,String ,int))"/>
<!--方位信息-->
<!--aop:before 前置通知/-->
<!-- <aop:before method="beforeMethod" pointcut-ref="pt1"></aop:before>-->
<!--aop:after 最终通知,无论如何都执行-->
<!--aop:after-returnning. 正常执行通知-->
<!--aop:after-throwing, 异常通知-->
<!--环绕通知-->
<aop:around method="arroundmethod" pointcut-ref="pt1"></aop:around>
</aop:aspect>
</aop:config>
</beans>
- 执行类
package cn.percent.utils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
public class LogUtils {
public void beforeMethod(JoinPoint joinPoint){
Object[] args = joinPoint.getArgs();
System.out.println("业务逻辑开始执行之前执行........");
}
public void afterMehtod(){
System.out.println("业务逻辑结束是执行,无论异常与否都执行........");
}
public void exceptionMethod(){
System.out.println("异常时执行........");
}
public void successMethod(){
System.out.println("业务正常时执行........");
}
// 环绕通知
public Object arroundmethod(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("环绕通知中的beforeMethod");
Object result = null;
try{
result = proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());
} catch (Exception e) {
System.out.println("环绕通知中的exceptionMethod...");
} finally {
System.out.println("环绕通知中的afterMethod...");
}
return result;
}
}
- 测试函数
import cn.percent.service.TransferService;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.sql.SQLException;
public class IoCTest {
@Test
public void testXmlAop() throws SQLException {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
TransferService transferService = applicationContext.getBean(TransferService.class);
transferService.transfer("6029621011001","6029621011000",2000);
}
}
2. 注解方式
日后补充
3. 源码分析
日后补充