Spring框架之AOP详解
AOP 概述
在OOP(面向对象编程)中,正是这种分散在各处的与对象核心功能无关的代码(横向切面代码)的存在,使得模块的复用复用难度增加。AOP则将封装好的对象剖开,找出其中的对多个对象产生影响的公共行为,并将其封装为一个可复用的模块,这个模块倍命名为切面(Aspect),切面将那些与业务无关,却被业务模块共同调用的逻辑提取并封装起来,减少了系统中的重复代码,降低了模块之间的耦合度,同时提高的系统的可维护性。
AOP的实现策略
(1)Java SE动态代理: 使用动态代理可以为一个或多个接口在运行期动态生成实现对象,生成的对象中实现接口的方法时可以添加增强代码,从而实现AOP。缺点是只能针对接口进行代理,另外由于动态代理是通过反射实现的,有时可能要考虑反射调用的开销。 (2)字节码生成(CGLib 动态代理) 动态字节码生成技术是指在运行时动态生成指定类的一个子类对象,并覆盖其中特定方法,覆盖方法时可以添加增强代码,从而实现AOP。其常用工具是cglib。 (3)定制的类加载器 当需要对类的所有对象都添加增强,动态代理和字节码生成本质上都需要动态构造代理对象,即最终被增强的对象是由AOP框架生成,不是开发者new出来的。解决的办法就是实现自定义的类加载器,在一个类被加载时对其进行增强。JBoss就是采用这种方式实现AOP功能。 (4)代码生成 利用工具在已有代码基础上生成新的代码,其中可以添加任何横切代码来实现AOP。 (5)语言扩展 可以对构造方法和属性的赋值操作进行增强,AspectJ是采用这种方式实现AOP的一个常见Java语言扩展。
AOP术语
1)连接点(Joinpoint)
程序执行的某个特定位置:如类开始初始化前、类初始化后、类某个方法调用前、调用后、方法抛出异常后。一个类或一段程序代码拥有一些具有边界性质的特定点,这些点中的特定点就称为“连接点”。Spring仅支持方法的连接点,即仅能在方法调用前、方法调用后、方法抛出异常时以及方法调用前后这些程序执行点织入增强。连接点由两个信息确定:第一是用方法表示的程序执行点;第二是用相对点表示的方位
2)切点(Pointcut)
每个程序类都拥有多个连接点,如一个拥有两个方法的类,这两个方法都是连接点,即连接点是程序类中客观存在的事物。AOP通过“切点”定位特定的连接点。连接点相当于数据库中的记录,而切点相当于查询条件。切点和连接点不是一对一的关系,一个切点可以匹配多个连接点。在Spring中,切点通过org.springframework.aop.Pointcut接口进行描述,它使用类和方法作为连接点的查询条件,Spring AOP的规则解析引擎负责切点所设定的查询条件,找到对应的连接点。其实确切地说,不能称之为查询连接点,因为连接点是方法执行前、执行后等包括方位信息的具体程序执行点,而切点只定位到某个方法上,所以如果希望定位到具体连接点上,还需要提供方位信息。
3)增强(Advice)
增强是织入到目标类连接点上的一段程序代码,在Spring中,增强除用于描述一段程序代码外,还拥有另一个和连接点相关的信息,这便是执行点的方位。结合执行点方位信息和切点信息,我们就可以找到特定的连接点。
4)目标对象(Target)
增强逻辑的织入目标类。如果没有AOP,目标业务类需要自己实现所有逻辑,而在AOP的帮助下,目标业务类只实现那些非横切逻辑的程序逻辑,而性能监视和事务管理等这些横切逻辑则可以使用AOP动态织入到特定的连接点上。
5)引介(Introduction)
引介是一种特殊的增强,它为类添加一些属性和方法。这样,即使一个业务类原本没有实现某个接口,通过AOP的引介功能,我们可以动态地为该业务类添加接口的实现逻辑,让业务类成为这个接口的实现类。
6)织入(Weaving)
织入是将增强添加对目标类具体连接点上的过程。AOP像一台织布机,将目标类、增强或引介通过AOP这台织布机天衣无缝地编织到一起。根据不同的实现技术,AOP有三种织入的方式:
a、编译期织入,这要求使用特殊的Java编译器。
b、类装载期织入,这要求使用特殊的类装载器。
c、动态代理织入,在运行期为目标类添加增强生成子类的方式。
Spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。
7)代理(Proxy)
一个类被AOP织入增强后,就产出了一个结果类,它是融合了原类和增强逻辑的代理类。根据不同的代理方式,代理类既可能是和原类具有相同接口的类,也可能就是原类的子类,所以我们可以采用调用原类相同的方式调用代理类。
8)切面(Aspect)
切面由切点和增强(引介)组成,它既包括了横切逻辑的定义,也包括了连接点的定义,Spring AOP就是负责实施切面的框架,它将切面所定义的横切逻辑织入到切面所指定的连接点中。
案例分析
代理模式是常用的Java设计模式,按照代理模式的创建时期分为两种代理模式。
静态代理模式:
由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。
动态代理模式:
在程序运行时,运用反射机制动态创建而成,动态代理分为jdk动态代理和CGLIB动态代理 。
应用案例
静态代理
//entity实体类
public class Employee{
}
//dao接口
public interface EmployeeDAO{
void update();
void save();
}
//dao实现类
public class EmployeeDAOImpl implements IEmployeeDAO {
public void save(Employee emp) {
System.out.println("保存员工");
}
public void update(Employee emp) {
System.out.println("修改员工");
}
}
//模拟事物
//模拟事务管理器:
public class TransactionManager {
public void begin() {
System.out.println("开启事务");
}
public void commit() {
System.out.println("提交事务");
}
public void rollback() {
System.out.println("回滚事务");
}
}
//静态代理类
//静态代理类
public class EmployeeServiceProxy implements IEmployeeService {
private IEmployeeService target;//真实对象/委托对象
private TransactionManager txManager;//事务管理器
public void setTarget(IEmployeeService target) {
this.target = target;
}
public void setTxManager(TransactionManager txManager) {
this.txManager = txManager;
}
public void save(Employee emp) {
txManager.begin();
try {
target.save(emp);
txManager.commit();
} catch (Exception e) {
e.printStackTrace();
txManager.rollback();
}
}
public void update(Employee emp) {
txManager.begin();
try {
target.update(emp);
txManager.commit();
} catch (Exception e) {
e.printStackTrace();
txManager.rollback();
}
}
}
//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:p="http://www.springframework.org/schema/p" 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-4.3.xsd">
<bean id="employeeDAO" class="cn.wolfcode.dao.impl.EmployeeDAOImpl" />
<bean id="transactionManager" class="cn.wolfcode.tx.TransactionManager" />
<!-- 代理对象 -->
<bean id="employeeServiceProxy" class="cn.wolfcode.proxy.EmployeeServiceProxy">
<property name="txManager" ref="transactionManager" />
<!-- 配置委托对象 -->
<property name="target">
<bean class="cn.wolfcode.service.EmployeeServiceImpl">
<!-- 委托对象依赖dao -->
<property name="dao" ref="employeeDAO" />
</bean>
</property>
</bean>
</beans>
//App测试
package cn.wolfcode;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import cn.wolfcode.domain.Employee;
import cn.wolfcode.service.IEmployeeService;
@SpringJUnitConfig
public class App {
@Autowired
private IEmployeeService service;
@Test
void testSave() throws Exception {
System.out.println(service.getClass());//查看对象的真实类型
service.save(new Employee());
}
@Test
void testUpdate() throws Exception {
service.update(new Employee());
}
}
测试结果
class com.sun.proxy.$Proxy19
开启事务
保存员工
保存成功
提交事务
jdk动态代理
注意:jdk动态代理是基于接口实现
//entity实体类
public class Employee{
}
//dao接口
public interface IEmployeeDAO {
void save(Employee emp);
void update(Employee emp);
}
//dao实现类
public class EmployeeDAOImpl implements IEmployeeDAO {
public void save(Employee emp) {
System.out.println("保存员工");
}
public void update(Employee emp) {
System.out.println("修改员工");
}
}
//service接口
public interface IEmployeeService {
void save(Employee emp);
void update(Employee emp);
void delete(Long id);
List<Employee> listAll();
}
//service实现类
public class EmployeeServiceImpl implements IEmployeeService {
private IEmployeeDAO dao;
public void setDao(IEmployeeDAO dao) {
this.dao = dao;
}
public void save(Employee emp) {
dao.save(emp);
System.out.println("保存成功");
}
public void update(Employee emp) {
dao.update(emp);
//测试增强是否遇到异常能回滚
throw new RuntimeException("故意错误的");
}
public void delete(Long id) {
System.out.println("删除操作");
}
public List<Employee> listAll() {
System.out.println("查询所有");
return new ArrayList<>();
}
}
//模式事物类
//模拟事务管理器:
public class TransactionManager {
public void begin() {
System.out.println("开启事务");
}
public void commit() {
System.out.println("提交事务");
}
public void rollback() {
System.out.println("回滚事务");
}
}
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
@SuppressWarnings("all")
//事务的增强操作
public class TransactionManagerAdvice implements java.lang.reflect.InvocationHandler {
private Object target;//真实对象(对谁做增强)
private TransactionManager txManager;//事务管理器(模拟)
public void setTxManager(TransactionManager txManager) {
this.txManager = txManager;
}
public void setTarget(Object target) {
this.target = target;
}
//ClassLoader loader:类加载器
//Class<?>[] interfaces:得到全部的接口
//InvocationHandler h:得到InvocationHandler接口的子类实例
//创建一个代理对象
public <T> T getProxyObject() {
return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), //类加载器,一般跟上真实对象的类加载器
target.getClass().getInterfaces(), //真实对象所实现的接口(JDK动态代理必须要求真实对象有接口)
this);//如何做事务增强的对象 //要绑定接口(这是一个缺陷,cglib弥补了这一缺陷)
}
//参数说明:
//Object proxy:指被代理的对象。
//Method method:要调用的方法
//Object[] args:方法调用时所需要的参数
//如何为真实对象的方法做增强的具体操作
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().startsWith("get") || method.getName().startsWith("list")) {
return method.invoke(target, args);//放行
}
Object ret = null;
txManager.begin();
try {
//---------------------------------------------------------------
ret = method.invoke(target, args);//调用真实对象的方法
//---------------------------------------------------------------
txManager.commit();
} catch (Exception e) {
e.printStackTrace();
txManager.rollback();
}
return ret;
}
}
<?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:p="http://www.springframework.org/schema/p" 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-4.3.xsd">
<!-- 配置dao -->
<bean id="employeeDAO" class="cn.wolfcode.dao.impl.EmployeeDAOImpl" />
<!-- 配置事物管理器 -->
<bean id="transactionManager" class="cn.wolfcode.tx.TransactionManager" />
<!-- 配置service -->
<bean id="employeeService" class="cn.wolfcode.service.impl.EmployeeServiceImpl">
<!-- 依赖注入dao -->
<property name="dao" ref="employeeDAO" />
</bean>
<!-- 配置一个事务增强的类 -->
<bean id="transactionManagerAdvice" class="cn.wolfcode.tx.TransactionManagerAdvice">
<property name="target" ref="employeeService"/>
<property name="txManager" ref="transactionManager"/>
</bean>
</beans>
//测试类
//使用Spring5提供的测试
@SpringJUnitConfig
public class App {
@Autowired
private TransactionManagerAdvice advice;
//代理对象:com.sun.proxy.$Proxy19
@Test //junit测试
void testSave() throws Exception {
//获取代理对象
IEmployeeService proxy = advice.getProxyObject();
System.out.println(proxy.getClass());
proxy.save(new Employee());
}
@Test
void testUpdate() throws Exception {
//获取代理对象
IEmployeeService proxy = advice.getProxyObject();
proxy.update(new Employee());
}
}
测试结果
class com.sun.proxy.$Proxy19
开启事务
保存员工
保存成功
提交事务
CGLIB动态代理
JDK的动态代理机制只能代理实现了接口的类,而不能实现接口的类就不能实现JDK的动态代理,cglib是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理
//创建需要被代理的类
public class EmployeeDAOImpl {
public void save() {
System.out.println("保存员工");
}
public void update() {
System.out.println("修改员工");
}
}
//模拟事务管理器:
public class TransactionManager {
public void begin() {
System.out.println("开启事务");
}
public void commit() {
System.out.println("提交事务");
}
public void rollback() {
System.out.println("回滚事务");
}
}
//创建CGLIB动态代理类
package com.iarchie.cglib.advice;
import java.lang.reflect.Method;
import org.springframework.cglib.proxy.Enhancer;
//实现Spring - InvocationHandler接口
import org.springframework.cglib.proxy.InvocationHandler;
public class MyAdvice implements InvocationHandler {
private Object target;
private TransactionManager tManager;
public void setTarget(Object target) {
this.target = target;
}
public void settManager(TransactionManager tManager) {
this.tManager = tManager;
}
// 创建代理对象
public <T> T getObjectProxy() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass());// 继承哪个类去做增加
enhancer.setCallback(this); // 设置增加的对象
return (T) enhancer.create();// 创建代理对象
}
// 为真实对象的真实方法做具体的增强
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object object = null;
// 开启事物
tManager.begin();
try {
object = method.invoke(target, args);// 调用真实对象的方法
// 提交事物
tManager.commit();
} catch (Exception e) {
e.printStackTrace();
// 回滚事物
tManager.rollback();
}
return object;
}
}
<?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:p="http://www.springframework.org/schema/p" 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-4.3.xsd">
<!-- 配置dao -->
<bean id="employeeDAO"
class="com.iarchie.cglib.dao.impl.EmployeeDAOImpl" />
<!-- 配置事物管理器 -->
<bean id="transactionManager"
class="com.iarchie.cglib.advice.TransactionManager" />
<!-- 配置一个事务增强的类 -->
<bean id="myAdvice" class="com.iarchie.cglib.advice.MyAdvice">
<property name="target" ref="employeeDAO" />
<property name="tManager" ref="transactionManager" />
</bean>
</beans>
//测试
package com.iarchie.cglib;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import com.iarchie.cglib.advice.MyAdvice;
import com.iarchie.cglib.dao.impl.EmployeeDAOImpl;
@SpringJUnitConfig
public class App {
@Autowired
private MyAdvice advice;
//JDK代理对象:com.sun.proxy.$Proxy19
//CGLIB代理对象:com.iarchie.cglib.dao.impl.EmployeeDAOImpl$$EnhancerByCGLIB$$765a98d8
@Test
void testSave() throws Exception {
EmployeeDAOImpl proxy = advice.getObjectProxy();
proxy.save();
System.out.println(proxy.getClass());
}
}
测试结果
开启事务
保存员工
提交事务
class com.iarchie.cglib.dao.impl.EmployeeDAOImpl$$EnhancerByCGLIB$$765a98d8
jdk动态代理和CGLIB动态代理的区别
java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
1、如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP 2、如果目标对象实现了接口,可以强制使用CGLIB实现AOP
3、如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换
如何强制使用CGLIB实现AOP? (1)添加CGLIB库,SPRING_HOME/cglib/*.jar (2)在spring配置文件中加入<aop:aspectj-autoproxy proxy-target-class="true"/>
JDK动态代理和CGLIB字节码生成的区别? (1)JDK动态代理只能对实现了接口的类生成代理,而不能针对类 (2)CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法 因为是继承,所以该类或方法最好不要声明成final
上一篇: Oracle使用临时变量