SpringAOP详解(第二次学习Spring,主要是JDK动态代理)

    第一次学习Spring的时候只是觉得很好用,而且也只是停留在会用的阶段,现在在公司实习,又叫看Spring,于是就有了这篇文章,这时才体会到Spring确实优秀。(博主只是一个自学一年不到的小白,如果有错还望各位大佬批评指正)

 一.装饰类    

        例子:在我们实现具体的业务需求操作的时候,我们为了保证业务安全,需要在业务方法前面开启事务,方法完成之后再提交事务,如果业务出错还需要回滚事务。如果有大量的业务方法,我们就会发现业务代码变得臃肿,而且全是重复事务的操作,这时候我们就会想要重构。因此首先我们是采用装饰类来加强业务代码。(说明一下,为了直观一点,我所有的bean都用spring的xml装配,不采用@autowired自动装配)

package com.swust.service.impl;
import com.swust.dao.IEmployeeDAO;
import com.swust.dao.impl.EmployeeDAOImpl;
import com.swust.domain.Employee;
import com.swust.service.IEmployeeService;

import lombok.Setter;

public class EmployeeServiceImpl implements IEmployeeService {
	@Setter
	private IEmployeeDAO dao;
 
	public void save(Employee employee) {
			dao.save(employee);
	}

	public void update(Employee employee) {
		dao.update(employee);
		throw new RuntimeException("故意出错=============");
	}

}
//这是包装类
package com.swust.test.wapper;
import com.swust.TranscantionManager;
import com.swust.domain.Employee;
import com.swust.service.IEmployeeService;
/**
 * @author Mr
 * 模仿包装类,用事务包装EmployeeServiceImpl,达到增强的目的
 */
public class EmployeeServiceWapper implements IEmployeeService{
	
	private IEmployeeService serviceImpl;
	private TranscantionManager tx;

	public EmployeeServiceWapper(IEmployeeService serviceImpl, TranscantionManager tx) {
		this.serviceImpl = serviceImpl;
		this.tx = tx;
	}
	public void save(Employee e) {
		try {
			tx.begin();
			serviceImpl.save(e);
			tx.commit();
		} catch (Exception e2) {
			e2.printStackTrace();
			tx.rollback();
		}
	}
	public void update(Employee e) {
		try {
			tx.begin();
			serviceImpl.update(e);
			tx.commit();
		} catch (Exception e2) {
			e2.printStackTrace();
			tx.rollback();
		}
	}
}
package com.swust;
//需要增强的操作,可以是事务,log等

public class TranscantionManager {
	public void begin(){
		System.out.println("***********开启事务***********");
	}
	public  void commit(){
		System.out.println("***********提交事务***********");
	}
	public void rollback(){
		System.out.println("***********回滚事务***********");
	}
}

我们可以看见,在包装类中需要实现真实对象的接口,需要引入真实对象并通过构造器注入真实对象,接下来看一看测试类。

package com.swust.test.wapper;
import org.junit.Test;
import com.swust.TranscantionManager;
import com.swust.domain.Employee;
import com.swust.service.impl.EmployeeServiceImpl;

public class TestWapper {
	EmployeeServiceWapper wapper=new EmployeeServiceWapper(new EmployeeServiceImpl(), new  TranscantionManager());
	Employee e=new Employee();
	@Test
	public void testSave() throws Exception {
		wapper.save(e);	
	}
	@Test
	public void testUpdate() throws Exception {
		wapper.update(e);
	}
}

可以看见这种方法确实可以增强业务方法,但是它向外暴露的真实对象,不符合封装,并且这个类不可复用,一个真实类需要一个包装类。所以改进一下引出静态代理。同样是那些业务,来看看静态代理类。

二.静态代理

package com.swust.test.proxy.staticProxy;
import com.swust.TranscantionManager;
import com.swust.domain.Employee;
import com.swust.service.IEmployeeService;
import lombok.Setter;

