代理与AOP

代理模式

1.为什么使用代理,什么情况下使用

(1)客户端无法直接访问对象,或访问非常耗时,例如,远程访问时需要对访问对象进行打包拆包等一系列操作,这些操作属于非功能代码,应与功能代码区分开。
(2)对被代理对象(简称目标对象)功能上的扩展,为保证对象功能的单一性,须在对象外扩展对象的功能,例如权限控制等非业务功能和公共功能,为解决代码冗余,需要使用代理。

2.静态代理的实现

(1)代理类和目标类引用同一个接口或父类,约束同样的方法
(2)代理类中包含对目标类的引用
(3)代理类对接口方法的中调用目标对象的方法并对功能进行扩展
在这里插入图片描述
问题:每个代理只针对一个接口,如果接口变多,则代理类会很多,只解决了功能的扩展,没有很好的解决代码冗余问题。

3.动态代理のJDK动态代理

(1)涉及的接口和类
①InvocationHandler.invoke(…)
1)代理类实现该接口的invoke方法,对目标类进行功能扩展
2)参数1:Object arg0:被代理的目标对象
3)参数2:Method arg1:被代理方法
4)参数3:Object[] arg2:方法的参数
②Proxy.newProxyInstance(…)
1)返回代理对象。
2)参数1:ClassLoader loader:目标类的类加载器
3)参数2:Class<?>[] interfaces:目标类的接口
4)参数3:InvocationHandler h:代理类的实现类

(2)实现过程及代码
①公共接口

public interface Inter {
	public void doSomthing(String arg);
}

②目标类:实现接口方法

public class Target implements Inter {
	@Override
	public void doSomthing(String arg) {
		System.out.println("do target method : "+arg);
	}
}

③InvocationHandler实现类:包含成员目标对象target,并通过构造函数或bind方法绑定目标对象,这里的bind方法还直接返回的代理对象。通过实现invoke方法扩展目标类的功能。在invoke中反射调用方法method.invoke(target,args),并在调用前后添加功能代码。

public class DynProxy implements InvocationHandler {
	private Object target;
	/**
	 * 运行时通过构造方法绑定目标对象
	 */
	public DynProxy(Object target) {
		this.target=target; 
	}
	public DynProxy() {
		
	}
	/**
	 * 运行时通过方法绑定目标对象,并通过Proxy返回
*实现了Inter接口的代理对象
	 */
	public Inter bind(Object target){
		this.target=target;
		return (Inter)Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),this);
//根据目标类,接口组和当前实现了InvocationHandler的类生成代理类并通过其构造方法返回一个代理对象,供用户调用。
//如果不通过bind方法绑定目标对象,则需要在使用时调用Proxy.newProxyInstance方法。
	}

	/**
	 * 调用接口方法时进入该方法,是从生成的$Proxy0中调用的invoke
	 */
	@Override
	public Object invoke(Object arg0, Method arg1, Object[] arg2) throws Throwable {
		System.out.println("do dynamic proxy pre");//功能扩展1
		arg1.invoke(target, arg2);//反射调用目标对象的方法
		System.out.println("do dynamic proxy post");//功能扩展2
		return null;
	}
}

④调用

public static void main(String[] args) {
	//构造方法
	Target target = new Target();
//构造方法绑定目标对象,手动调用newProxyInstance
	DynProxy proxy = new DynProxy(target);		
Inter inter = (Inter)Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),proxy);
	inter.doSomthing("I'm in dynamic proxy main");//直接调用接口的方法
		
	DynProxy proxy2 = new DynProxy();
	Inter inter2 = proxy2.bind(target);//调用bind方法绑定目标对象,该方法直接返回了代理对象,代理对象实现了Inter接口
	inter2.doSomthing("I'm in dynamic proxy main by bind method");//调用该方法时进入了invoke方法
}

