SpringAOP的使用
1.在配置文件中引入对aop的约束,并在POM文件中加入织入包的依赖
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
//上面两个是基础IOC的约束,必备
xmlns:context="http://www.springframework.org/schema/context"
//上面一个是开启注解管理Bean对象的约束
xmlns:aop="http://www.springframework.org/schema/aop"
//aop的注解约束
xmlns:tx="http://www.springframework.org/schema/tx"
//事务的约束
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
//上面两个是基础IOC的约束,必备
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
//上面一个是开启注解管理Bean对象的约束
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
//aop的注解约束
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
//事务的约束
</beans>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>
<!-- 在POM文件中加入织入包的依赖https://mvnrepository.com/artifact/org.aspectj/aspectjrt -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.6</version>
</dependency>
定义一个UserService接口和其实现类UserServiceImpl
public interface UserService {
void add();
void update();
void delete();
void query();
}
public class UserServiceImpl implements UserService{
public void add() {
System.out.println("添加了一个用户");
}
public void update() {
System.out.println("更新了一个用户");
}
public void delete() {
System.out.println("删除了一个用户");
}
public void query() {
System.out.println("查询了用户");
}
}
自定义一个切面
public class TestAspect {
public void BeforeMyAdvice() {
System.out.println("这是一个前置通知");
}
public void AfterMyAdvice() {
System.out.println("这是一个后置通知");
}
public void AfterReturnMyAdvice() {
System.out.println("这是一个执行完成后的通知");
}
public Object AroundMyAdvice(ProceedingJoinPoint jp) {
Object result = null;
try {
System.out.println("环绕通知开始 日志记录");
long start = System.currentTimeMillis();
//有返回参数 则需返回值
result = jp.proceed();
long end = System.currentTimeMillis();
System.out.println("总共执行时长" + (end - start) + " 毫秒");
System.out.println("环绕通知结束 日志记录");
} catch (Throwable t) {
System.out.println("出现错误");
}
return result;
}
}
2.配置AOP
<?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"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<bean id="UserService" class="com.lihan.Service.UserServiceImpl"></bean> <!--测试用的UserService-->
<bean id="IdentityAspect" class="com.lihan.Service.TestAspect"></bean> <!--自定义的切面-->
<aop:config>
<aop:aspect ref="IdentityAspect"> <!--使用Execute表达式-->
<aop:pointcut expression="execution(* com.lihan.Service.*.*(..))" id="mypoint"/> <!--选择切点-->
<aop:before pointcut-ref="mypoint" method="BeforeMyAdvice"/>
<aop:after pointcut-ref="mypoint" method="AfterMyAdvice"/>
</aop:aspect>
</aop:config>
</beans>
3.使用注解方式进行切入
<aop:aspectj-autoproxy /><!--开启织入注解-->
编写切面类
@Aspect //切面注解
public class AnnoTestAspect {
@Before("execution(* com.lihan.Service.*.*(..))") //用Execution表达式指定一个Pointcut(切入点)
public void MyBefore() {
System.out.println("这是一个前置通知----@anno");
}
@After("execution(* com.lihan.Service.*.*(..))")
public void MyAfter() {
System.out.println("这是一个后置通知----@anno");
}
@Around("execution(* com.lihan.Service.*.*(..))")
public Object MyAround(ProceedingJoinPoint joinpoint) {
Object result = null;
try {
System.out.println("环绕通知开始 日志记录");
long start = System.currentTimeMillis();
//有返回参数 则需返回值
result = joinpoint.proceed();
long end = System.currentTimeMillis();
System.out.println("总共执行时长" + (end - start) + " 毫秒");
System.out.println("环绕通知结束 日志记录");
} catch (Throwable t) {
System.out.println("出现错误");
}
return result;
}
}
4.Execution表达式
execution(* * *)
//表达式组成:修饰符(public,private..) 返回类型 包名.类名.方法名(类型,不填就是没有)
//在此有几个必填 返回类型(构造器可无) 名称(可以全填,也可以只填包名,类名,甚至方法名) 参数模式(没有就填())
execution(<修饰符模式>?<返回类型模式><方法名模式>(<参数模式>)<异常模式>?)
execution(* com.baobaotao.*(..)) //匹配com.baobaotao包下所有类的所有方法;
execution(* com.baobaotao..*(..)) //匹配com.baobaotao包、子孙包下所有类的所有方法,如com.baobaotao.dao,com.baobaotao.servier以及com.baobaotao.dao.user包下的所有类的所有方法都匹配。".."出现在类名中时,后面必须跟"*",表示包、子孙包下的所有类
execution(* com..*.*Dao.find*(..)) //匹配包名前缀为com的任何包下类名后缀为Dao的方法,方法名必须以find为前缀。如com.baobaotao.UserDao#findByUserId()、com.baobaotao.dao.ForumDao#findById()的方法都匹配切点。
execution(* joke(String,int)))
//匹配匹配joke(String,int)方法,且joke()方法的第一个入参是String,第二个入参是int。它匹配NaughtyWaiter#joke(String,int)方法。如果方法中的入参类型是Java.lang包下的类,可以直接使用类名,否则必须使用全限定类名,如joke(java.util.List,int);
execution(* joke(String,*)))
//匹配目标类中的joke()方法,该方法第一个入参为String,第二个入参可以是任意类型,如joke(Strings1,String s2)和joke(String s1,double d2)都匹配,但joke(String s1,doubled2,String s3)则不匹配
args(com.baobaotao.Waiter)
//表示运行时入参是Waiter类型的方法,它和execution(**(com.baobaotao.Waiter))区别在于后者是针对类方法的签名而言的,而前者则针对运行时的入参类型而言。如args(com.baobaotao.Waiter)既匹配于addWaiter(Waiterwaiter),也匹配于addNaiveWaiter(NaiveWaiter naiveWaiter),而execution(**(com.baobaotao.Waiter))只匹配addWaiter(Waiterwaiter)方法;实际上,args(com.baobaotao.Waiter)等价于execution(**(com.baobaotao.Waiter+)),当然也等价于args(com.baobaotao.Waiter+)。
@args()
//该函数接受一个注解类的类名,当方法的运行时入参对象标注发指定的注解时,方法匹配切点。
5.声明式事务和使用(整合Mybatis)
因为事务的引用必须配置数据源,所以这里采取mybatis的方法进行操作
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency> <!--引入Mybatis和其与spring的整合的依赖-->
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.6</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.microsoft.sqlserver/mssql-jdbc -->
<dependency>
<groupId>com.microsoft.sqlserver</groupId> <!--引入数据库对应jdbc连接驱动-->
<artifactId>mssql-jdbc</artifactId>
<version>8.2.2.jre8</version>
</dependency>
<dependency> <!--引入spring对JDBC的支持-->
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.9</version>
</dependency>
为配置文件添加事务的约束
xmlns:tx="http://www.springframework.org/schema/tx" //加入事务的约束
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
添加dao层的UserDao,并创建它对应的Mapper.xml,再来一个实现类UserDaoImpl
public interface UserDao {
List<User> queryAllUser();
void insert(User user);
void update(User user);
void delete(Integer ID);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.lihan.dao.UserDao"> <!--绑定的mapper.xml-->
<select id="queryAllUser" resultType="com.lihan.pojo.User">
select * from t_User;
</select>
<insert id="insert" parameterType="com.lihan.pojo.User">
insert into t_User values(#{user.Name},#{user.Address},#{user.Phone});
</insert>
<update id="update"> <!--故意写错sql语句,测试事务时用-->
update tt_User set Name = #{user.Name},Address = #{user.Address},#{user.Phone} where ID = #{user.ID}
</update>
<delete id="delete" parameterType="Integer">
delete from t_User where ID = #{ID}
</delete>
</mapper>
多加的实现类UserDaoImpl
public class UserDaoImpl implements UserDao {//不要用UserDao接口去配置Bean(其实也可以),用它的实现类UserDaoImpl
private SqlSession sqlSession; //spring-mybatis官方推荐用SqlSession直接进行数据库操作
public SqlSession getSqlSession() { //提供sqlSession的Getter和Setter方法以便在XML中配置它的Bean
return sqlSession;
}
public void setSqlSession(SqlSession sqlSession) {
this.sqlSession = sqlSession;
}
public List<User> queryAllUser() {
UserDao userdao = sqlSession.getMapper(UserDao.class); //我个人习惯用得到Mapper来执行方法
userdao.delete(7); //这个删除操作为了触发事务的异常(违反设置的只读属性)
return userdao.queryAllUser();
}
public void insert(User user) {
UserDao userdao = sqlSession.getMapper(UserDao.class);
userdao.insert(user);
userdao.update(user); //调用写错sql语句的update方法,查看事务执行情况
}
public void update(User user) {
UserDao userdao = sqlSession.getMapper(UserDao.class);
userdao.update(user);
}
public void delete(Integer ID) {
UserDao userdao = sqlSession.getMapper(UserDao.class);
userdao.delete(ID);
}
}
添加spring-mybatis.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"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<aop:config proxy-target-class="true"></aop:config>
<!--默认动态代理是jdk动态代理,而jdk动态代理不支持类注入也就是依赖注入的对象不能是类,只能是接口
解决方法:proxy-target-class
proxy-target-class属性值决定是基于接口的还是基于类的代理被创建。首先说明下proxy-target-class="true"和proxy-target-class="false"的区别,为true则是基于类的代理将起作用(需要cglib库),为false或者省略这个属性,则标准的JDK 基于接口的代理将起作用。
-->
<bean id="propertyConfigurer"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="classpath:jdbc.properties" /> <!--通过jdbc.properties设置连接信息-->
</bean>
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
<property name="driverClassName" value="${driver}" /> <!--配置数据源,使用DBCP2连接池-->
<property name="url" value="${url}" /> <!--读取连接信息--> <!--须要额外导入依赖-->
<property name="username" value="${username}" />
<property name="password" value="${password}" />
<property name="initialSize" value="${initialSize}"></property>
<property name="maxIdle" value="${maxIdle}"></property>
<property name="minIdle" value="${minIdle}"></property>
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"><!--通过引用数据源-->
<property name="dataSource" ref="dataSource" /> <!--创建sqlSessionFactory,并扫mapper包-->
<property name="mapperLocations" value="classpath:com/lihan/Mapper/*.xml"></property>
<!--以下可以配置原来mabatis-configer.xml的属性,如别名什么的,IDEA会有更多提示-->
<!--<properties name="typealias" value="com.lihan.pojo" />-->
</bean>
<tx:jta-transaction-manager /><!--Spring 应该被设置为使用 JtaTransactionManager 或由容器指定的一个子类作为事务管理器。最简单的方式是使用 Spring 的事务命名空间-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" /> <!--配置spring的事务管理器-->
</bean>
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate"> <!--配置sqlsession的bean-->
<constructor-arg index="0" ref="sqlSessionFactory" /> <!--构造函数注入Factory的引用-->
</bean>
<bean id="userDao" class="com.lihan.dao.UserDaoImpl"> <!--创建实现类UserDaoImpl的Bean-->
<property name="sqlSession" ref="sqlSession" /> <!--注入sqlsession的引用-->
</bean>
再数据库中创建对应的数据表,再插入若干条数据
编写测试类
public static void main(String[] args) {
ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = (UserService) app.getBean("UserService");
userService.add();
UserDao dao = app.getBean("userDao", UserDaoImpl.class);//拿到xml中配置的bean
List<User> users = dao.queryAllUser(); //执行查询拿到查询到的集合
for(User u:users) {
System.out.println(u.getName()+u.getAddress()+u.getPhone());
}
}
PS这里使用了DBCP2的连接池,需要额外添加DBCP2的依赖
<tx:jta-transaction-manager /> <!--开启事务-->
在SqlSessionFactory的bean中添加内部bean的TransactionManage
<property name="transactionFactory">
<bean class="org.apache.ibatis.transaction.managed.ManagedTransactionFactory" />
</property>
添加事务管理器的配置到spring-mybatis的配置文件中
<!-- 结合AOP实现事务的织入 -->
<!-- 配置事务的通知 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!-- 给哪些方法设置事务 -->
<tx:attributes>
<tx:method name="insert" propagation="REQUIRED"/>
<tx:method name="update" propagation="REQUIRED"/>
<tx:method name="delete" propagation="REQUIRED"/>
<tx:method name="queryAllUser" read-only="true"/>
</tx:attributes>
</tx:advice>
<aop:config> <!-- 定义织入点 -->
<aop:pointcut expression="exection(* com.lihan.dao.*.*(..))" id="TXpointCut"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="TXpointCut"/> <!--aop配置事务通知及其切入点-->
</aop:config>
定义一个方法进行测试
public static void main(String[] args) {
ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = (UserService) app.getBean("UserService");
userService.add();
UserDao dao = app.getBean("userDao", UserDaoImpl.class);
List<User> users = dao.queryAllUser(); //调用查询方法,等待事务异常抛出
for(User u:users) {
System.out.println(u.getName()+u.getAddress()+u.getPhone());
}
}
再写一个测试类
public static void main(String[] args) {
ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = (UserService) app.getBean("UserService");
userService.add();
UserDao dao = app.getBean("userDao", UserDaoImpl.class);
// List<User> users = dao.queryAllUser();
// for(User u:users) {
// System.out.println(u.getName()+u.getAddress()+u.getPhone());
// }
User newone = new User();
newone.setName("默罕默德");
newone.setAddress("伊斯坦布尔");
newone.setPhone("1453");
dao.insert(newone);
}
触发了事务异常,查看以下数据库是否插入了新数据
并没有插入测试方法里的新对象,说明事务的配置生效了
6.编程式事务管理和使用
先注释掉之前的xml的声明式配置
然后在持久层中添加事务管理器
public class UserDaoImpl implements UserDao {
@Autowired
private PlatformTransactionManager pManager;//看过它的源码,是一个接口,实现类是不同的数据源的事务管理器,
//比如HibernateTransactionManager,之前配的
//DataSourceTransactionManager是它的抽象实现类 //AbstractPlatformTransactionManager的子类,所以可以可以直接注入
private SqlSession sqlSession;
public SqlSession getSqlSession() { //提供Getter和Setter方法以便注入sqlSession
return sqlSession;
}
public void setSqlSession(SqlSession sqlSession) {
this.sqlSession = sqlSession;
}
public List<User> queryAllUser() {
UserDao userdao = sqlSession.getMapper(UserDao.class);
List<User> list = new ArrayList<User>();
TransactionTemplate template = new TransactionTemplate(pManager);
template.setReadOnly(true);
template.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
DefaultTransactionDefinition dd = new DefaultTransactionDefinition();
dd.setReadOnly(true);
status = pManager.getTransaction(dd);
try {
List<User> temp = userdao.queryAllUser();
User user = new User();user.setName("穆拉德");user.setAddress("伊斯特本");user.setPhone("1453"); //人为制造异常
userdao.insert(user);
userdao.update(new User());
} catch (Exception e) {
System.out.println("出现了事务的异常");
pManager.rollback(status); //回滚
throw e;
}
pManager.commot(status); //提交
}
});
return userdao.queryAllUser();
}
public void insert(User user) {
UserDao userdao = sqlSession.getMapper(UserDao.class);
userdao.insert(user);
}
public void update(User user) {
UserDao userdao = sqlSession.getMapper(UserDao.class);
userdao.update(user);
}
public void delete(Integer ID) {
UserDao userdao = sqlSession.getMapper(UserDao.class);
userdao.delete(ID);
}
}
编写测试类
public static void main(String[] args) {
ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = (UserService) app.getBean("UserService");
userService.add();
UserDao dao = app.getBean("userDao", UserDaoImpl.class);
List<User> users = dao.queryAllUser(); //调用
for(User u:users) {
System.out.println(u.getName()+u.getAddress()+u.getPhone());
}
}
再看看数据库里有没有插入方法中的新的User对象,
并没有插入新的对象