/**
 * @author Mr
 * 模仿静态代理类,使用spring容易管理bean,静态代理没有暴露真实对象,而包装类会暴露
 */
public class EmployeeServiceStaticProxy implements IEmployeeService{
	@Setter
	private IEmployeeService target;
	@Setter
	private TranscantionManager tx;

	public void save(Employee e) {
		try {
			tx.begin();
			target.save(e);
			tx.commit();
		} catch (Exception e2) {
			e2.printStackTrace();
			tx.rollback();
		}	
	}
}
<!-- 配置事务管理 -->
	<bean id="txManager" class="com.swust.TranscantionManager"></bean>
	<!-- 配置DAO -->
	<bean id="employeeDAO" class="com.swust.dao.impl.EmployeeDAOImpl"></bean>

	<!-- 配置代理 -->
	<bean id="employeeServiceProxy" class="com.swust.test.proxy.staticProxy.EmployeeServiceStaticProxy">
		<property name="tx" ref="txManager"></property>
		<property name="target">
			<!-- 配置真实对象 -->
			<bean class="com.swust.service.impl.EmployeeServiceImpl">
				<property name="dao" ref="employeeDAO"></property>
			</bean>
		</property>
	</bean><bean class="com.swust.service.impl.EmployeeServiceImpl">
				<property name="dao" ref="employeeDAO"></property>
			</bean>
		</property>
	</bean>

可以看出静态代理,并没有向外暴露真实对象,因为他是无参数的构造器,在配置文件中也可以隐藏起来,其他和装饰类基本一样。

三.JDK动态代理

        为了解决类的不可复用性,就引入了动态代理。下面来模仿一下JDK的动态代理(AOP基于动态代理)。

package com.swust.test.proxy.jdkProxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import com.swust.TranscantionManager;
import lombok.Setter;

public class TranscantionManagerHandler implements InvocationHandler{
	@Setter
	private Object target;//真实对象,可以通过spring注入
	@Setter
	private TranscantionManager tx;
	/**
	 * @return 代理对象
	 * 生成的代理类会继承Proxy类
	 */
	@SuppressWarnings("unchecked")
	public <T>T getProxyObj(){
		Object object = Proxy.newProxyInstance(target.getClass().getClassLoader()//通过真实对象得到类加载器
				, target.getClass().getInterfaces()//真实对象的接口
				, this);//InvocationHandler h:这里表示当前类对象,即代理辅助类对象,最终会调用加强后的invoke方法
		return (T) object;
	}
	/**
	 * 在真实对象上增强
	 * 
	 */
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		System.out.println("辅助类中的method是:"+method+"===args:"+args);
                System.out.println("辅助类中的args是:"+args);
		System.out.println("========前面做======增强");
		Object object = method.invoke(target, args);
		System.out.println("========后面做======增强");
		return object;
	}
}

假设还是需要事务增强,那么这个类就必须实现InvocationHandler接口,这个接口中只有一个方法

package java.lang.reflect;
public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

然后实现接口的invoke方法。随后还需要在类中写一个获取代理对象的方法(不在这儿写也可以,获取的方法一样)。有两种方法可以获取代理对象,这里使用Proxy.newProxyInstance来获取。这里需要传入三个参数,一个是类加载器,一个是真实对象类实现的接口,一个就是代理辅助类的对象h。我们先看看Proxy的源码

public class Proxy implements java.io.Serializable {

     protected InvocationHandler h;  
 
     public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        Objects.requireNonNull(h);

        final Class<?>[] intfs = interfaces.clone();
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }
        Class<?> cl = getProxyClass0(loader, intfs);
        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }

            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            return cons.newInstance(new Object[]{h});
        } 
    }


protected InvocationHandler h;  
 
     public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        Objects.requireNonNull(h);

        final Class<?>[] intfs = interfaces.clone();
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }
        Class<?> cl = getProxyClass0(loader, intfs);
        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }

            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            return cons.newInstance(new Object[]{h});
        } 
    }