(3)Tips:
不同的目标对象有不同的行为,因此invoke的实现类并不关心具体的目标对象行为,而改为反射调用目标对象的特定方法,也就是说,运行时传入不同对象,代理类提供的扩展功能是不变的,变化的是目标对象的功能,例如通过jdk动态代理实现对会员和管理员的权限控制,前期权限检查和后期返回结果都是不变的,都需要查表验证,但是因为传入的目标对象不同,执行method.invoke(target,args)时执行的代码不同,所以完成的功能也是不同的。对于易扩展,若需要对超级用户进行权限控制,只需让该类实现公共接口,然后调用代理即可。这样可以提高代码的重用性,易于扩展,同时降低模块之间的耦合性(权限控制与功能代码)
(4)深入理解源码Proxy.newProxyInstance
详情参见https://www.cnblogs.com/MOBIN/p/5597215.html
①在newProxyInstance中调用了getProxyClass0(loader,intfs),生成代理类,返回一个Class,并获取该类的构造函数,构造一个代理类实例,供用户调用。
②在getProxyClass0(loader,intfs)中,如果缓存中存在代理类则直接返回,若不存在则调用ProxyFactory创建代理类并返回
③在ProxyFactory中先对ClassLoader和interfaces做一系列验证,声明代理类的统一名称 P r o x y i ( i 为 0   n 数 字 ) , 生 成 的 代 理 类 都 在 c o m . s u n . P r o x y 包 下 , 这 样 就 组 成 了 代 理 类 的 全 限 定 名 c o m . s u n . P r o x y . Proxyi(i为0~n数字),生成的代理类都在com.sun.Proxy包下,这样就组成了代理类的全限定名com.sun.Proxy. Proxyi(i0 n)com.sun.Proxycom.sun.Proxy.Proxyi.class,通过ProxyGenerator.generateProxyClass(全限定名,接口组)生成代理类的字节码并写出到class文件,并通过defineClass()加载字节码,返回Class对象。
④在ProxyGenerator.generateProxyClass(全限定名,interfaces)中调用了ProxyGeneraotr.generateClassFile()来生成类的字节码文件,并将文件写入FileOutputStream。
⑤在generateClassFile()中为待生成的代理类ProxyGenerator对象(简称pg)添加了hashCode, equls, toString三个Object类方法,并将参数interfaces中的所有接口方法都添加到了pg中,然后编写代理类字节码,根据pg中写入的方法,添加方法和静态成员
1)添加代理类的构造函数
2)将所有方法映射到该pg对象的静态成员,private static Method mi(i=0~n),包括接口方法和Object三大件
3)生成代理类方法,并添加到pg对象。
最后生成字节码。
⑥最后生成的 P r o x y 0. c l a s s 文 件 如 下 : Proxy0.class文件如下: Proxy0.classProxy继承自Proxy类,并实现了被代理的接口

public final class $Proxy0 extends Proxy implements Inter{   //继承了Proxy类和实现Inter接口
    //变量,都是private static Method  XXX
    private static Method m3;  
    private static Method m1;
    private static Method m0;
    private static Method m2;
//代理类的构造函数,其参数正是是InvocationHandler实例,
//Proxy.newInstance方法就是通过通过这个构造函数来创建代理实例的
    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }
    //接口代理方法
    public final void doSomthing(String arg) throws  {
        try {
            super.h.invoke(this, m3, (Object[])arg);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }
    //以下Object中的三个方法
    public final boolean equals(Object var1) throws  {
        try {
            return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue();
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }
    public final int hashCode() throws  {
        try {
            return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }
    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }
    //对变量进行一些初始化工作
    static {
        try {
            m3 = Class.forName("com.spring.boot.proxy").getMethod("doSomthing", new Class[0]);
            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]);
            m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

(5)问题
只能代理实现了接口的类,若某个类没有实现接口,则不能被代理。因此可以用cglib代理。

4.动态代理のCGLib代理

(1)涉及到的接口和类
①MethodInterceptor.intercept(…)
1)代理类实现该接口的intercept方法,在该方法中实现功能扩展
2)参数1:Object target:目标对象
3)参数2:Method method:目标方法
4)参数3:Object[] args:方法参数
5)参数4:MethodProxy proxy:代理
②Enhancer.setSuperClass(…) & setCallback(…)
1)增强器,用于扩展目标类
2)setSuperClass(Class targetC):设置增强器的父类,即目标类
3)setCallback(…):设置回调,即调用哪个代理,一般参数为this
(2)实现过程及代码
①目标类

public class Target implements Inter {
	@Override
	public void doSomthing(String arg) {
		System.out.println("do target method : "+arg);
	}
	
	public void doOwn(String arg){
		System.out.println("do own method : "+arg);
	}
}

