SpringAOP的使用及实现原理

SpringAOP

1、代理模式

​ 代理模式:就是给被代理对象提供一个代理对象,代理对象能够进行进行一些常用的业务操作,并应用被代理对象中的方法,从而保证被代理对象内容的简洁和实用。代理模式一般分为静态代理和动态代理两种。

1.1、静态代理

以下是以事务管理为例对静态代理解决的问题进行详细的阐述。

  • 问题1:事务是开在dao上面的,所以如果在service中调用多个dao的方法,如果在这些方法执行过程中报错,事务没法回滚?

    解决办法:对于我们的应用,事务都应该开在service的方法上面。那么具体的操作就要注意以下两点。

    1,sessionFactory.getCurrentSession:得到当前线程中的session;

    2,把session的开启和事务的开启,事务的提交从dao中移到service方法中;

在这里插入图片描述

  • 问题2:存在一个事务跨层的问题,但是如果直接把事务管理的代码写在service中,会造成service代码污染

    解决办法:最好的方式就是把事务的代码从service中抽取出来,使用代理模式,再加到目标代码中,代码如下:

    DAOImpl的代码:

    public class EmployeeDAOImpl implements IEmployeeDAO{
    
    	@Override
    	public void save() {
    		System.out.println("Session.save");//DAOImpl的实现		
    	}
    }

    ServiceImpl的代码:

    public class EmployeeServiceImpl implements IEmployeeService {
    	@Autowired
    	IEmployeeDAO employeedao ;
    	@Override
    	public void save() {
    		employeedao.save();	//调用DAO存的方法
    		int i = 1/0;//业务逻辑出错,判断是否回滚
    	}
    }

    TranscationServiceImpl代理类的代码

    public class EmployeeTranscationServiceImpl implements IEmployeeService{
    	private IEmployeeService target;//目标对象
    	public EmployeeTranscationServiceImpl(IEmployeeService service) {//用构造方法传入被代理对象
    		this.target = service;
    	}
    	@Override
    	public void save() {
    		try {
    		System.out.println("sessionfactory.getCurrentSession");//拿到DAO层的session
    		System.out.println("session.getTranscation().begin()");//开启事务
    		target.save();
    		System.out.println("session.getTranscation().commit()");//提交事务
    		}catch (Exception e) {
    			System.out.println("session.getTranscation().rollback()");//事务回滚
    		}
    	}
    }

    配置文件:

    <bean id="employeedao" class="com.it520.dynProxy.EmployeeDAOImpl"></bean>
    <bean id="employeeservice" class="com.it520.dynProxy.EmployeeServiceImpl" ></bean>
    <bean id="employeeservicetranscation" class="com.it520.dynProxy.EmployeeTranscationServiceImpl" >
    <!-- 构造函数将被代理对象注入 -->
    <constructor-arg ref="employeeservice"></constructor-arg>
    </bean>

    运行结果:

    sessionfactory.getCurrentSession
    session.getTranscation().begin()//代理对象的事务开启
    Session.save//调DAO层的实现
    session.getTranscation().rollback()//Service层出错,事务回滚
    

1.2、动态代理

1.2.1,有接口的对象的动态代理

JDK中提供了针对有接口对象的动态代理实现方式;

在这里插入图片描述

(1)客户端调用save方法,执行被InvocationHanalder拦截;

(2)在invoke方法中执行添加的额外的逻辑;

(3)调用m.invoke(target,args)执行到了真实的目标对象上的对应方法;

(4)真实对象返回执行结果到InvocationHandler里面;

(5)执行完成其他逻辑,把结果返回给客户端;

实现流程

(1)使用JDK的动态代理,必须要保证代理的真实对象是有接口的;接口实现类如下:

public class EmployeeServiceImpl implements IEmployeeService {
	@Override
	public void save(Employee e) {
		System.out.println("dao.seve(employee)");
        int i = 1/0;
	}
}

(2)需要实现一个InvocationHandler,并实现invoke方法;

a、需要给InvocationHanlder的实现类提供真实对象;

b、在invoke方法中执行自己的额外添加的业务逻辑;案例的其他业务逻辑时事务TranscationManager类中的方法;

c、在编写完额外逻辑之后,调用method.invoke(target,args)执行真实对象上的对应方法;

d、得到真实方法执行的结果;

e、处理结果,并把结果返回给客户端;

TranscationManager类代码:

public class TranscationManager {
	public void begin() {
		System.out.println("session.getTranscation().begin()");
	}
	public void commit() {
		System.out.println("session.getTranscation().commit()");
	}
	public void rollback() {
		System.out.println("session.getTranscation().rollback()");
	}
}

代理类的代码:

public class TranscationInvocationHandler implements InvocationHandler {
	private TranscationManager txManager;//额外业务逻辑的类
	private Object target;//给InvocationHanlder的实现类提供真实对象;
	public TranscationInvocationHandler(TranscationManager txManager,Object target) {
		this.target=target;
		this.txManager=txManager;
	}
	@Override
	//proxy:代理出来的对象;method:这次调用的方法;args:调用方法传入的参数
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		txManager.begin();//额外添加的业务逻辑
		try {
			Object ret = method.invoke(target, args);//执行真实对象上的对应方法;得到真实方法执行的结果
			txManager.commit();
			return ret;//处理结果,并把结果返回给客户端
		}catch(Exception e) {
			txManager.rollback();
		}
		return null;
	}
}

(3)在客户端调用Proxy.newProxyInstance方法:

a、需要传入目标对象的classloader;

b、需要传入目标对象的接口类型;

c、需要传入一个自定义的InvocationHandler的实例;

@RunWith(value = SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class DynProxyTest {
	@Autowired
	private IEmployeeService service;
	@Autowired
	private TranscationManager txManager;
	@Test
	public void test() {
	/*	a、service.getClass().getClassLoader()需要传入目标对象的classloader;
		b、new Class[] {IEmployeeService.class}需要传入目标对象的接口类型;
		c、new TranscationInvocationHandler(txManager, service))需要传入一个自定义的InvocationHandler的实例*/;
		IEmployeeService o = (IEmployeeService)Proxy.newProxyInstance(service.getClass().getClassLoader(), 
				new Class[] {IEmployeeService.class}, new TranscationInvocationHandler(txManager, service));
		Employee e = new Employee();
		o.save(e);
	}
}

执行结果:

session.getTranscation().begin()
dao.seve(employee)
session.getTranscation().rollback()
1.2.2,没有接口的对象的动态代理

使用继承的方式完成动态代理;javassist/cglib

使用CGLIB完成没有接口的对象的动态代理如下:

被代理对象的代码:

public class Somebean {
	public void print(int i) {
		System.out.println("somebean");
	}
}

代理对象的代码:

public class LogInvocationHandler implements InvocationHandler {
	private Object target;
	public LogInvocationHandler(Object target) {
		this.target=target;
	}
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		System.out.println("操作时间"+new Date()+"当前调用了"+method+"方法传入的参数是"+Arrays.toString(args));
		return method.invoke(target, args);
	}
}

客户端的测试类:

public class CglibTest {
@Test
public void test() {
	Somebean target = new Somebean();
	Enhancer e = new Enhancer();
	e.setClassLoader(target.getClass().getClassLoader());//传入目标对象的classloader
	e.setSuperclass(Somebean.class);//传入目标对象类型
	e.setCallback(new LogInvocationHandler(target));//传入一个自定义的InvocationHandler的实例
	Somebean o = (Somebean)e.create();
	o.print();
}
}

结果:

操作时间Sun Jul 05 22:21:09 CST 2020当前调用了public void spring.cglib.Somebean.print(int)方法传入的参数是[2]
somebean

2、SpringAOP

AOP就是一种更高级的动态代理的使用;Aspect Oritention Programming(面向切面编程)

切入点:要加入业务逻辑的点(在哪些类的哪些方法上面;)

通知:通知包含两个方面,1,代表方法的执行时间,2,在这个时间上面要做什么事情;

切面:一个切入点+一个通知=一个切面(在什么地方,在什么时候,做什么事情);

织入:把一个切面应用到真实对象上面的过程,就叫做织入;

2.1、使用Spring的AOP:

在Java中,没有语言能够准确的描述切入点;所以,有一个AspectJ,这是一种语言,提供了用于描述切入点的语言;

定义切入点的表达式

在这里插入图片描述

如果可以是任意的值,使用*就可以了;带?的表示可省略的的部分

例如:

aop包中,所有的service的所有的方法;

execution(* com._520it.spring.aop.Service.(…));

day2中所有service的save方法;

execution(* com._520it.spring…*Service.save(…))

2.1.1、使用XML的配置方式

配置文件如下

 <bean id="service" class="spring.aop.EmployeeServiceImpl"></bean>
 <bean id="txManager"  class="spring.aop.TranscationManager"></bean>
	<!-- 配置AOP -->
	<aop:config>
		<!-- 配置一个切入点
		 expression:这个切入点的表达式
		 id:这个切入点的名字 -->
		<aop:pointcut expression="execution(* spring.aop.*Service.*(..))" id="pc"/>
		<!-- 配置一个切面
		代表这个切面定义中,所有做的事情都有txManager这个对象的方法提供 -->
		<aop:aspect ref="txManager">
			<aop:before method="begin" pointcut-ref="pc"/>
			<aop:after-returning method="commit" pointcut-ref="pc"/>
			<aop:after-throwing method="rollback" pointcut-ref="pc"/>
		</aop:aspect>
	</aop:config>

TranscationManager代码:

public class TranscationManager {
	public void begin() {
		System.out.println("session.getTranscation().begin()");
	}
	public void after() {
		System.out.println("session.getTranscation().after()");
	}
	public void commit() {
		System.out.println("session.getTranscation().commit()");
	}
	public void rollback() {
		System.out.println("session.getTranscation().rollback()");
	}
}

测试代码:

@RunWith(value = SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class AOPTest {
	@Autowired
	private IEmployeeService service;
	@Autowired
	private IDepartmentService dservice;
	@Autowired
	private TranscationManager txManager;
	@Test
	public void test() {
		service.save(new Employee());//这个方法无错误
	}
	@Test
	public void test1() {
		dservice.save(new Department());//这个方法有错误
	}

运行结果:

session.getTranscation().begin()
dao.seve(employee)
session.getTranscation().commit()
session.getTranscation().begin()
session.getTranscation().rollback()
2.1.2、使用注解的方式

(1)配置文件中开启切点的自动注入和注解扫描

<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<context:component-scan base-package="spring.annaop"></context:component-scan>

(2)在代理对象上添加注解

@Aspect//这个注解相对<aop:aspect ref="txManager">
@Component
public class TranscationManager {
	@Pointcut("execution(* spring.annaop.Somebean.*(..))")//定义一个方法,用@Pointcut注解来配置切点
	public void pc() {}
	@Before(value = "pc()")//通过注解@Before相当于xml配置中的aop:before,value参数引入切点
	public void begin() {
		System.out.println("session.getTranscation().begin()");
	}
	@After("pc()")//通过注解@After相当于xml配置中的aop:afte,value参数引入切点
	public void after() {
		System.out.println("session.getTranscation().after()");
	}
	@AfterReturning("pc()")//通过注解@AfterReturning相当于xml配置中的aop:after-returning,value参数引入切点
	public void commit() {
		System.out.println("session.getTranscation().commit()");
	}
	@AfterThrowing("pc()")//通过注解@AfterThrowing相当于xml配置中的aop:after-throwing,value参数引入切点
	public void rollback(Throwable ex) {
		System.out.println("session.getTranscation().rollback()"+ex.getMessage());
	}

(3)SomeBean类和测试类代码:

@Component
public class Somebean {
	public void print() {
		System.out.println("somebean");
	}
}
@RunWith(value = SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class AOPTest {
	@Autowired
	private Somebean bean;
	@Test
	public void test() {
		bean.print();
	}
}
2.1.3、Spring中AOP的通知类型

aop:before:前置通知;

aop:after-returning:后置通知;

aop:after:最终通知;

aop:after-throwing:异常通知;

这四个通知的执行方式可参考以下代码,前面例子也提到过,不做赘述。

try {
    System.out.println("before");
    Object ret = pjp.proceed();
    System.out.println("after-returning");
    return ret;
} catch (Throwable ex) {
    System.out.println("after-throwing");
}finally {
    System.out.println("after");
}

下面具体讲一下一个整合了以上四个通知的通知:

aop:around:环绕通知;

Xml配置中的实现方式如下:

<aop:around method="around" arg-names="pjp" pointcut-ref="pc"/><!--  arg-names:around方法的参数名字 -->

代理类中关于around方法的代码如下:

public Object around(ProceedingJoinPoint pjp) {			
		try {
			System.out.println("session.getTranscation().begin()");
			Object ret = pjp.proceed();
			System.out.println("session.getTranscation().commit()");
			return ret;
		} catch (Throwable ex) {
			System.out.println("session.getTranscation().rollback()");
		}finally {
			System.out.println("session.getTranscation().after()");
		}
		return null;
	}

使用注解的方式就是在around方法上添加@Around注解,value值是切点;

@Around(value = "pc()")
	public Object around(ProceedingJoinPoint pjp) {}

2.2、SpringAOP的执行流程

1,解析xml;

2,实例化所有的bean;

3,解析aop:config;

(1)解析aop:aspect,得到aspect引用的对象;txManager

(2)解析aop:aspect里面的每一个切面;

a、得到该aspect对应的pointcut-ref;

b、得到pointcut-ref对应的pointcut的表达式;

(3)使用表达式中用于匹配类型的表达式;

(4)使用该表达式去和spring里面配置的所有的bean的类型进行匹配;

​ a、如果匹配不上,不管;

​ b、如果匹配上了,该对象作为spring动态代理的目标对象;

​ 1,如果该对象实现了接口,使用JDK的动态代理包装;

​ 2,如果该对象没有实现接口,使用cglib包装;

​ 3,得到配置的拦截时机+逻辑提供类(txManager)的对应方法(从method解析)+pointcut表达式中方法的匹配模式创建一个拦截器

​ 4,在把该拦截器使用对应的动态代理机制代理成代理对象;
的实例;
5,替换spring容器中的对应bean的实例;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值