主要看颜色加深部分,可以看见源码中的Proxy维护了一个InvocationHandler 对象h,调用newProxyInstance方法的时候会先检查辅助类TranscantionManagerHandler对象h是否为空,如果空,则抛出空指针。之后利用传进来的接口和类加载器获取代理类(代理对象会实现该接口,注意代理类只存在内存中,如需存入硬盘需要改变JVM启动参数,方法自行百度)。随后根据代理类的含有InvocationHandler 对象h的构造器创建一个代理类的对象返回。之后我们看看测试方法。

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class JDKProxyTest {
	@Autowired
	private TranscantionManagerHandler handle;
	
	@Test
	public void testSave() throws Exception {
		IEmployeeService service = handle.getProxyObj();//得到的service为代理对象,代理类存在内存中
		service.save(new Employee());
                System.out.println("service对象的类是=======》"+service.getClass());
         }
}

通过注入代理辅助类对象调用 得到代理对象的方法getProxyObj() 得到代理对象。之后代理对象调用自身的业务方法(这里是save),很多人看到这里就很好奇,辅助类的TranscantionManagerHandler增强的invoke方法什么时候调用的呢?先看看测试结果

service确实是代理类的对象,业务方法也得到了增强,而在代理辅助类invoke方法中的参数method可以看见是接口中的save方法。

需要知道什么时候调用的invoke就需要反编译生成的代理类字节码(调整虚拟机参数将代理类字节码保存在硬盘),在生成代理类中$Proxy11继承了Proxy类,实现了需要被代理类的接口,也就是IEmployeeService,其构造器是如下,还含有三个Object的方法和真实对象的业务方法。

public $Proxy11(InvocationHandler invocationhandler) {  
        super(invocationhandler);  
    }
  static {  
        try {  
            m1 = Class.forName("java.lang.Object").getMethod("equals",  
                    new Class[] { Class.forName("java.lang.Object") });  
  
            m0 = Class.forName("java.lang.Object").getMethod("hashCode",  
                    new Class[0]);  
  
            m3 = Class.forName("***.RealSubject").getMethod("save",  //代理方法名称
                    new Class[0]);  
  
            m2 = Class.forName("java.lang.Object").getMethod("toString",  
                    new Class[0]);  
  
    } //static   
public final void save() {  //代理方法
        try {  
            super.h.invoke(this, m3, null);  
            return;  
        } catch (Error e) {  
        } catch (Throwable throwable) {  
            throw new UndeclaredThrowableException(throwable);  
        }  

    } super.h.invoke(this, m3, null);  
            return;  
        } catch (Error e) {  
        } catch (Throwable throwable) {  
            throw new UndeclaredThrowableException(throwable);  
        }  

    } 

在这儿就可以看出了,当代理对象调用save方法时,会调用

super.h.invoke(this, m3, null);

而在父类Proxy中之前我们讲过它维护了一个InvocationHandler的对象h,因此由于代理辅助类实现了一个InvocationHandler的接口,所以最终h会调用其实现类TranscantionManagerHandler的invoke方法,因此在

Object object = method.invoke(target, args);

前后就可以做想要的增强了。

 

                                                                     总结一下


    JDK动态代理,含有增强方法的类需要实现InvocationHandler接口,重写invoke方法。获取代理对象的时候需要传入接口,类加载器,因此需要代理的类必须实现接口。在内存中创建代理类的时候,通过传的接口,创建一个代理类并实现该传入的接口,继承Proxy,在生成的代理类的业务方法中调用父类维护的InvocationHandler的h对象,并调用h.invoke()方法,而由于含有增强方法的代理辅助类实现了InvocationHandler接口,因此实际就会调用代理辅助类实现的invoke方法,最后通过代理辅助类里面注入的真实对象使用method.invoke(target,args)就可以调用真实的业务方法了,在在方法前后就可以做增强。