②Cglib代理
public class CglibProxy implements MethodInterceptor {

private Object target;//目标对象

/**
 * getInstance:返回代理类对象 <br/>
 * 绑定目标对象,添加增强及回调方法
 */
public Object getInstance(Object target){
	this.target=target;//绑定目标对象
	Enhancer enhancer = new Enhancer();//创建增强器
	enhancer.setSuperclass(target.getClass());//设置父类
	enhancer.setCallback(this);//设置回调方法
	return enhancer.create();//返回增强后的对象
}
@Override
public Object intercept(Object target, Method method, Object[] args, MethodProxy proxy) 
		throws Throwable {
	System.out.println("cglib pre enhancer");//前期增强
	proxy.invokeSuper(target, args);//调用目标对象方法
	System.out.println("cglib post enhancer");//后期增强
	return null;
}

}
③测试类

public class TestCglibProxy {
	public static void main(String[] args) {
		CglibProxy proxy = new CglibProxy();//创建代理
		Target target = (Target)proxy.getInstance(new Target());//获取代理对象
		target.doSomthing("cglib");//调用扩展后的方法
	}
}

(3)不太深入的理解
CGlib通过FastClass机制能比反射更快的访问到代理的方法,具体的机制为:为类的所有方法生成索引,并创建一个与之对应的FastClass,在该FastClass中根据索引快速访问方法,避免反射调用。在Cglib中是根据目标类和代理类分析生成FastClass。当调用invokeSuper方法时,其实是直接进入到了代理类重写的方法中,并直接调用super.method(args)访问目标类的方法。
(4)JDK代理与CGlib代理的区别
①机制不同:反射和继承
②速度不同:反射相对较慢,FastClass快
③JDK需要目标类实现接口,所以只能代理接口中的方法,CGlib基于继承,因此无需实现接口,且可代理类的所有方法。
④在调用时JDK将代理对象转换为接口对象,CGlib向上转型为父类对象。

AOP

概览

解决的问题和优势

面向对象将代码都封装到对象中,这就导致了一部分非功能性代码的冗余,因此AOP将对象拆解开,取出非功能性代码并封装成一个模块(权限,日志等),此为切面。AOP降低了代码耦合性,提高了可维护性。

概念

1.AOP将系统分为2部分:核心关注点(业务功能),横切关注点(非业务功能),因此AOP得功能是分离系统中的各种关注点
2.横切关注点:拦截哪些方法,拦截后怎么处理
3.切面(aspect):对横切关注点的抽象,相当于类对对象的抽象,在实际编码中就是一个类
4.连接点(join point):在程序执行中的某个特定的点,通常指方法调用时或处理异常时,在spring中通常指被拦截到的方法。
5.切入点(pointcut):匹配连接点的断言,通常在切面类中使用,功能是让通知和切入点关联,并在满足这个切入点表达式的连接点上执行通知代码,说人话就是,关联被拦截到的方法与拦截后执行的代码,当某一个方法满足切入点表达式时,就在被拦截的方法上执行通知中的切面代码。连接点关联切入点,切入点关联通知。切入点只是一个描述信息,通知是在连接点上执行的,而切入点规定了哪些连接点可以执行通知。
6.通知(advice):指拦截到连接点(方法)后要执行的代码,分为前置,后置,异常,最终和环绕5种通知。许多 AOP框架, 包括 Spring AOP, 会将 advice 模拟为一个拦截器(interceptor), 并且在 join point 上维护多个 advice, 进行层层拦截。并维护一个以连接点为中心的拦截链。
7.引入(introduction):在不修改代码的前提下,引入可以在运行期为类动态的添加一些方法和字段(如何用,怎么用)–待添加chapter6
8.目标对象(target):指织入通知后产生的代理类。
9.织入(weaving):把切面连接到对象上,并创建一个被通知对象,织入是在运行时完成的。
10.通过切入点匹配连接点的概念是AOP的关键,切入点使得通知可以独立于面向对象层次结构,例如,一个提供声明式事务管理的通知可以被应用到一组横跨多个对象的方法上。我的理解为,这个通知可以应用到任何需要事务管理的方法上,只需定义一组切入点即可实现。
原理
AOP原理为通过切入点在目标类中织入增强代码,生成一个AOP Proxy代理类,该类融合了原方法和通知方法的逻辑,从而实现了对原方法的扩展。Spring AOP默认是J2EE代理,若类未实现接口,则使用CGLib代理。
Spring2.0中实现AOP有两种方式:基于注解的和基于XML配置的,一般采用基于注解的形式,因为比较方便,而且看起来高大上。
Spring2.0中引入利用AspectJ来做切入点的声明和匹配,但是在实际运行时仍然是基于代理的spring AOP,没有用到AspectJ的编译器和织入器。可以简单的认为由@Aspect注解的类就是切面类。
例1.AOP Test
本例以自定义注解标注需要横切的类
1.自定义注解

