Spring AOP 回顾和拓展
20190604 田超凡
知网用户_进击的猿宝宝(田超凡)版权所有,转载注明原作者
- AOP的概念术语和基本含义
- AOP指的是面向切面编程,是面向对象编程(OOP)的一定维度的扩展和补充,而不是重新发明一个轮子。AOP的主要作用是把从业务逻辑中的日志记录、性能监测、权限控制等代码剥离出来,形成一个横切逻辑,也就是切面(Aspect)。在目标方法执行过程中的特定时间点,由程序启动时在这些目标方法上动态加入某些功能(或者说加入增强处理),把这些动态加入的多个功能封装在一起,就形成了理想意义上的横切逻辑,即切面(Aspect)。AOP的设计思想彻底摒弃了传统硬编码写死的方式而造成的代码耦合度高的问题,使得代码扩展性提高了很多。与此同时,提高了增强方法(也就是动态加入的功能)的扩展性和维护性,也更加符合OOP的核心思想和目标:高内聚,低耦合。
- Spring AOP的基本术语:
Target 目标实例:即需要代理的目标执行实例
Proxy 代理实例:即替代目标实例进行操作的实例
JoinPoint 连接点:目标执行实例需要执行的方法在真正执行时的特定时间片,或者可以理解为跟切入点特征描述比较匹配的目标方法。
PointCut 切入点:代理实例需要代理的目标实例的方法特征,是一个特征描述,目标实例可以存在多个和切入点描述的特征匹配的目标方法作为代理目标方法。
Advice 增强:横切逻辑中需要对目标方法加入的功能,以方法为单位,切面中的一个方法就是一个增强。
Advisor 切面:多个增强方法组成的一个横切逻辑。
Weaving 织入:将切面中的增强和目标方法执行时结合的这一个过程,强调的是增强方法和目标方法结合的过程,作用单位是方法。
Introduction 引入:将切面中的增强和目标类中的符合切入点描述特征的方法结合的过程,强调的是增强方法和目标类中符合切入点特征描述的结合过程,作用单位是类。
- 代理设计模式
- 代理设计模式的核心思想是替代处理,通俗的说,就是找一个替身来做本属于原身要做的事,也可以形象的理解为“替罪羊”,但是很明显这种形容也不是很恰当,如果代理设计模式运用在合适的场景下,并且能发挥出好的功效,代理模式起码也是“占为己有”。Spring AOP实际上就是代理设计模式的一种实现方式。
- 静态代理和动态代理
- 静态代理:定义XXXProxy.java作为静态代理类来对目标接口中的方法进行代理。但是有一个不可忽视的问题就是,如果需要代理的目标接口方法发生了更改,则除了该接口的实现类中的方法需要发生更改,同时该静态代理类中的代理方法也需要调整和更改,代理的目标接口随便的一个方法发生改动,就会牵一发动全身(动实现类,动静态代理类),如果这个接口中的方法数量庞大,比如100个方法,那改动量就不言而喻了,这样很明显代码的扩展性问题还是存在瓶颈,传统的静态代理模式仅适用于代理的目标接口方法不是很多的情况。
- 动态代理:定义一个通用的代理类,代理目标是某个接口或普通类,通过反射来对所有需要代理的目标接口中的方法进行统一代理,当代理目标中的方法发生变化,对于动态代理类而言,他只关心代理的目标本身是否发生变化,即代理的接口和类名称是否发生变化,也就是说当代理的目标接口或类中的方法不论发生什么改动,改动多少次,都不会影响定义的这个动态代理类中的代码,只需要调整和改动代理的目标接口对应的实现类中的代码即可。这种方式很明显提高了代码扩展性和维护性,从容应对代理目标内部发生的任何变化,都不会影响动态代理类本身,这也是代理设计模式的核心所在。
(1).定义需要代理的目标接口和实现类
/***
* TODO TCF 被代理的接口,基于jdk动态代理
* @author 71485
*
*/
public interface HelloAop {
//TODO TCF 被代理的目标方法
public void show();
}
/**
* TODO TCF HelloAop接口实现类
* @author 71485
*
*/
public class HelloAopImpl implements HelloAop{
@Override
public void show()
{
System.out.println("======Hello,Aop======");
}
}
(2).定义静态代理类
package com.aop.example.statics;
import org.apache.log4j.Logger;
import com.aop.example.HelloAop;
/***
* TODO TCF 静态代理类
* @author 71485
*
*/
public class HelloAopProxy implements HelloAop{
private Logger logger=Logger.getLogger(HelloAopProxy.class);
//TODO TCF 构造注入的目标代理对象
private HelloAop helloAop;
public HelloAopProxy(HelloAop helloAop)
{
this.helloAop=helloAop;
}
//TODO TCF 前置增强
public void before()
{
logger.debug("前置增强功能.....");
}
//TODO TCF 后置增强
public void afterReturning()
{
logger.debug("后置增强功能.....");
}
//TODO TCF 汉堡=上中下
public void show()
{
before();
helloAop.show();
afterReturning();
}
}
(3).编写测试代码,查看运行结果
@SuppressWarnings(value = "unchecked")
public static void main(String[] args)
{
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("applicationContext-mybatis.xml");
HelloAopProxy proxy=new HelloAopProxy(new HelloAopImpl());
proxy.show();
}
- JDK动态代理:代理的目标对象只能是接口
package testAOP;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class MyInvocationHandler implements InvocationHandler {
private Object target;
MyInvocationHandler(Object target){
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
//目标方法前执行
System.out.println("——————————————————————————");
System.out.println("下一位请登台发言!");
//目标方法调用
Object obj = method.invoke(target, args);
//目标方法后执行
System.out.println("大家掌声鼓励!");
return obj;
}
}
4.CGLIB动态代理:代理的目标对象可以是任何类
package testAOP.cglib;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class CglibProxy implements MethodInterceptor{
Enhancer enhancer = new Enhancer();
public Object getProxy(Class clazz) {
//设置需要创建的子类
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
//通过字节码技术动态创建子类实例
return enhancer.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
// TODO Auto-generated method stub
System.out.println("——————————————————————————");
System.out.println("下一位请登台发言!");
//目标方法调用
Object result = proxy.invokeSuper(obj, args);
//目标方法后执行
System.out.println("大家掌声鼓励!");
return result;
}
}
package testAOP.cglib;
import testAOP.Saying;
import testAOP.SayingImpl;
public class CglibProxyTest {
public static void main(String[] args) {
CglibProxy proxy = new CglibProxy();
//通过动态生成子类的方式创建代理类
Saying target = (Saying) proxy.getProxy(SayingImpl.class);
target.sayHello("小明");
target.talking("小丽");
}
}
运行结果:
——————————————————————————
下一位请登台发言!
小明:大家好啊!
大家掌声鼓励!
——————————————————————————
下一位请登台发言!
小丽:我的意思是,我们要努力建设和谐社会!
大家掌声鼓励!
- SpringAOP配置式和声明式
- Spring AOP的配置是基于spring-framework-aop模块实现的,需要事先在Spring的核心配置文件applicationContext-mybatis.xml中的xml声明中引入对应的xsd文件头即可。
- 配置式实现Spring AOP的配置
- 注解式实现Spring AOP的配置
- 注解式实现Spring AOP的增强处理
- 编码式实现Spring AOP的增强处理
- 基于AOP的声明式事务管理
- 配置式实现声明式事务管理
(1).applicationContext-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:aop="http://www.springframework.org/schema/aop"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:cache="http://www.springframework.org/schema/cache"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:jee="http://www.springframework.org/schema/jee"
xmlns:lang="http://www.springframework.org/schema/lang"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:mybatis-spring="http://mybatis.org/schema/mybatis-spring"
xmlns:oxm="http://www.springframework.org/schema/oxm"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:task="http://www.springframework.org/schema/task"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd
http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache-4.1.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-4.1.xsd
http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.1.xsd
http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-4.1.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd
http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring-1.2.xsd
http://www.springframework.org/schema/oxm http://www.springframework.org/schema/oxm/spring-oxm-4.1.xsd
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.1.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.1.xsd">
<!-- 扫描DAO层组件包 -->
<context:component-scan base-package="com.aop"></context:component-scan>
<!-- TODO TCF Spring AOP声明式事务XML配置方式 -->
<!-- 引入数据源配置文件 -->
<context:property-placeholder location="classpath:database.properties"/>
<!-- 配置druid数据源 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${driver}"></property>
<property name="url" value="${url}"></property>
<property name="username" value="${user}"></property>
<property name="password" value="${pwd}"></property>
</bean>
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 事务织入规则 -->
<tx:advice id="txAdvice">
<tx:attributes>
<tx:method name="insert*" propagation="REQUIRED"></tx:method>
<tx:method name="update*" propagation="REQUIRED"></tx:method>
<tx:method name="delete*" propagation="REQUIRED"></tx:method>
<tx:method name="add*" propagation="REQUIRED"></tx:method>
<tx:method name="select*" propagation="SUPPORTS"></tx:method>
<!-- 默认均使用事务代理 -->
<tx:method name="*" propagation="REQUIRED"></tx:method>
</tx:attributes>
</tx:advice>
<!-- AOP切入点和增强处理 -->
<aop:config>
<!-- 事务切入点,原则上均用来织入事务管理 -->
<aop:pointcut expression="execution(* com.aop.example.TransactionTest.*(..))" id="transactionPointcut"/>
<!-- 织入事务增强 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="transactionPointcut"/>
</aop:config>
<!-- 启用基于jdk动态代理(false),只能代理接口实现类 -->
<aop:aspectj-autoproxy proxy-target-class="false"></aop:aspectj-autoproxy>
</beans>
(2)编写测试代码:
@SuppressWarnings(value = "unchecked")
public static void main(String[] args)
{
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("applicationContext-mybatis.xml");
//TODO TCF 声明式事务管理
TransactionTest transactionTest=(TransactionTest)applicationContext.getBean("transactionTest");
transactionTest.add();
transactionTest.query();
}
很明显,根据配置的事务增强匹配规则,
add()织入了事务处理,query()不匹配规则,所以没有创建对应的事务管理器进行事务处理。
- 注解式实现声明式事务管理
(1).applicationContext-mybatis.xml加入如下配置即可启用注解式事务管理,无需再次进行切入点匹配规则的配置。
<!-- 启用注解式事务管理 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
<!-- 事务织入规则 -->
<!-- <tx:advice id="txAdvice">
<tx:attributes>
<tx:method name="insert*" propagation="REQUIRED"></tx:method>
<tx:method name="update*" propagation="REQUIRED"></tx:method>
<tx:method name="delete*" propagation="REQUIRED"></tx:method>
<tx:method name="add*" propagation="REQUIRED"></tx:method>
<tx:method name="select*" propagation="SUPPORTS"></tx:method>
</tx:attributes>
</tx:advice> -->
<!-- AOP切入点和增强处理 -->
<!-- <aop:config>
事务切入点,原则上均用来织入事务管理
<aop:pointcut expression="execution(* com.aop.example.TransactionTest.*(..))" id="transactionPointcut"/>
织入事务增强
<aop:advisor advice-ref="txAdvice" pointcut-ref="transactionPointcut"/>
</aop:config> -->
(2).事务测试类中对应方法加入@Transactional注解,指定Propagation事务传播方式和Isolation事务隔离等级(可选,均已提供默认值)
@Component
public class TransactionTest {
private static Logger logger=Logger.getLogger(TransactionTest.class);
//TODO TCF 新增
@Transactional(propagation=Propagation.REQUIRED)
public void add()
{
logger.debug("新增操作执行....");
}
//TODO TCF 修改
public void update()
{
logger.debug("修改操作执行....");
}
//TODO TCF 删除
public void delete()
{
logger.debug("删除操作执行....");
}
//TODO TCF 查询
public void query()
{
logger.debug("query查询....");
}
public void select()
{
logger.debug("select查询....");
}
}
(3).编写测试代码,运行结果如下
@SuppressWarnings(value = "unchecked")
public static void main(String[] args)
{
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("applicationContext-mybatis.xml");
//TODO TCF 声明式事务管理
TransactionTest transactionTest=(TransactionTest)applicationContext.getBean("transactionTest");
transactionTest.add();
transactionTest.query();
}
参考文献:
1. CSDN 《SpringAOP详细配置和使用》
2.博客园 《JDK和CGLIB动态代理》