注意:jdk动态代理必须要实现接口

 

四.CGlib动态代理

原理大部分和jdk动态代理一样,只是获取代理对象的方法不同,其余都一样。

public class TranscantionManagerHandlerCallbck implements org.springframework.cglib.proxy.InvocationHandler {
	@Setter
	private Object target;// 真实对象

	@SuppressWarnings("unchecked")
	public <T> T getProxyObj() {
		Enhancer enhancer = new Enhancer();
		enhancer.setSuperclass(target.getClass());//代理类需要继承的父类
		enhancer.setCallback(this);//需要增强的对象,即该类的对象
		return (T) enhancer.create();
	}

	/**
	 * 在真实对象上增强
	 */
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		System.out.println("辅助类中的method是:" + method + "===args:" + args);
		System.out.println("辅助类中的args是:" + args);
		System.out.println("========前面做======增强");
		Object object = method.invoke(target, args);
		System.out.println("========后面做======增强");
		return object;
	}
}
Enhancer enhancer = new Enhancer();
		enhancer.setSuperclass(target.getClass());//代理类需要继承的父类
		enhancer.setCallback(this);//需要增强的对象,即该类的对象
		return (T) enhancer.create();
	}

	/**
	 * 在真实对象上增强
	 */
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		System.out.println("辅助类中的method是:" + method + "===args:" + args);
		System.out.println("辅助类中的args是:" + args);
		System.out.println("========前面做======增强");
		Object object = method.invoke(target, args);
		System.out.println("========后面做======增强");
		return object;
	}
}

这里实现的类是spring的cglib包里的。另外需要cglib代理的类必须是可扩展的,因为cglib代理不需要实现接口,只需要继承父类就可以(继承需要被代理的类)。

 

五.AOP

  先贴一段aop的配置

        <!-- 配置aop:切面what,切入位置where,切入时机when -->
	<aop:config>
		<!-- what:做什么增强 -->
		<aop:aspect ref="txManager">
			<!--where: 在哪些包下的哪些类和接口的哪些方法上做增强 -->
			<aop:pointcut expression="execution(* com.swust.common.service.*Service.*(..))" id="txPoint" />
			<!-- when:在什么时候做增强(业务方法前/后/前后)第一个表示在之前做txManager中begin方法的增强 -->
			<aop:before method="begin" pointcut-ref="txPoint"/>
			<aop:after method="commit" pointcut-ref="txPoint"/>
			<aop:after-throwing method="rollback" pointcut-ref="txPoint"/>
		</aop:aspect>
	</aop:config>
	<!-- 配置事务管理 -->
	<bean id="txManager" class="com.swust.common.TranscantionManager"></bean>

(有很多配法)在这儿<aspect>里面配的就相当于需要增强的类,相当于jdk代理中的代理辅助类。而pointcut表示切入点,意思就是我们需要在哪些类的哪些方法上做增强,相当于前面的save业务方法,在这儿指的是service包下的所以带Service结尾的方法,execution是切的语法,需要引入相关aop和aspectj的包。而再下面就是增强的时机,需要在业务方法之前,之后还是环绕增强,并配置增强的方法。由于所有的bean都交给spring管理,spring会帮我们生成相关的类。关于sop还有其他的一些配置只有了解到了aop原理那么配置那些都是很简单的,需要了解可以去官方看看。另外,除了xml配置spring还支持注解配置。

 

注:Spring中AOP代理由Spring的IOC容器负责生成、管理,其依赖关系也由IOC容器负责管理。因此,AOP代理可以直接使用容器中的其它bean实例作为目标,这种关系可由IOC容器的依赖注入提供。Spring创建代理的规则为:

        1、默认使用Java动态代理来创建AOP代理,这样就可以为任何接口实例创建代理了

        2、当需要代理的类不是代理接口的时候,Spring会切换为使用CGLIB代理,也可强制使用CGLIB

 

 

 

 

 

 

 

 

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值