/**
 * ClassName: AuthChecked <br/>
 * Description: 用于标注需要权限拦截的请求 <br/>
 * Date: 2018年12月4日 上午11:04:23 <br/>
 * @author liuqiqi <br/>
 */
@Target(ElementType.METHOD)//标注方法
@Retention(RetentionPolicy.RUNTIME)//待查
public @interface AuthChecker {
	
}

2.创建切面类,在切面类中定义:切点,通知,采用环绕通知方式

/**
 * Description: 切面类 1.声明切入点2.声明一系列通知 <br/>
 */
@Component
@Aspect
public class HttpAopAdviseDefine {
	/**
	 * pointcut:声明一个切入点,
	 * 切入点表达式为@annotation(com.spring.boot.aop.AuthChecker)
	 * 含义为以由AuthChecker注解标注的方法为切入点<br/>
	 */
	@Pointcut("@annotation(com.spring.boot.aop.AuthChecker)")
	public void pointcut(){}
	/**
	 * checkAuth:声明一个环绕通知,当该切入点捕捉到的连接点被调用前,先执行该通知,说人话就是这是一个在被拦截方法前执行的方法,应该叫通知,执行一段代码,然后调用proceed()执行被拦截方法,执行结束后再运行一段代码,那我拦截哪些方法呢,由切入点规定,在注解中指定切入点<br/>
	 */
	@Around("pointcut()")
	public Object checkAuth(ProceedingJoinPoint joinPoint) throws Throwable{
		//在通知中获取当前请求
		HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
		String token = getToken(request);
		if(!token.equals("12345")){
			return "执行了前置通知,token不对";
		}//运行前
		Object result= joinPoint.proceed();
        System.out.println(result.toString);//运行后
        return result;
	}
	public String getToken(HttpServletRequest request){
		Cookie[] cookies = request.getCookies();
		if(cookies==null){
			return "";
		}
		for(Cookie cookie : cookies){
			if(cookie.getName().equalsIgnoreCase("user_token")){
				return cookie.getValue();
			}
		}
		return "";
	}
}

3.测试Controller

/**
 * ClassName: TestAdviceController <br/>
 * Description:  测试切入点<br/>
 * Date: 2018年12月4日 下午2:02:40 <br/>
 * @author liuqiqi <br/>
 */
@Controller
public class TestAdviceController {

	@RequestMapping("/aop/testWithAspect")
	@AuthChecker
	@ResponseBody
	public String testAdviceMethod2(HttpServletRequest request){
		return "这是调用了切面的方法且通过权限检查 ";
	}
}

例2.AOP实现权限控制
服务中有很多接口,有的需要权限校验而有的不需要。传统方法是在每个接口中添加校验代码,会导致代码冗余,采用aop解决。当然也可以通过过滤器和拦截器实现。权限控制我觉得用拦截器会更好,因为拦截器是针对url请求拦截,与拦截方法的AOP相比,可以保证拦截到每个请求。AOP比较是个做日志,异常处理等。
1.在切面类中声明切点,拦截DataController下的方法

/**
 * ClassName: HttpAopAdviseDefine <br/>
 * Description: 切面类
 * 1.声明切入点
 * 2.声明一系列通知 <br/>
 * Date: 2018年12月4日 上午11:09:27 <br/>
 * @author liuqiqi <br/>
 */
@Component
@Aspect
public class HttpAopAdviseDefine {
	/**
	 * 验证用户是否登录
	 * 在所有com.spring.boot.controller.project包下的类上执行通知
	 */
	@Pointcut("execution(* com.spring.boot.controller.project.DataController.*(..))")
	public void loginPointcut(){}
	/**
	 * checkLogin: 判断用户是否登录,若未登录重定向到login页面 <br/>
	 */
	@Around("loginPointcut()")
	public Object checkLogin(ProceedingJoinPoint joinPoint) throws Throwable{
		//在通知中获取当前请求
		HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
		HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
		if(request.getSession().getAttribute("user")!=null||request.getRequestURL().indexOf("welcom")!=-1
				||request.getRequestURL().indexOf("login")!=-1){
			return joinPoint.proceed();
		}
		else{
			response.sendRedirect("/welcom/");
			return null;
		}
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值