Spring实战以及AOP介绍
转账案例:
需求:
使用spring框架整合DBUtils技术,实现用户转账功能
基础功能:
步骤分析:
创建java项目,导入坐标:
<dependencies>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.47version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.1.15version>
dependency>
<dependency>
<groupId>commons-dbutilsgroupId>
<artifactId>commons-dbutilsartifactId>
<version>1.6version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.1.5.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-testartifactId>
<version>5.1.5.RELEASEversion>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
dependency>
dependencies>
编写Account实体类:
package com.lagou.domain;
public class Account {
private Integer id;
private String name;
private Double money;
@Override
public String toString() {
return "Account{" +
"id=" + id +
", name='" + name + '\'' +
", money=" + money +
'}';
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Double getMoney() {
return money;
}
public void setMoney(Double money) {
this.money = money;
}
}
编写AccountDao接口和实现类 :
package com.lagou.dao;
public interface AccountDao {
public void out(String outUser,double money);
public void in(String inUser,double money);
}
package com.lagou.dao.impl;
import com.lagou.dao.AccountDao;
import org.apache.commons.dbutils.QueryRunner;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import java.sql.SQLException;
@Repository("accountDao")
public class AccountDaoImpl implements AccountDao {
@Autowired
private QueryRunner queryRunner;
@Override
public void out(String outUser, double money) {
String sql = "update account set money = money - ? where name = ?";
try {
queryRunner.update(sql, money, outUser);
System.out.println(queryRunner);
} catch (SQLException e) {
e.printStackTrace();
}
}
@Override
public void in(String inUser, double money) {
String sql = "update account set money = money + ? where name = ?";
try {
queryRunner.update(sql, money, inUser);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
编写AccountService接口和实现类:
package com.lagou.service;
public interface AccountService {
public void transfer(String out, String in, Double money);
}
package com.lagou.service.impl;
import com.lagou.dao.AccountDao;
import com.lagou.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service("accountService")
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
@Override
public void transfer(String outUser, String inUser, Double money) {
accountDao.out(outUser, money);
accountDao.in(inUser, money);
}
}
编写spring核心配置文件:
<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
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.lagou">context:component-scan>
<context:property-placeholder location="classpath:jdbc.properties">context:property-placeholder>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}">property>
<property name="url" value="${jdbc.url}">property>
<property name="username" value="${jdbc.username}">property>
<property name="password" value="${jdbc.password}">property>
bean>
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner">
<constructor-arg name="ds" ref="dataSource">constructor-arg>
bean>
beans>
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql:///spring_db?characterEncoding=utf8
jdbc.username=root
jdbc.password=123456
编写测试代码:
package com.lagou.test;
import com.lagou.service.AccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AccountServiceTest {
@Autowired
private AccountService accountService;
@Test
public void textTransfer() {
accountService.transfer("tom", "jerry", 100d);
}
}
问题分析:
上面的代码事务在dao层,转出转入操作都是一个独立的事务
因为dao层的方法,基本用来编写单个操作的,如转出操作和转入操作
使得service层也只能依次调用,而dao层之所以不合并方法
是为了需要合并中的单个方法时,可以使用(如只需要转入或者转出操作,但这样的业务通常都是一起,虽然也可以多建立方法)
但是方法越少越好(因为实现一次,就要重写一次,为了好维护)
所以实际开发,当有些业务逻辑需要合起来调用时,应该把业务逻辑放在service层,即控制在一个事务中,使得不能分开执行
代码演示:
@Override
public void transfer(String outUser, String inUser, Double money) {
accountDao.out(outUser, money);
accountDao.in(inUser, money);
}
从上面的问题出发,看如下解决方式
传统事务:
步骤分析:
编写线程绑定工具类:
package com.lagou.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
@Component
public class ConnectionUtils {
@Autowired
private DataSource dataSource;
private ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>();
public Connection getThreadConnection(){
Connection conn = threadLocal.get();
if(conn == null){
try {
conn= dataSource.getConnection();
threadLocal.set(conn);
} catch (SQLException e) {
e.printStackTrace();
}
}
return conn;
}
public void removeThreadConnection(){
threadLocal.remove();
}
}
连接池和ThreadLocal的区别:
ThreadLocal使用图解:
最后得到的都是同一个对象,也就是同一个连接,与引用的不同没有关系(不同引用可以指向同一个对象)
因为执行方法的,从来不是引用,而是对象,只是用引用来代替而已
编写事务管理器:
package com.lagou.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.sql.Connection;
@Component
public class TransactionManager {
@Autowired
private ConnectionUtils connectionUtils;
public void beginTransaction(){
Connection threadConnection = connectionUtils.getThreadConnection();
try {
threadConnection.setAutoCommit(false);
} catch (Exception e) {
e.printStackTrace();
}
}
public void commit(){
Connection threadConnection = connectionUtils.getThreadConnection();
try {
threadConnection.commit();
} catch (Exception e) {
e.printStackTrace();
}
}
public void rollback(){
Connection threadConnection = connectionUtils.getThreadConnection();
try {
threadConnection.rollback();
} catch (Exception e) {
e.printStackTrace();
}
}
public void release(){
Connection threadConnection = connectionUtils.getThreadConnection();
try {
threadConnection.setAutoCommit(true);
connectionUtils.getThreadConnection().close();
connectionUtils.removeThreadConnection();
} catch (Exception e) {
e.printStackTrace();
}
}
}
修改service层代码:
package com.lagou.service.impl;
import com.lagou.dao.AccountDao;
import com.lagou.service.AccountService;
import com.lagou.utils.TransactionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service("accountService")
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
@Autowired
private TransactionManager transactionManager;
@Override
public void transfer(String outUser, String inUser, Double money) {
transactionManager.beginTransaction();
try {
accountDao.out(outUser, money);
accountDao.in(inUser, money);
transactionManager.commit();
} catch (Exception e) {
e.printStackTrace();
transactionManager.rollback();
}finally{
transactionManager.release();
}
}
}
修改dao层代码 :
package com.lagou.dao.impl;
import com.lagou.dao.AccountDao;
import com.lagou.utils.ConnectionUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import java.sql.SQLException;
@Repository("accountDao")
public class AccountDaoImpl implements AccountDao {
@Autowired
private QueryRunner queryRunner;
@Autowired
private ConnectionUtils connectionUtils;
@Override
public void out(String outUser, double money) {
String sql = "update account set money = money - ? where name = ?";
try {
queryRunner.update(connectionUtils.getThreadConnection(), sql, money, outUser);
System.out.println(queryRunner);
} catch (SQLException e) {
e.printStackTrace();
}
}
@Override
public void in(String inUser, double money) {
String sql = "update account set money = money + ? where name = ?";
try {
queryRunner.update(connectionUtils.getThreadConnection(), sql, money, inUser);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
问题分析 :
上面代码,通过对业务层改造,已经可以实现事务控制了,但是由于我们添加了事务控制
也产生了一个新的问题: 业务层方法变得臃肿了,里面充斥着很多重复代码
并且业务层方法和事务控制方法耦合了,违背了面向对象的开发思想
Proxy优化转账案例:
我们可以将业务代码和事务代码进行拆分,通过动态代理的方式,对业务方法进行事务的增强
这样就不会对业务层产生影响,解决了耦合性的问题啦!
常用的动态代理技术:
JDK 代理:
基于接口的动态代理技术·:利用拦截器(必须实现invocationHandler)加上反射机制生成一个代理接口的匿名类
在调用具体方法前调用InvokeHandler来处理,从而实现方法增强
CGLIB代理:
基于父类的动态代理技术:动态生成一个要代理的子类,子类重写要代理的类的所有不是final的方法
不重写也没关系,只是使用的是父类方法的操作然后增强
在子类中采用方法拦截技术拦截所有的父类方法的调用,顺势织入横切逻辑,对方法进行增强
相当于其他类帮你调用方法(也有自己的一些调用,这些调用,就可以说是增强)
JDK动态代理方式:
Jdk工厂类:
package com.lagou.proxy;
import com.lagou.service.AccountService;
import com.lagou.utils.TransactionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
@Component
public class JDKProxyFactory {
@Autowired
private AccountService accountService;
@Autowired
private TransactionManager transactionManager;
public AccountService createAccountServiceJdkProxy(){
AccountService o = (AccountService)
Proxy.newProxyInstance(accountService.getClass().getClassLoader(),
accountService.getClass().getInterfaces(), new
InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
transactionManager.beginTransaction();
try {
method.invoke(accountService, args);
transactionManager.commit();
} catch (Exception e) {
e.printStackTrace();
transactionManager.rollback();
}finally{
transactionManager.release();
}
return null;
}
});
return o;
}
}
package com.lagou.service.impl;
import com.lagou.dao.AccountDao;
import com.lagou.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service("accountService")
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
@Override
public void transfer(String outUser, String inUser, Double money) {
accountDao.out(outUser, money);
accountDao.in(inUser, money);
}
}
测试代码:
@Test
public void testTransferProxyJDK(){
System.out.println("被代理类的transfer执行了");
AccountService accountServiceJdkProxy = jdkProxyFactory.createAccountServiceJdkProxy();
accountServiceJdkProxy.transfer("tom", "jerry", 100d);
}
CGLIB动态代理方式:
Cglib工厂类:
package com.lagou.proxy;
import com.lagou.service.AccountService;
import com.lagou.utils.TransactionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
@Component
public class CglibProxyFactory {
@Autowired
private AccountService accountService;
@Autowired
private TransactionManager transactionManager;
public AccountService createAccountServiceCglibProxy(){
AccountService accountServiceproxy = (AccountService)
Enhancer.create(accountService.getClass(), new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy
methodProxy) throws Throwable {
try {
transactionManager.beginTransaction();
method.invoke(accountService, objects);
transactionManager.commit();
} catch (Exception e) {
e.printStackTrace();
transactionManager.rollback();
} finally {
transactionManager.release();
}
return null;
}
});
return accountServiceproxy;
}
}
测试代码:
@Test
public void testTransferProxyCglib(){
AccountService accountServiceCglibProxy = cglibProxyFactory.createAccountServiceCglibProxy();
accountServiceCglibProxy.transfer("tom", "jerry", 100d);
}
与JDK动态代理不同,Cglib动态代理的返回值可以是接口
也可以是对应类(需要符号父类指向子类或者本身的规则),即指向规则
而JDK动态代理只可以返回接口,因为JDK动态代理继承了一个类,所以不可再次继承
而Cglib动态代理则没有继承,所以可以继承类
通过分析进行文件添加,但他们都是通过拦截来进行操作的
实际上在文件添加时,由于实现或继承了对应接口或者类,在调用对应方法时
那么就会有对应方法的信息(添加时封装好的),即这些信息就可以说是拦截的信息,然后赋值使用
JDK动态代理由于代理继承了类,那么就要对应需要代理的类实现一个接口
而Cglib动态代理虽然可以返回接口和对应类,但他们对应的final的重写不了
JDK动态代理和Cglib动态代理他们都有父子类关系
初识AOP :
什么是AOP:
AOP 为 Aspect Oriented Programming 的缩写,意思为面向切面编程
AOP 是 OOP(面向对象编程) 的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容
利用AOP可以对业务逻辑的各个部分进行隔离
从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率
这样做的好处是:
在程序运行期间,在不修改源码的情况下对方法进行功能增强
逻辑清晰,开发核心业务的时候,不必关注增强业务的代码
减少重复代码,提高开发效率,便于后期维护
AOP底层实现:
实际上,AOP 的底层是通过 Spring 提供的的动态代理技术实现的
在运行期间Spring通过动态代理技术动态的生成代理对象,代理对象方法执行时进行增强功能的介入
在去调用目标对象的方法,从而完成功能的增强
AOP相关术语:
Spring 的 AOP 实现底层就是对上面的动态代理的代码进行了封装,封装后我们只需要对需要关注的部分进行代码编写
并通过配置的方式完成指定目标的方法增强
在正式讲解 AOP 的操作之前,我们必须理解 AOP 的相关术语
常用的术语如下:
图解:
对应理解的代码如下:
package com.lagou.proxy;
import com.lagou.service.AccountService;
import com.lagou.utils.TransactionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
@Component
public class JDKProxyFactory {
@Autowired
private AccountService accountService;
@Autowired
private TransactionManager transactionManager;
public AccountService createAccountServiceJdkProxy(){
AccountService o = (AccountService)
Proxy.newProxyInstance(accountService.getClass().getClassLoader(),
accountService.getClass().getInterfaces(), new InvocationHandler()
{
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if(method.getName().equals("transfer")) {
System.out.println("进行了前置增强");
transactionManager.beginTransaction();
method.invoke(accountService, args);
transactionManager.commit();
System.out.println("进行了后置增强");
}else{
method.invoke(accountService, args);
}
} catch (Exception e) {
e.printStackTrace();
transactionManager.rollback();
}finally{
transactionManager.release();
}
return null;
}
});
return o;
}
}
package com.lagou.service;
public interface AccountService {
public void transfer(String out, String in, Double money);
public void save();
public void update();
public void delete();
}
package com.lagou.service.impl;
import com.lagou.dao.AccountDao;
import com.lagou.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service("accountService")
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
@Override
public void transfer(String outUser, String inUser, Double money) {
accountDao.out(outUser, money);
accountDao.in(inUser, money);
}
@Override
public void save() {
System.out.println("save方法");
}
@Override
public void update() {
System.out.println("update方法");
}
@Override
public void delete() {
System.out.println("delete方法");
}
}
@Test
public void testTransferProxyJDK(){
System.out.println("被代理类的transfer执行了");
AccountService accountServiceJdkProxy = jdkProxyFactory.createAccountServiceJdkProxy();
accountServiceJdkProxy.save();
}
AOP开发明确事项:
开发阶段(我们做的)
编写核心业务代码(目标类的目标方法) 切入点
把公用代码抽取出来,制作成通知(增强功能方法) 通知
在配置文件中,声明切入点与通知间的关系,即切面
运行阶段(Spring框架完成的):
Spring 框架监控切入点方法的执行
一旦监控到切入点方法被运行,拦截,并使用代理机制,动态创建目标对象的代理对象来进行执行方法
根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行
底层代理实现:
在 Spring 中,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式
当bean实现接口时,会用JDK代理模式
当bean没有实现接口,用cglib实现
也可以强制使用cglib,在spring配置中加入如下配置(后面会讲到)
<aop:aspectj-autoproxy proxy-target-class="true"/>
知识小结:
基于XML的AOP开发:
快速入门:
步骤分析:
创建java项目,导入AOP相关坐标:
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.1.5.RELEASEversion>
dependency>
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>1.8.13version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-testartifactId>
<version>5.1.5.RELEASEversion>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
dependency>
dependencies>
创建目标接口和目标实现类:
package com.lagou.service;
public interface AccountService {
public void transfer();
}
package com.lagou.service.impl;
import com.lagou.service.AccountService;
public class AccountServiceImpl implements AccountService {
@Override
public void transfer() {
System.out.println("转账方法执行了");
}
}
创建通知类:
package com.lagou.advice;
public class MyAdvice {
public void before() {
System.out.println("前置通知执行了");
}
}
将目标类和通知类对象创建权交给spring以及在核心配置文件中配置织入关系,及切面:
<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
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="accountService" class="com.lagou.service.impl.AccountServiceImpl">bean>
<bean id="myAdvice" class="com.lagou.advice.MyAdvice">bean>
<aop:config>
<aop:aspect ref="myAdvice">
<aop:before method="before"
pointcut="execution(void
com.lagou.service.impl.AccountServiceImpl.transfer())"/>
aop:aspect>
aop:config>
beans>
编写测试代码:
package com.lagou.test;
import com.lagou.service.AccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AccountServiceTest {
@Autowired
private AccountService accountService;
@Test
public void testTransfer() {
accountService.transfer();
}
}
XML配置AOP详解:
切点表达式:
表达式语法:
访问修饰符可以省略
返回值类型、包名、类名、方法名可以使用星号 * 代替,代表任意
包名与类名之间一个点 . 代表当前包下的类,两个点 … 表示当前包及其子包下的类
参数列表可以使用两个点 … 表示任意个数,任意类型的参数列表
还有如下例子:
最后注意一点:使用@Test的测试方法,public void 方法名称(){}是固定格式,也就是说
访问权限必须是public,返回值必须是void,方法参数必须是无参,有一个不满足就会报错
使用后置通知,修改配置文件和加上对应方法:
<aop:config>
<aop:aspect ref="myAdvice">
<aop:before method="before"
pointcut="execution(void
com.lagou.service.impl.AccountServiceImpl.transfer())"/>
<aop:after-returning method="after"
pointcut="execution(void
com.lagou.service.impl.AccountServiceImpl.transfer())"/>
aop:aspect>
aop:config>
package com.lagou.advice;
public class MyAdvice {
public void before() {
System.out.println("前置通知执行了");
}
public void after() {
System.out.println("后置通知执行了");
}
}
切点表达式抽取:
当多个增强的切点表达式相同时,可以将切点表达式进行抽取
在增强中使用 pointcut-ref 属性代替 pointcut 属性来引用抽取后的切点表达式
<aop:config>
<aop:pointcut id="myPointcut"
expression="execution(void
com.lagou.service.impl.AccountServiceImpl.transfer())"/>
<aop:aspect ref="myAdvice">
<aop:before method="before" pointcut-ref="myPointcut"/>
-->
<aop:after-returning method="after" pointcut-ref="myPointcut"/>
aop:aspect>
aop:config>
通知类型:
通知的配置语法:
aop:通知类型>
注意:通常情况下,环绕通知都是独立使用的
测试代码:
package com.lagou.advice;
public class MyAdvice {
public void before() {
System.out.println("前置通知执行了");
}
public void after() {
System.out.println("后置通知执行了");
}
public void afterThrowing() {
System.out.println("异常通知执行了");
}
public void afterFinally() {
System.out.println("最终通知执行了");
}
}
xml配置文件:
<aop:config>
<aop:pointcut id="myPointcut"
expression="execution(void
com.lagou.service.impl.AccountServiceImpl.transfer())"/>
<aop:aspect ref="myAdvice">
<aop:before method="before" pointcut-ref="myPointcut"/>
<aop:after-returning method="after" pointcut-ref="myPointcut"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="myPointcut"/>
<aop:after method="afterFinally" pointcut-ref="myPointcut"/>
aop:aspect>
aop:config>
运行结果:
使用环绕通知:
<aop:around method="around" pointcut-ref="myPointcut">aop:around>
public Object around(ProceedingJoinPoint pjp){
Object proceed = null;
try {
System.out.println("前置通知执行了");
proceed = pjp.proceed();
System.out.println("后置通知执行了");
} catch (Throwable e) {
e.printStackTrace();
System.out.println("异常通知执行了");
}finally {
System.out.println("最终通知执行了");
}
return proceed;
}
其实相当于替代执行方法的位置
通过实验
<aop:config>
<aop:pointcut id="myPointcut"
expression="execution(void
com.lagou.service.impl.AccountServiceImpl.transfer())"/>
<aop:aspect ref="myAdvice">
<aop:before method="before" pointcut-ref="myPointcut"/>
<aop:after-returning method="after" pointcut-ref="myPointcut"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="myPointcut"/>
<aop:after method="afterFinally" pointcut-ref="myPointcut"/>
<aop:around method="around" pointcut-ref="myPointcut">aop:around>
aop:aspect>
aop:config>
当他们一起时,对应方法
package com.lagou.advice;
import org.aspectj.lang.ProceedingJoinPoint;
public class MyAdvice {
public void before() {
System.out.println("前置通知执行了");
}
public void after() {
System.out.println("后置通知执行了");
}
public void afterThrowing() {
System.out.println("异常通知执行了");
}
public void afterFinally() {
System.out.println("最终通知执行了");
}
public Object around(ProceedingJoinPoint pjp){
Object proceed = null;
try {
System.out.println("前置通知执行了11");
proceed = pjp.proceed();
System.out.println("后置通知执行了11");
} catch (Throwable e) {
e.printStackTrace();
System.out.println("异常通知执行了11");
}finally {
System.out.println("最终通知执行了11");
}
return proceed;
}
}
执行结果:
知识小结:
基于注解的AOP开发 :
快速入门:
步骤分析:
创建java项目,导入AOP相关坐标:
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.1.5.RELEASEversion>
dependency>
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>1.8.13version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-testartifactId>
<version>5.1.5.RELEASEversion>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
dependency>
dependencies>
创建目标接口和目标实现类:
package com.lagou.service;
public interface AccountService {
public void transfer();
}
package com.lagou.service.impl;
import com.lagou.service.AccountService;
import org.springframework.stereotype.Service;
@Service
public class AccountServiceImpl implements AccountService {
@Override
public void transfer() {
System.out.println("转账方法执行了");
}
}
创建通知类:
package com.lagou.advice;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class MyAdvice {
@Before("execution(* com.lagou.service.impl.AccountServiceImpl.*(..))")
public void before() {
System.out.println("前置通知执行了");
}
}
在配置文件中开启组件扫描和 AOP 的自动代理:
<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
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">
<context:component-scan base-package="com.lagou">context:component-scan>
<aop:aspectj-autoproxy proxy-target-class="true">aop:aspectj-autoproxy>
beans>
编写测试代码 :
package com.lagou.test;
import com.lagou.service.AccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AccountServiceTest {
@Autowired
private AccountService accountService;
@Test
public void testTransfer() {
accountService.transfer();
}
}
注解配置AOP详解 :
切点表达式:
切点表达式的抽取
package com.lagou.advice;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class MyAdvice {
@Pointcut("execution(* com.lagou.service.impl.AccountServiceImpl.*(..))")
public void myPoint(){}
@Before("MyAdvice.myPoint()")
public void before() {
System.out.println("前置通知执行了");
}
@AfterReturning("MyAdvice.myPoint()")
public void afterReturning() {
System.out.println("后置通知执行了");
}
}
通知类型:
通知的配置语法:@通知注解(“切点表达式")
注意:
package com.lagou.advice;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class MyAdvice {
@Pointcut("execution(* com.lagou.service.impl.AccountServiceImpl.*(..))")
public void myPoint(){}
@Before("MyAdvice.myPoint()")
public void before() {
System.out.println("前置通知执行了");
}
@AfterReturning("MyAdvice.myPoint()")
public void afterReturning() {
System.out.println("后置通知执行了");
}
@AfterThrowing("MyAdvice.myPoint()")
public void afterThrowing() {
System.out.println("异常通知执行了");
}
@After("MyAdvice.myPoint()")
public void after() {
System.out.println("最终通知执行了");
}
@Around("MyAdvice.myPoint()")
public Object around(ProceedingJoinPoint pjp){
Object proceed = null;
try {
System.out.println("前置通知执行了11");
proceed = pjp.proceed();
System.out.println("后置通知执行了11");
} catch (Throwable e) {
e.printStackTrace();
System.out.println("异常通知执行了11");
}finally {
System.out.println("最终通知执行了11");
}
return proceed;
}
}
运行结果:
他们的添加方法,有不同的效果,主要是注解或者环绕通知造成的
一般的,我们都会单独使用环绕通知,而不会用其他通知和环绕通知一起,若其他通知出现顺序不对,就可以使用环绕通知来操作
一般开发中,基本都会使用环绕通知来进行,其他通知基本不会使用,因为环绕通知比较方便
纯注解配置 :
package com.lagou.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@ComponentScan("com.lagou")
@EnableAspectJAutoProxy
public class SpringConfig {
}
package com.lagou.test;
import com.lagou.config.SpringConfig;
import com.lagou.service.AccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class AccountServiceTest {
@Autowired
private AccountService accountService;
@Test
public void testTransfer() {
accountService.transfer();
}
}
知识小结 :
AOP优化转账案例 :
依然使用前面的转账案例,将两个代理工厂对象直接删除!改为spring的aop思想来实现
xml配置实现:
在转账案例的配置文件中加入以下配置文件(注意命名空间和约束路径):
<aop:config>
<aop:pointcut id="myPoint"
expression="execution(* com.lagou.service.impl.AccountServiceImpl.*(..))"/>
<aop:aspect ref="transactionManager">
<aop:before method="beginTransaction" pointcut-ref="myPoint"/>
<aop:after-returning method="commit" pointcut-ref="myPoint"/>
<aop:after-throwing method="rollback" pointcut-ref="myPoint"/>
<aop:after method="release" pointcut-ref="myPoint"/>
aop:aspect>
aop:config>
注意加上依赖:
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>1.8.13version>
dependency>
执行测试方法:
@Test
public void textTransfer() {
accountService.transfer("tom", "jerry", 100d);
}
注解配置实现 :
在转账案例的配置文件中加入以下配置文件:
<aop:aspectj-autoproxy>aop:aspectj-autoproxy>
根据下面代码,在转账案例的对应地方加上对应代码:
@Component
@Aspect
public class TransactionManager {
@Around("execution(* com.lagou.service.impl.AccountServiceImpl.*(..))")
public Object around(ProceedingJoinPoint pjp){
Object proceed = null;
try {
connectionUtils.getThreadConnection().setAutoCommit(false);
proceed = pjp.proceed();
connectionUtils.getThreadConnection().commit();
} catch (Throwable e) {
e.printStackTrace();
try {
connectionUtils.getThreadConnection().rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
}finally {
try {
connectionUtils.getThreadConnection().setAutoCommit(true);
connectionUtils.getThreadConnection().close();
connectionUtils.removeThreadConnection();
} catch (SQLException e) {
e.printStackTrace();
}
}
return proceed;
}
}
运行:
@Test
public void textTransfer() {
accountService.transfer("tom", "jerry", 100d);
}
使用注解拦截会先拦截我们写的Proxy类,即在使用注解时,若使用自己的代理类,那么基本会报错
而注解和xml则共生的添加,因为操作是一样的,除非有程序的报错,就如上面的事务,当注解和xml一起使用时
相当于大肠包小肠,即如下