耦合:程序间的依赖关系
耦合包括:
解耦:
? 降低程序间的依赖关系
开发解耦:
解耦思路:
new
关键字Bean:可重用组件
JavaBean:用Java语言编写的可重用组件(≠实体类,>实体类)
使用配置文件配置需要解耦的类
配置内容:唯一标识=全类名
通过读取配置文件中的配置内容,反射创建对象,存入Bean容器中
bean.properties
userService=com.spring.jdbc.service.UserService
com.spring.jdbc.service.UserService
public class UserService {
public void useService(){
System.out.println("hello");
}
}
创建Bean工厂,加载bean.properties
中的配置,将需要解耦的对象加载进Bean容器,从而实现了对象的单例模式
com.spring.jdbc.factory.BeanFactory
public class BeanFactory {
/**
* 定义一个properties对象
*/
private static Properties properties;
/**
* 定义一个Map,作为存储Bean的容器
*/
private static Map<String, Object> beans;
/**
* 使用静态代码块为Properties对象赋值并初始化Bean容器
*/
static {
try {
/**
* 1.实例化配置对象
*/
properties = new Properties();
//加载ClassPath下的配置文件,获取properties的流对象
InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
//将配置文件的内容进行装载
properties.load(in);
/**
* 2.实例化Bean的容器
*/
beans = new HashMap<String, Object>();
/**
* 3.加载配置文件并将反射的对象存入容器中
*/
//获取key的枚举
Enumeration<Object> keys = properties.keys();
while (keys.hasMoreElements()) {
//取出每个key
String key = keys.nextElement().toString();
//根据key获取value
String beanPath = properties.getProperty(key);
//反射创建对象
Object value = Class.forName(beanPath).newInstance();
//把key和value存入容器中
beans.put(key, value);
}
} catch (Exception e) {
e.printStackTrace();
throw new ExceptionInInitializerError("初始化properties失败");
}
}
/**
* 根据Bean名称获取Bean对象
*
* @param beanName
* @return
*/
public static Object getBean(String beanName) {
return beans.get(beanName);
}
}
使用
public class Main {
public static void main(String[] args) {
UserService userService = (UserService) BeanFactory.getBean("userService");
userService.useService();
}
}
将创建类权力交给了工厂(框架),放弃了自己对类的创建控制
削减了程序的耦合
导入Spring框架依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.4.RELEASE</version>
</dependency>
创建Spring的Bean配置文件
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
将对象的创建交给Spring去管理
id:Bean的名称,class:反射对象的全类名
<bean id="userService" class="com.spring.jdbc.service.UserService"></bean>
使用
public class Jdbc {
/**
* 获取IOC的核心容器,并根据id获取对象
* @param args
*/
public static void main(String[] args) {
//1.获取容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.1 根据id获取Object类对象
UserService userService = (UserService)ac.getBean("userService");
//2.2 根据id和类对象直接获取对象
//UserService userService1 = ac.getBean("userService",UserService.class);
userService.useService();
}
}
用于根据参数构建bean工厂
可以加载类路径下的配置文件
加载磁盘任意路径下的配置文件(必须有访问权限)
用于读取注解创建容器
单例对象适用
在构建核心容器时,创建对象的策略是立即加载
只要一读取完配置文件,就立刻创建对象
多例对象适用
在构造核心容器是,采用延迟加载的方式加载
只有在通过容器获取对象的时候才创建对象
使用默认构造函数创建
在Spring的配置文件中使用bean标签,除了id和class没有其他标签时使用
如果该类没有默认构造函数那么该对象无法创建
<bean id="userService" class="com.spring.jdbc.service.UserService"></bean>
使用某个类中的方法返回值创建对象并存入容器
com.spring.jdbc.factory.InstanceFactory
public class InstanceFactory {
public UserService getUserService(){
return new UserService();
}
}
bean.xml
<!--将工厂存入容器-->
<bean id="instanceFactory" class="com.spring.jdbc.factory.InstanceFactory"></bean>
<!--从工厂中的方法中获取bean-->
<bean id="userService" factory-bean="instanceFactory" factory-method="getUserService"></bean>
使用某个类中的静态方法创建对象并存入容器
com.spring.jdbc.factory.StaticFactory
public class StaticFactory {
public static UserService getUserService(){
return new UserService();
}
}
bean.xml
<bean id="userService" class="com.spring.jdbc.factory.StaticFactory" factory-method="getUserService"></bean>
改变Bean标签的scope属性
可以在Bean标签中加入
init-method
、destory-method
指创建和销毁对象时运行的函数要调用
destory-method
需要调用容器close
方法,不然容器会直接销毁,无法查看效果
依赖注入就是依赖关系的维护
能注入的类型:
- 基本类型和String
- 其他Bean类型(在配置文件中或者注解标识的)
- 复杂类型/集合类型
注入方式:
- 构造函数提供
- 使用set方法提供
- 使用注解提供
使用constructor-arg标签给构造函数的参数赋值
缺点:必须在xml中配置所有的构造函数才能注入,不常用
com.spring.jdbc.service.UserService
public class UserService {
/**
* 如果是经常变化的数据不适用于注入
*/
private String name;
private Integer age;
private Date birthday;
public UserService(String name, Integer age, Date birthday) {
this.name = name;
this.age = age;
this.birthday = birthday;
}
public void useService(){
System.out.println(name+" "+age+" "+birthday);
}
}
bean.xml
<!--构造函数的注入,使用constructor-arg标签指定构造函数-->
<bean id="userService" class="com.spring.jdbc.service.UserService">
<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>
<!--注入一个自定义的日期对象-->
<!--<bean id="now" class="java.util.Date">-->
<!-- <constructor-arg name="year" value="100"></constructor-arg>-->
<!-- <constructor-arg name="month" value="1"></constructor-arg>-->
<!-- <constructor-arg name="date" value="1"></constructor-arg>-->
<!--</bean>-->
type:指定要注入的数据类型,该数据类型是构造函数中某个或者某些参数的类型
再使用value
将指定的值注入给指定的数据类型
index:指定赋值的索引位置,起始为0
name:指定赋值的参数名字
value:将基本数据类型和String类型赋给上面三种方式指定的参数的值
ref:指定其他的Bean类型(在SpringIOC容器中出现的Bean对象)给上面三种方式指定的参数赋值
使用property配置需要赋值的参数
优势:更加灵活选择赋值,更常用
com.spring.jdbc.service.UserService
public class UserService {
private String name;
private Integer age;
private Date birthday;
public void setName(String name) {
this.name = name;
}
public void setAge(Integer age) {
this.age = age;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public void useService(){
System.out.println(name+" "+age+" "+birthday);
}
}
bean.xml
<bean id="userService" class="com.spring.jdbc.service.UserService">
<property name="name" value="test"></property>
<property name="age" value="18"></property>
<property name="birthday" ref="now"></property>
</bean>
<bean id="now" class="java.util.Date">
<constructor-arg name="year" value="100"></constructor-arg>
<constructor-arg name="month" value="1"></constructor-arg>
<constructor-arg name="date" value="1"></constructor-arg>
</bean>
com.spring.jdbc.service.UserService
public class UserService {
private String[] myStrs;
private List<String> myList;
private Set<String> mySet;
private Map<String,Object> myMap;
private Properties myPro;
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, Object> myMap) {
this.myMap = myMap;
}
public void setMyPro(Properties myPro) {
this.myPro = myPro;
}
@Override
public String toString() {
return "UserService{" +
"myStrs=" + Arrays.toString(myStrs) +
", myList=" + myList +
", mySet=" + mySet +
", myMap=" + myMap +
", properties=" + myPro +
'}';
}
}
Bean.xml
<bean id="userService" class="com.spring.jdbc.service.UserService">
<property name="myStrs">
<array>
<value>A</value>
<value>B</value>
<value>C</value>
</array>
</property>
<property name="myList">
<list>
<value>A</value>
<value>B</value>
<value>C</value>
</list>
</property>
<property name="mySet">
<set>
<value>A</value>
<value>B</value>
<value>C</value>
</set>
</property>
<property name="myMap">
<map>
<!--方式一-->
<entry key="test1" value="aaa"></entry>
<!--方式二-->
<entry key="test2">
<value>bbb</value>
</entry>
</map>
</property>
<property name="myPro">
<props>
<prop key="test1">aaa</prop>
<prop key="test2">bbb</prop>
</props>
</property>
</bean>
用于给List结构集合注入值的标签:list
array
set
用于给Map结构集合注入值的标签:map
props
结构相同,标签可以互换
注解需要的context命名空间和约束
如果想要去除XML,需要配置配置类
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:context="http://www.springframework.org/schema/context"
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">
<!--告知Spring在创建容器时需要扫描的包,配置所需要的标签不是在beans的约束中,而是在context名称空间和约束中-->
<context:component-scan base-package="com.spring.jdbc"></context:component-scan>
</beans>
和
标签实现的作用一样 以下注解的作用和属性相同,只是Spring为了让三层架构更加清晰
和
标签作用一样
在使用注解注入时,Set方法不是必须
以上三个注解都只能注入Bean,基本类型和String的注入如下,但是集合类型的注入只能通过xml
@Value
注入配置classpath:
表示类路径在
标签中使用 scope
属性作用一样标注在类上
在
标签中使用 init-method
destory-method
标签作用一样标注在方法上
AnnotationConfigApplicationContext
(如下使用所示)的参数时可以不写,否则配置无效,Spring不会将Bean扫描进去作用:指定Spring在创建容器时要扫描的包
取代XML中的扫描配置
位置:配置类上
属性:
作用:用于导入其他配置类到主配置类中
当使用该注解后,有Imprt注解的类就是父配置类,而导入的都是子配置,子配置可以不需要@Configuration
位置:配置类上
属性:
public class Main {
public static void main(String[] args) {
//此时需要用到注解Context实现类去扫描配置类
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfigruation.class);
UserService userService = (UserService) applicationContext.getBean("userService");
userService.test();
}
}
原生Junit无法在执行自己Runner类(main方法)时进行Spring容器的创建
所以必须在测试时进行手动创建容器,不然无法完成Bean的注入使用,比较麻烦
导入依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.4.RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<!-- 表示开发的时候引入,发布的时候不会加载此包 -->
<scope>test</scope>
</dependency>
使用Junit提供的替换原有Runner(运行器:main方法)的注解,替换成Spring提供的
@RunWith(SpringJUnit4ClassRunner.class)
public class Test(){}
告知Spring运行器SpringIOC容器是基于注解还是XML
@ContextConfiguration
属性:
classpath:
指定在类路径下.class
)@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfigruation.class)
public class test {}
特点:字节码随用随创建,随用随加载
作用:不修改原方法源码给方法增强
分类:
- 基于接口的动态代理
- 涉及的类:Proxy
- 提供者:JDK官方
- 创建代理对象的要求:被代理类至少实现一个接口,如果没有不能使用
- 基于子类的动态代理
- 涉及的类:Enhancer
- 提供者:第三方cglib库
- 创建代理对象的要求:被代理的类不能是最终类
使用Proxy类中的
newProxyInstance
方法
newProxyInstance
生厂商接口
com.spring.jdbc.proxy.IProducer
public interface IProducer {
/**
* 销售
*
* @param money
*/
public void saleProduct(float money);
/**
* 售后
*
* @param money
*/
public void afterService(float money);
}
生产商实现类
com.spring.jdbc.proxy.Producer
public class Producer implements IProducer{
/**
* 销售
*
* @param money
*/
@Override
public void saleProduct(float money) {
System.out.println("销售并拿到钱:" + money);
}
/**
* 售后
*
* @param money
*/
@Override
public void afterService(float money) {
System.out.println("提供售后服务比拿到钱:" + money);
}
}
代理经销商
com.spring.jdbc.proxy.Client
public class Client {
public static void main(String[] args) {
//匿名内部类访问外部成员需要final类型
final Producer producer = new Producer();
//创建一个经销商,代理厂家的销售方法
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 {
/**
* 提供增强代码
*/
//判断当前方法是不是销售
if ("saleProduct".equals(method.getName())) {
//获取原方法参数
Float money = (Float) args[0];
//修改传进原方法的参数新方法返回值
//参数:1.被代理对象 2.执行方法的参数
return method.invoke(producer, money * 0.8f);
}
return method.invoke(producer, args);
}
});
proxyProducer.saleProduct(1000);
}
}
使用Ehancer类中的
create
方法
create
MethodInterceptor
导入依赖
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.1_3</version>
</dependency>
生产厂商
com.spring.jdbc.cglib.Producer
public class Producer{
/**
* 销售
*
* @param money
*/
public void saleProduct(float money) {
System.out.println("销售并拿到钱:" + money);
}
/**
* 售后
*
* @param money
*/
public void afterService(float money) {
System.out.println("提供售后服务比拿到钱:" + money);
}
}
经销商
com.spring.jdbc.cglib.Client
public class Client {
public static void main(String[] args) {
//匿名内部类访问外部成员需要final类型
final Producer producer = new Producer();
//创建一个经销商,代理厂家的销售方法
Producer cglibProducer = (Producer) Enhancer.create(producer.getClass(), new MethodInterceptor() {
/**
* 作用:执行被代理对象的任何方法都会经过该方法
* @param o 代理对象的引用
* @param method 当前执行的方法
* @param objects 当前执行方法需要的参数
* 以上三个参数和基于接口的动态代理相同
* @param methodProxy 当前执行方法的代理对象
* @return 和被代理对象方法有相同的返回值
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
/**
* 提供增强代码
*/
//判断当前方法是不是销售
if ("saleProduct".equals(method.getName())) {
//获取原方法参数
Float money = (Float) objects[0];
//修改传进原方法的参数新方法返回值
//参数:1.被代理对象 2.执行方法的参数
return method.invoke(producer, money * 0.8f);
}
return method.invoke(producer, objects);
}
});
cglibProducer.saleProduct(1000);
}
}
JoinPoint(连接点):
指被拦截到的点,在Spring中这些点指方法
通俗来说就是原来需要增强的所有方法
PointCut(切入点)
指我们对哪些JointPoint进行拦截定义
即被增强的方法
切入点一定是连接点,连接点不一定是切入点
advice(通知/增强)
拦截到JoinPoint之后需要做的事
Introduction(引介)
特殊的通知,在运行期动态添加一些方法或者Field
Target(目标对象)
代理的目标对象
Weaving(织入)
把增强应用到目标对象来创建新对象的过程
即加入增强的过程
Proxy(代理)
被AOP增强后返回产生的代理类
Aspect(切面)
切入点和通知之间的关系
依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.4.RELEASE</version>
</dependency>
<!--切入点表达式-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
账户业务层接口
com.spring.service.IAccountService
public interface IAccountService {
/**
* 模拟保存账户
*/
void saveAccount();
/**
* 模拟更新账户
* @param i
*/
void updateAccount(int i);
/**
* 删除账户
* @return
*/
int deleteAccount();
}
账户实现类
com.spring.service.impl.AccountServiceImpl
public class AccountServiceImpl implements IAccountService {
@Override
public void saveAccount() {
System.out.println("执行了保存");
}
@Override
public void updateAccount(int i) {
System.out.println("执行了更新"+i);
}
@Override
public int deleteAccount() {
System.out.println("执行了删除");
return 0;
}
}
日志工具类,提供公共方法,作为切面通知
com.spring.utils.Logger
public class Logger {
/**
* 用于打印日志,计划让其在切入点方法执行之前执行
*/
public void printLog(){
System.out.println("Logger中的printLog方法开始记录日志");
}
}
AOP和IOC的配置文件
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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
</beans>
AOP的XML配置步骤
将需要增强的Bean交给Spring
<bean id="accountService" class="com.spring.service.impl.AccountServiceImpl"></bean>
把通知的Bean也交给Spring
<bean id="logger" class="com.spring.utils.Logger"></bean>
使用<aop:config>
标签表明开始AOP的配置
在config标签内,使用<aop:aspect>
标签表明配置切面
在aspect标签内配置通知的类型,建立通知方法和切入点之间的关系
如<aop:before>
前置通知
属性:
method:指定前置通知的方法
pointcut:用于指定切入点表达式,指定业务层需要增强的方法
写法:
关键字:execution(表达式)
表达式:访问修饰符 返回值 包名.包名...类名.方法名(参数列表)
访问修饰符可以省略
返回值:可以使用通配符(*
),表示任意返回值
* com.spring.service.impl.AccountServiceImpl.saveAccount()
包名:可以使用通配符,表示任意包,但是有几级包就要有几个*
;
也可以使用..
表示当前包及其子包
* *..AccountServiceImpl.saveAccount()
类名和方法名:都可以使用*
来实现通配
* *..*.*()
参数列表:
int
java.lang.String
*
表示任意类型,但是必须有参数..
表示有无参数均可全通配写法:* *..*.*(..)
实际开发中的写法:
切到业务层实现类下的所有方法
如execution(* com.spring.service.impl.*.*(..))
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--配置IOC-->
<bean id="accountService" class="com.spring.service.impl.AccountServiceImpl"></bean>
<!--Spring中基于XML的AOP配置步骤-->
<!--1.配置通知类-->
<bean id="logger" class="com.spring.utils.Logger"></bean>
<!--2.配置AOP-->
<aop:config>
<!--3.配置切面-->
<aop:aspect id="logAdvice" ref="logger">
<!--4.配置通知的类型且建立通知方法和切入点方法的关联-->
<aop:before method="printLog" pointcut="execution(public void com.spring.service.impl.AccountServiceImpl.saveAccount())"></aop:before>
</aop:aspect>
</aop:config>
</beans>
使用
public class test {
public static void main(String[] args) {
//获取容器
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//获取对象
IAccountService as = (IAccountService)ac.getBean("accountService");
//执行方法
as.saveAccount();
}
}
<aop:before>
<aop:after-returning>
<aop:after-throwing>
<aop:after>
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--配置IOC-->
<bean id="accountService" class="com.spring.service.impl.AccountServiceImpl"></bean>
<!--Spring中基于XML的AOP配置步骤-->
<!--1.配置通知类-->
<bean id="logger" class="com.spring.utils.Logger"></bean>
<!--2.配置AOP-->
<aop:config>
<!--3.配置切面-->
<aop:aspect id="logAdvice" ref="logger">
<!--4.1 配置前置通知-->
<aop:before method="beforePrintLog" pointcut-ref="pt1"></aop:before>
<!--4.2 配置后置通知-->
<aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt1"></aop:after-returning>
<!--4.3 配置异常通知-->
<aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing>
<!--4.4 配置最终通知-->
<aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after>
<!--配置切入点表达式
id属性指定表达式唯一属性,expression属性指定表达式内容
1.写在aop:aspect内部只能当前标签使用
2.写在aop:aspect外部(必须写在引用切面之前),让所有切面可用
-->
<aop:pointcut id="pt1" expression="execution(* com.spring.service.impl.*.*(..))"/>
</aop:aspect>
</aop:config>
</beans>
实质:他是Spring提供在代码中控制增强方法何时执行的方法
相当于动态代理的接口实现方法
com.spring.utils.Logger
public class Logger {
/**
* 环绕通知
* Spring为我们提供一个接口ProceedingJoinPoint,接口中有一个proceed(),此方法相当于切入点调用方法。
* 该接口可以作为环绕通知的方法参数,在程序执行时,Spring为我们提供接口的实现类使用
*/
public Object aroundPrintLog(ProceedingJoinPoint pjp){
Object returnValue = null;
try {
//得到切入点方法执行的参数
Object[] args = pjp.getArgs();
//前置通知
System.out.println("环绕通知中的前置通知");
//调用切入点方法
returnValue = pjp.proceed(args);
//后置通知
System.out.println("环绕通知中的后置通知");
} catch (Throwable throwable) {
//异常通知
System.out.println("环绕通知中的异常通知");
throwable.printStackTrace();
}finally {
//最终通知
System.out.println("环绕通知中的最终通知");
return returnValue;
}
}
}
beam.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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--配置IOC-->
<bean id="accountService" class="com.spring.service.impl.AccountServiceImpl"></bean>
<!--Spring中基于XML的AOP配置步骤-->
<!--1.配置通知类-->
<bean id="logger" class="com.spring.utils.Logger"></bean>
<!--2.配置AOP-->
<aop:config>
<!--通用切入表达式-->
<aop:pointcut id="pt1" expression="execution(* com.spring.service.impl.*.*(..))"/>
<!--3.配置切面-->
<aop:aspect id="logAdvice" ref="logger">
<aop:around method="aroundPrintLog" pointcut-ref="pt1"></aop:around>
</aop:aspect>
</aop:config>
</beans>
Spring注解的后置通知和异常通知会出现顺序问题
环绕通知不会有问题
加入了xmlns:context="http://www.springframework.org/schema/aop"
命名空间
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"
xmlns:context="http://www.springframework.org/schema/context"
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/aop/spring-aop.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!--配置Spring创建容器时要扫描的包-->
<context:component-scan base-package="com.spring"></context:component-scan>
<!--配置Spring开启注解AOP支持-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
@Component("logger")
@Aspect
public class Logger {
/**
* 切入表达式
*/
@Pointcut("execution(* com.spring.service.impl.*.*(..))")
private void pt1(){}
/**
* 前置通知
*/
@Before("pt1()")
public void beforePrintLog() {
System.out.println("前置通知:Logger中的beforePrintLog方法开始记录日志");
}
/**
* 后置通知
*/
@AfterReturning("pt1()")
public void afterReturningPrintLog() {
System.out.println("后置通知:Logger中的afterPrintLog方法开始记录日志");
}
/**
* 异常通知
*/
@AfterThrowing("pt1()")
public void afterThrowingPrintLog() {
System.out.println("异常通知:Logger中的afterThrowingPrintLog方法开始记录日志");
}
/**
* 最终通知
*/
@After("pt1()")
public void afterPrintLog() {
System.out.println("最终通知:Logger中的finallyPrintLog方法开始记录日志");
}
/**
* 环绕通知
* Spring为我们提供一个接口ProceedingJoinPoint,接口中有一个proceed(),此方法相当于切入点调用方法。
* 该接口可以作为环绕通知的方法参数,在程序执行时,Spring为我们提供接口的实现类使用
*/
@Around("pt1()")
public Object aroundPrintLog(ProceedingJoinPoint pjp){
Object returnValue = null;
try {
//得到切入点方法执行的参数
Object[] args = pjp.getArgs();
//前置通知
System.out.println("环绕通知中的前置通知");
//调用切入点方法
returnValue = pjp.proceed(args);
//后置通知
System.out.println("环绕通知中的后置通知");
} catch (Throwable throwable) {
//异常通知
System.out.println("环绕通知中的异常通知");
throwable.printStackTrace();
}finally {
//最终通知
System.out.println("环绕通知中的最终通知");
return returnValue;
}
}
}
在配置类上加上@EnableAspectJAutoProxy
,再用注解context加载配置类即可
通知注解上的value和pointcut作用相同,都是用来指定确如表达式,当有pointcut时value会被覆盖
在普通通知中获取切入点方法
JoinPoint
是ProceedingJoinPoint
父类,没有执行切点的proceed
方法
@AfterReturning(value = "execution(* com.spring.service.impl.*.*(..))")
public void afterReturningPrintLog(JoinPoint jp) {
//获取切入点参数
Object[] args = jp.getArgs();
}
在普通通知里面获取切入点异常
@AfterThrowing(value = "execution(* com.spring.service.impl.*.*(..))",throwing = "exception")
public void afterThrowingPrintLog(Exception exception) {
System.out.println("异常为:"+exception);
}
在普通通知里面获取切入点返回值
@AfterReturning(value = "execution(* com.spring.service.impl.*.*(..))", returning = "result")
public void afterReturningPrintLog(Object result) {
System.out.println("切入点返回值为:" + result);
}
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.2.4.RELEASE</version>
</dependency>
com.spring.domain.Account
@Data
public class Account implements Serializable {
private Integer id;
private String name;
private Float money;
}
com.spring.dao.impl.AccountDaoImpl
public class AccountDaoImpl extends JdbcDaoSupport implements IAccountDao{
@Override
public Account findAccountById(Integer id) {
List<Account> accounts = getJdbcTemplate().query("select *from account where id=?", new BeanPropertyRowMapper<Account>(Account.class), id);
return accounts.isEmpty() ? null : accounts.get(0);
}
@Override
public Account findAccountByName(String accountName) {
List<Account> accounts = getJdbcTemplate().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) {
getJdbcTemplate().update("update account set name=?,money=? where id=?", account.getName(), account.getMoney(), account.getId());
}
}
com.spring.service.impl.AccountServiceImpl
public class AccountServiceImpl implements IAccountService {
private IAccountDao accountDao;
public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public Account findAccountById(Integer id) {
return accountDao.findAccountById(id);
}
@Override
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;
//5.更新转入账户
accountDao.updateAccount(target);
}
}
XML配置
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"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
</beans>
配置事务管理器,需要指定数据源
<!--数据源配置-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>
<!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
配置事务通知器
此时需要导入事务的约束
使用<tx:advice>
标签配置事务通知
属性:
配置事务的属性,<tx:attributes>
中的<tx:method>
标签
属性:
name:需要添加事务的方法名,可以使用通配符*
如:find*
表示所有以find开头的方法,或者*
表示所有方法。优先范围小的
isolation:指定事务的隔离级别,默认时DEFAULT,表示使用数据库的默认隔离级别
propagation:指定事务的传播行为。默认值是REQUIRED,表示一定会有事务,增删改选;
查询可选SUPPORTS
read-only:用于指定事务是否只读,默认为false(读写);只有查询才设置为true
timeout:用于指定事务的超时时间,默认为-1,表示永不超时,如果指定数值以秒为单位
rollback-for:用于指定一个异常,当产生该异常时事务回滚,其他异常事务不回滚
默认值表示任何异常都回滚
no-rollback-for:用于指定一个异常,当产生该异常时事务不会滚,其他异常事务回滚
默认值表示任何异常都回滚
<!--配置事务的通知,此时需要导入事务的约束,tx和aop的都需要-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!--配置事务的属性-->
<tx:attributes>
<tx:method name="*" propagation="REQUIRED" read-only="false"/>
<tx:method name="find*" propagation="SUPPORTS" read-only="true"/>
</tx:attributes>
</tx:advice>
配置AOP中的通用切入点表达式,指定所有业务层方法
建立事务通知和切入表达式的对应关系
<!--配置AOP-->
<aop:config>
<!--配置切入点表达式-->
<aop:pointcut id="pt1" expression="execution(* com.spring.service.impl.*.*(..))"/>
<!--建立事务通知和切入表达式的对应关系-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"/>
</aop:config>
完整配置:
<?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"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--业务层-->
<bean id="accountService" class="com.spring.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"/>
</bean>
<!--Dao层-->
<bean id="accountDao" class="com.spring.dao.impl.AccountDaoImpl">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--数据源配置-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8"/>
<property name="username" value="root"/>
<property name="password" value="w327191248"/>
</bean>
<!--Spring中基于XML的声明式事务控制-->
<!--1.配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--2.配置事务的通知,此时需要导入事务的约束,tx和aop的都需要-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!--配置事务的属性-->
<tx:attributes>
<tx:method name="*" propagation="REQUIRED" read-only="false"/>
<tx:method name="find*" propagation="SUPPORTS" read-only="true"/>
</tx:attributes>
</tx:advice>
<!--3.配置AOP-->
<aop:config>
<!--配置切入点表达式-->
<aop:pointcut id="pt1" expression="execution(* com.spring.service.impl.*.*(..))"/>
<!--4.建立事务通知和切入表达式的对应关系-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"/>
</aop:config>
</beans>
com.spring.domain.Account
同上
com.spring.dao.impl.AccountDaoImpl
@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao{
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public Account findAccountById(Integer id) {
List<Account> accounts = jdbcTemplate.query("select *from account where id=?", new BeanPropertyRowMapper<Account>(Account.class), id);
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());
}
}
com.spring.service.impl.AccountServiceImpl
使用@Transactional
可以配置在类上或者方法上开启事务,具体参数
@Service("accountService")
@Transactional(propagation = Propagation.SUPPORTS,readOnly = true)
public class AccountServiceImpl implements IAccountService {
@Autowired
private IAccountDao accountDao;
@Override
public Account findAccountById(Integer id) {
return accountDao.findAccountById(id);
}
@Override
@Transactional(propagation = Propagation.REQUIRED,readOnly = false)
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;
//5.更新转入账户
accountDao.updateAccount(target);
}
}
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"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
</beans>
配置:
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!--配置Spring创建容器时要扫描的包-->
<context:component-scan base-package="com.spring"></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.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8"/>
<property name="username" value="root"/>
<property name="password" value="w327191248"/>
</bean>
<!--1.配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--2.开启Spring对注解事务的支持-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
</beans>
使用代码代替bean.xml
主配置类
com.spring.config.SpringConfiguration
/**
* 1.声明配置类
* 2.配置Spring创建容器时要扫描的包
* 3.声明子配置类
* 4.声明配置文件
* 5.开启注解事务支持
*/
@Configuration
@ComponentScan("com.spring")
@Import({JdbcConfig.class,TransactionConfig.class})
@PropertySource(value = "jdbcConfig.properties")
@EnableTransactionManagement
public class SpringConfiguration {
}
事务相关配置类
com.spring.config.TransactionConfig
public class TransactionConfig {
/**
* 用于创建事务管理器对象
* @param dataSource
* @return
*/
@Bean(name="transactionManager")
public PlatformTransactionManager createPlatformTransactionManager(DataSource dataSource){
return new DataSourceTransactionManager(dataSource);
}
}
连接数据库相关配置类
com.spring.config.JdbcConfig
public class JdbcConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
/**
* 创建JdbcTemplate对象
* @param dataSource
* @return
*/
@Bean(name = "jdbcTemplate")
public JdbcTemplate createJdbcTemplate(DataSource dataSource){
return new JdbcTemplate(dataSource);
}
/**
* 配置数据源
* @return
*/
@Bean(name = "dataSource")
public DataSource createDataSource(){
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(driver);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
}
resources.jdbcConfig.properties
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8
jdbc.username=root
jdbc.password=123456
原文:https://www.cnblogs.com/JMWan233/p/12446220.html