AOP
AOP(Aspect Oriented Programming):面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生泛型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
Aop在Spring中的作用
提供声明式事务;允许用户自定义切面。
横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点,如日志,安全,缓存,事务等等
- 切面(ASPECT):横切关注点是被模块化的特殊对象,即,它是一个类。
- 通知(Advice):切面必须要完成的工作。即,它是类中的一个方法。
- 目标(Target):被通知对象。
- 代理(Proxy):向目标对象应用通知之后创建的对象。
- 切入点(PointCut):切面通知执行的“地点”的定义
- 连接点(JointPoint):与切入点匹配的执行点。
AOP的五种通知类型:
- 前置通知(Before advice): 在连接点之前运行但无法阻止执行流程进入连接点的通知(除非它引发异常)。
- 后置返回通知(After returning advice):在连接点正常完成后执行的通知(例如,当方法没有抛出任何异常并正常返回时)。
- 后置异常通知(After throwing advice): 在方法抛出异常退出时执行的通知。
- 后置通知(总会执行)(After (finally) advice): 当连接点退出的时候执行的通知(无论是正常返回还是异常退出)。
- 环绕通知(Around Advice):环绕连接点的通知,例如方法调用。这是最强大的一种通知类型。环绕通知可以在方法调用前后完成自定义的行为。它可以选择是否继续执行连接点或直接返回自定义的返回值又或抛出异常将执行结束。
使用Spring实现AOP
使用Aop注入,需要导入一个依赖包。
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>
编写测试环境:
1、创建一个UserService接口,该接口实现4个方法:
public interface UserService {
public void add();
public void delete();
public void update();
public void select();
}
2、编写UserServiceImpl接口实现类:
public class UserserviceImpl implements UserService{
public void add() {
System.out.println("增加了一个用户");
}
public void delete() {
System.out.println("删除了一个用户");
}
public void update() {
System.out.println("修改了一个用户");
}
public void select() {
System.out.println("查询了一个用户");
}
}
方式一:使用Spring的API接口实现AOP
现需要在接口实现的方法前后都加上日志:
以下编写配置文件applicationContext.xml:
首先了解:execution()表达式
execution(<修饰符模式>? <返回类型模式> <方法名模式>(<参数模式>) <异常模式>?)
以下是一些常见的 execution 表达式示例:
-
通过方法签名定义切点 execution(public * *(..)) 匹配所有目标类的 public 方法
-
通过方法名定义切点 execution(* set*(..)) 匹配所有以 "set" 开头的方法
-
通过类定义切点 execution(* com.xyz.service.AccountService.*(..)) 匹配 AccountService 接口的所有方法
-
通过包定义切点 execution(* com.xyz.service..*(..)) 匹配 service 包及其子包中的所有方法
-
通过方法入参定义切点 execution(* joke(String, int)) 匹配 joke(String, int) 方法,且第一个入参是 String,第二个入参是 int
<?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">
<!--注册-->
<bean id="UserService" class="com.serenity.service.UserserviceImpl"/>
<bean id="log" class="com.serenity.service.Log"/>
<bean id="afterlog" class="com.serenity.service.AfterLog"/>
<!--配置AOP:需要导入AOP的约束-->
<!--方式一:使用原生Spring API接口-->
<aop:config>
<!--切入点:我们需要在哪执行 expression:表达式 execution()填写要执行的位置:修饰词、返回值、类名、方法名、参数 -->
<aop:pointcut id="pointcut" expression="execution(* com.serenity.service.UserserviceImpl.*(..))"/>
<!--执行环绕增强 advice-ref:引用到这个类 pointcut-ref:从切点引入-->
<aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="afterlog" pointcut-ref="pointcut"/>
</aop:config>
</beans>
编写测试文件:
方式二:使用自定义实现AOP
自定义一个切入面:为切入面写两个方法分别用于前置和后置
//自定义
public class DiyPointCut {
public void before(){
System.out.println("======方法执行前======");
}
public void after(){
System.out.println("======方法执行后======");
}
}
更改applicationContext.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">
<!--注册-->
<bean id="UserService" class="com.serenity.service.UserserviceImpl"/>
<bean id="log" class="com.serenity.service.Log"/>
<bean id="afterlog" class="com.serenity.service.AfterLog"/>
<!--配置AOP:需要导入AOP的约束-->
<!--方式二:使用自定义实现-->
<bean id="diy" class="com.serenity.div.DiyPointCut"/>
<aop:config>
<!--自定义切面 ref:要引用的类-->
<aop:aspect ref="diy">
<!--切入点-->
<aop:pointcut id="point" expression="execution(* com.serenity.service.UserserviceImpl.*(..))"/>
<!--通知:在切入点执行前执行before方法-->
<aop:before method="before" pointcut-ref="point"/>
<!--在切入点执行前执行before方法-->
<aop:after method="after" pointcut-ref="point"/>
</aop:aspect>
</aop:config>
</beans>
测试类不变,直接执行可得到:
方式三:注解实现AOP
编写AnnotationPointCut类使用注解设置为切面:
修改配置文件:
测试类不变:执行结果为:
添加一个后置通知:
测试类、配置文件不变:
添加环绕通知:
测试:
整合MyBatis
需要导入的依赖如下:
junit、mybatis、mysql、spring、aop、mybatis-spring
<!--单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>
<!--数据库连接-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.21</version>
</dependency>
<!--Mybatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<!--Spring-webmvc-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.20</version>
</dependency>
<!--Spring连接数据库时,还需要spring-jdbc-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.20</version>
</dependency>
<!--AOP注入-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>
<!--mybatis-spring整合的包-->
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.6</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build>
环境搭建
1、idea连接数据库
2、编写实体类
@Data
public class User {
private int id;
private String name;
private String pwd;
}
3、编写接口
public interface UserMapper {
public List<User> selectUser();
}
4、编写接口配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.serenity.mapper.UserMapper">
<select id="selectUser" resultType="user">
select * from mybatis.user;
</select>
</mapper>
5、编写核心配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<typeAliases>
<package name="com.serenity.pojo"/>
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC" />
<property name="username" value="root" />
<property name="password" value="123456" />
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/serenity/mapper/UserMapper.xml"></mapper>
</mappers>
</configuration>
6、编写测试类:
public class MyTest {
@Test
public void selectUserTest() throws IOException {
String resource="mybatis-config.xml";
InputStream in = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
SqlSession sqlSession = sqlSessionFactory.openSession(true);
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = mapper.selectUser();
for (User user : userList) {
System.out.println(user);
}
sqlSession.close();
}
}
整合Mybatis
了解MyBatis-Spring
MyBatis-Spring会帮助你将MyBatis代码无缝整合到Spring中,它将允许MyBatis参与到Spring的事务管理之中,创建映射器mapper和SqlSession并注入到bean中,以及将MyBatis的异常转换为Spring的DataAccessException。最终,可以做到应用代码不依赖MyBatis、Spring或MyBatis-Spring。
要和Spring一起使用MyBatis,需要在Spring应用上下文中定义至少两样东西:
- 一个SqlSessonFactory和至少一个数据映射器类。
在MyBatis-Spring中,可使用sqlSessionFactoryBean来创建SqlSessionFactory,要配置这个工厂bean,只需要把下面代码放在Spring的xml配置文件中。
<!--SqlSessonFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="datasource"/>
</bean>
SqlSessionFactory需要一个DataSource数据源,这可以是任意的DateSource,只需要和配置其他Spring数据库连接一样配置它就可以了。
SqlSessionTemplate是MyBatis-Spring的核心,作为SqlSession的一个实现,这意味着可以使用它无缝代替你代码中已经在使用的SqlSession,SqlSessionTemplate是线程安全的,可以被多个dao或映射器所共享使用。
编写Spring配置文件,使用Spring-jdbc连接数据库,配置数据源:
<?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">
<!--SqlSessonFactory-->
<!--DataSource:使用Spring的数据源替换MyBatis的配置,包括用户,密码,url等
此处使用Spring-jdbc:org.springframework.jdbc.datasource-->
<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/mybatis?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>
</beans>
对应原MyBatis配置文件的环境部分:
数据源DataSource与SqlSessionFactory关系:
sqlSessionFactory作为构造方法的参数来创建SqlSessionTemplate对象。
配置mapper接口映射:左边是原先的配置格式,右边是利用spring配置:
mapper接口在Spring中具体配置如下:
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="datasource"/>
<!--绑定MyBatis配置,将该配置与mybatis配置连接起来-->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<!--配置mapper接口-->
<property name="mapperLocations" value="classpath:com/serenity/mapper/*.xml"/>
</bean>
Mapper接口可以通过MapperFactoryBean加入到Spring中。
- MapperFactoryBean将会负责SqlSession的创建和关闭。
编写Mapper实现类:
public class UserMapperImpl implements UserMapper{
//原来我们的所有操作都使用sqlsession来执行,现在有sqlsessiontemplate
private SqlSessionTemplate sqlSession;
public void setSqlSession(SqlSessionTemplate sqlSession) {
this.sqlSession = sqlSession;
}
public List<User> selectUser() {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
return mapper.selectUser();
}
}
将mapper实现类注入到Spring
即:全部配置如下:
<?xml version="1.0" encoding="UTF8"?>
<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">
<!--SqlSessonFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="datasource"/>
<!--绑定MyBatis配置,将该配置与mybatis配置连接起来-->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<!--配置mapper接口-->
<property name="mapperLocations" value="classpath:com/serenity/mapper/*.xml"/>
</bean>
<!--DataSource:使用Spring的数据源替换MyBatis的配置,包括用户,密码,url等
此处使用Spring-jdbc:org.springframework.jdbc.datasource-->
<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/mybatis?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>
<!--SqlSessionTemplate:就是我们使用的sqlSession-->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<!--SqlSessionTemplate无set方法,可利用构造方法注入sqlSessionFactory-->
<constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>
<!---->
<bean id="userMapper" class="com.serenity.mapper.UserMapperImpl">
<property name="sqlSession" ref="sqlSession"/>
</bean>
</beans>
编写测试类:
@Test
public void selectUserTest2(){
ApplicationContext context = new ClassPathXmlApplicationContext("spring-dao.xml");
UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
for (User user : userMapper.selectUser()) {
System.out.println(user);
}
}
SqlSessionDaoSupport
SqlSessionDaoSupport是一个抽象的支持类,用来提供SqlSession,调用getSqlSession()方法你会得到一个SqlSessionTemplate,之后可以用于执行SQL方法。
SqlSessionDaoSupport需要通过属性设置一个sqlSessionFactory或SqlSessionTemplate,如果两个属性都被设置了,那么SqlSessionFactory将被忽略。
使用SqlSessionDaoSupport实现获取sqlsession:
注入:
<bean id="userMapper2" class="com.serenity.mapper.UserMapperImpl2">
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
测试:
@Test
public void selectUserTest3(){
ApplicationContext context = new ClassPathXmlApplicationContext("spring-dao.xml");
UserMapper userMapper = context.getBean("userMapper2", UserMapper.class);
for (User user : userMapper.selectUser()) {
System.out.println(user);
}
}
事务管理
利用上述整合好的项目,在接口mapper添加业务:
public interface UserMapper {
public List<User> selectUser();
//添加一个事务
public int addUser(User user);
//删除一个 用户
public int deleteUser(int id);
}
编写mapper.xml文件:
编写接口实现mapperImpl类:
更改实体类User:
实例化User类,添加一个用户和删除一个用户的操作:
测试代码:
已知在SqL语句中,删除语句的关键字出错误,那测试结果中一定会出现SQL语句错误提示,同时,添加语句无错,结果如下:
由上可知,上述的添加和删除操作都属于查看方法中,属于一个业务,不应该成功一半失败一半。
事务了解
一个使用MyBatis-Spring的其中一个主要原因是它允许MyBatis参与到Spring的事务管理中,而不是MyBatis创建一个新的专业事务管理器,MyBatis-Spring借助了Spring中的DataSourceTransactionManager来实现事务管理。
一旦配置好了Spring的事务管理器,你就可以在Spring中按你平时的方式来配置事务,并且支持@Transaction注解和AOP风格的配置。在事务处理期间,一个单独的SqlSession对象将会被创建和使用。当事务完成时,这个session会以合适的方式提交或回滚。
为事务管理器指定的DataSource必须和用来创建SqlSessionFactoryBean的是同一个数据源,否则事务管理器就无法工作了。
修改上述案例
在Spring配置文件中添加声明式事务:
<!--配置声明式事务-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<constructor-arg ref="datasource"/>
</bean>
结合AOP实现事务的注入:
<!--结合AOP实现事务的注入
配置事务通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!--给哪些方法配置事务 配置事务的传播性:propagation-->
<tx:method name="add" propagation="REQUIRED"/>
<tx:method name="delete" propagation="REQUIRED"/>
<tx:method name="update" propagation="REQUIRED"/>
<tx:method name="query" read-only="true"/>
</tx:attributes>
</tx:advice>
<!--配置事务切入-->
<aop:config>
<aop:pointcut id="txPointCut" expression="execution(* com.serenity.mapper.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>
重新进行测试由于删除语句SQL代码中有错误,输入添加无误,但配置了事务,则不会添加内容到数据表:
配置事务的传播性(propagation)
- REQUIRED:支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
- SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行。
- MANDATORY:支持当前事务,如果当前没有事务,就抛出异常。
- REQUIRES_NEW:新建事务,如果当前存在事务,把当前事务挂起。
- NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
- NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
- NESTED:支持当前事务,如果当前事务存在,则执行一个嵌套事务,如果当前没有事务,就新建一个事务。
使用事务:
- 解决数据提交不一致的问题。
- 如果不在Spring中去配置声明式事务,就需要在代码中手动配置事务。
- 事务在项目的开发中十分重要,设计到数据的一致性和完整性。