关于代理模式以及SpringAop

代理模式(Proxy)

代理模式是一种结构型设计模式,为其他对象(委托类)提供一个代理(代理类)以控制对原对象的访问,并允许在将请求提交给对象前后进行一些处理,如负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。

静态:由程序员创建代理类或特定工具自动生成源代码再对其编译,在程序运行前代理类的.class文件就已经存在了。
动态:在程序运行时运用反射机制动态创建而成。

静态代理

类图

image-20210901104726379

案例代码
  1. 定义一个接口
public interface ServiceInterface {
	void operation();
}
  1. 创建具体类实现接口
public class ServiceImpl implements ServiceInterface {   
    @Override  
    public void operation() {  
        System.out.println("执行业务操作.....");  
    }  
} 
  1. 创建代理类并实现接口(代理对象要实现与目标对象一样的接口)
public class ServiceProxy implements ServiceInterface {  
  
    // 目标对象  
    private ServiceInterface service;  
    // 通过构造方法传入目标对象  
    public Proxy(ServiceInterface service){  
        this.service = service;  
    }  
    
  	@Override
	public void operation() {
		System.out.println("业务操作执行之前....");
		service.operation();
		System.out.println("业务操作执行之后....");
	}
}  
  1. 测试
public class Client {
	public static void main(String[] args) {
		//创建被代理的接口
		ServiceInterface service = new ServiceImpl();
		//创建代理对象并将被代理对象聚合
		ServiceProxy serviceProxy = new ServiceProxy(service);
		//调用代理方法
		serviceProxy.operation();
	}
}
静态代理的优缺点

优点:
(1) 可以做到在不修改目标对象的功能前提下,对目标对象功能扩展(代理模式的优点)。
(2) 静态代理在编译时产生class字节码文件,可以直接使用,效率高。

缺点:
(1) 因为代理类和委托类实现了相同的接口,一旦接口增加方法,目标对象与代理对象都要维护。
(2) 代理对象只服务于一种类型的对象,如果要服务多个类型的对象,势必要为每一种对象都进行代理,这时需为每种对象都创建静态代理类(即静态代理类只能为一个接口服务,如想要为多个接口服务则需要建立很多个代理类),当项目规模较大时会引起类爆炸。

缺点的解决方案: 引入动态代理。

动态代理

动态代理有两种实现方式:
1.jdk动态代理
2.cgilb动态代理

jdk动态代理

jdk的动态代理机制只能代理实现了接口的类,故为接口代理。(代理对象不需要实现接口,但是目标对象一定要实现接口)

相关类:java.lang.reflect包下Proxy类和InvocationHandler类。其中,InvocationHandler是一个接口,可以通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态地将横切逻辑和业务逻辑编织在一起。

/*
返回一个指定接口的代理类的实例方法调用分派到指定的调用处理程序。 (返回代理对象)
     loader:一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载
     interfaces:一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了
     h:一个InvocationHandler接口,表示代理实例的调用处理程序实现的接口。每个代理实例都具有一个关联的调用处理程序。对代理实例调用方法时,将对方法调用进行编码并将其指派到它的调用处理程序的invoke方法(传入InvocationHandler接口的子类)
*/
public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
类图

image-20210901151551794

代码案例

1.委托类(接口和实现类)

//接口
public interface ITeacherDao {
	void teach(); // 授课方法
	void sayHello(String name);
}
//实现类
public class TeacherDao implements ITeacherDao {
	@Override
	public void teach() {
		System.out.println(" 老师授课中.... ");
	}
	@Override
	public void sayHello(String name) {
		System.out.println("hello " + name);
	}
}

2.代理工厂类

public class ProxyFactory {
    //维护一个目标对象, Object
	private Object obj;
    //构造器
	public ProxyFactory(Object obj) {
		this.obj = obj;
	}
    //给目标对象生成一个代理对象
	public Object getProxyInstance() {
		//说明
		//1. ClassLoader loader : 指定当前目标对象使用的类加载器, 获取加载器的方法固定
		//2. Class<?>[] interfaces:  目标对象实现的接口类型,使用泛型方法确认类型
		//3. InvocationHandler h : 事情处理,执行目标对象的方法时,会触发事情处理器方法, 会把当前执行
        //的目标对象方法作为参数传入
		return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), 
			new InvocationHandler() {
				@Override
				public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
					System.out.println("jdk代理开始了");
					//利用反射机制调用目标对象的方法
					Object returnVal = method.invoke(obj, args);
					System.out.println("jdk代理结束");
					return returnVal;
				}
			});
	}
}

3.测试

public class Client {
	public static void main(String[] args) {
		//创建目标对象
		ITeacherDao target = new TeacherDao();
		
		//给目标对象,创建代理对象, 可以转成 ITeacherDao
		ITeacherDao proxyInstance = (ITeacherDao)new ProxyFactory(target).getProxyInstance();
	
		// proxyInstance=class com.sun.proxy.$Proxy0 内存中动态生成了代理对象
		System.out.println("proxyInstance=" + proxyInstance.getClass());
		
		//通过代理对象,调用目标对象的方法
		proxyInstance.teach();
		proxyInstance.sayHello(" tom ");
	}
}
/**
proxyInstance=class com.sun.proxy.$Proxy0
jdk代理开始了
 老师授课中.... 
jdk代理结束
jdk代理开始了
hello  tom 
jdk代理结束
*/
cglib动态代理

cglib代理,也叫做子类代理。在内存中构建一个子类对象从而实现对目标对象功能的扩展。

对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理(如果方法为final、private则也无法使用cglib)。

需添加cglib的maven依赖(spring中已包含了cglib):

<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>2.2.2</version>
</dependency>
类图

image-20210901152623654

案例代码

1.委托类

public class TeacherDao {
	public String teach() {
		System.out.println(" 老师授课中 (我是cglib代理,不需要实现接口)");
		return "hello";
	}
}

2.代理工厂类

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 ProxyFactory implements MethodInterceptor {

	//维护一个目标对象
	private Object target;
	
	//构造器,传入一个被代理的对象
	public ProxyFactory(Object target) {
		this.target = target;
	}

	//获取代理对象:  是 target 对象的代理对象
	public Object getProxyInstance() {
		//1. 通过Enhancer对象的create()方法可以生成一个类,用于生成代理对象
		Enhancer enhancer = new Enhancer();
		//2. 设置父类(将目标类作为其父类)
		enhancer.setSuperclass(target.getClass());
		//3. 设置回调函数
		enhancer.setCallback(this);
		//4. 创建子类对象(即代理对象)并返回
		return enhancer.create();
		
	}
    
    /**
     * 拦截器
     *  1、目标对象的方法调用
     *  2、增强行为
     */
	@Override
	public Object intercept(Object arg0, Method method, Object[] args, MethodProxy arg3) throws Throwable {
		System.out.println("Cglib代理模式 ~~ 开始");
        // 调用目标对象的方法(返回Object)
		Object returnVal = method.invoke(target, args);
		System.out.println("Cglib代理模式 ~~ 提交");
		return returnVal;
	}

}

3.测试

public class Client {
	public static void main(String[] args) {
		//创建目标对象
		TeacherDao target = new TeacherDao();
		//获取到代理对象,并且将目标对象传递给代理对象
		TeacherDao proxyInstance = (TeacherDao)new ProxyFactory(target).getProxyInstance();

		//执行代理对象的方法,触发intecept 方法,从而实现 对目标对象的调用
		String res = proxyInstance.teach();
		System.out.println("res=" + res);
        
        System.out.println("proxyInstance=" + proxyInstance.getClass());
	}
}
/**
Cglib代理模式 ~~ 开始
 老师授课中 (我是cglib代理,不需要实现接口)
Cglib代理模式 ~~ 提交
res=hello
proxyInstance=class com.hollay.TeacherDao$$EnhancerByCGLIB$$53d58614
*/
jdk代理与cglib代理的区别
  • jdk动态代理基于接口,cglib动态代理基于继承
  • 如果目标对象有接口实现,选择jdk代理;如果没有接口实现,选择cglib代理
  • 目标对象存在接口时,jdk动态代理执行效率高于cglib
  • jdk代理生成的类为 class com.sun.proxy.$Proxy0,cglib代理生成的类为class com.hollay.TeacherDao$$EnhancerByCGLIB$$53d58614
  • JDK动态代理是通过反射机制来实现,CGLib动态代理是通过字节码底层继承要代理类来实现

Spring Aop

AOP底层,是通过动态代理实现的。

spring aop使用哪种代理

Spring AOP支持jdk和cglib两种代理方式。

5.2.12.RELEAS官方文档:

5.3. AOP Proxies

Spring AOP defaults to using standard JDK dynamic proxies for AOP proxies. This enables any interface (or set of interfaces) to be proxied.

Spring AOP can also use CGLIB proxies. This is necessary to proxy classes rather than interfaces. By default, CGLIB is used if a business object does not implement an interface. As it is good practice to program to interfaces rather than classes, business classes normally implement one or more business interfaces. It is possible to force the use of CGLIB, in those (hopefully rare) cases where you need to advise a method that is not declared on an interface or where you need to pass a proxied object to a method as a concrete type.

It is important to grasp the fact that Spring AOP is proxy-based. See Understanding AOP Proxies for a thorough examination of exactly what this implementation detail actually means.

5.8. Proxying Mechanisms

Spring AOP uses either JDK dynamic proxies or CGLIB to create the proxy for a given target object. JDK dynamic proxies are built into the JDK, whereas CGLIB is a common open-source class definition library (repackaged into ).spring-core

If the target object to be proxied implements at least one interface, a JDK dynamic proxy is used. All of the interfaces implemented by the target type are proxied. If the target object does not implement any interfaces, a CGLIB proxy is created.

If you want to force the use of CGLIB proxying (for example, to proxy every method defined for the target object, not only those implemented by its interfaces), you can do so. However, you should consider the following issues:

  • With CGLIB, methods cannot be advised, as they cannot be overridden in runtime-generated subclasses.final
  • As of Spring 4.0, the constructor of your proxied object is NOT called twice anymore, since the CGLIB proxy instance is created through Objenesis. Only if your JVM does not allow for constructor bypassing, you might see double invocations and corresponding debug log entries from Spring’s AOP support.

image-20210902203745404
结论:

  1. proxyTargetClass这个属性默认是false,即jdk代理,改为true则是cglib代理。【目标的代理对象为ture就是和原对象一样的类型,通过cglib代理,基于继承实现。为false就是和原对象不是一个类型,instanceof 判断为false,通过jdk代理,基于接口实现 (jdk动态代理产生的对象属于Proxy类型,属于给定的接口类型,但不属于我们要代理的那个对象的类型)】

  2. 如果要被代理的对象是个实现类,那么Spring会使用jdk动态代理来完成操作(Spirng默认采用jdk动态代理实现机制);如果要被代理的对象不是个实现类,那么Spring会强制使用cglib来实现动态代理。

  3. 我们可以自己强制使用cglib,xml指定proxy-target-class = “true” 或者 基于注解@EnableAspectJAutoProxy(proxyTargetClass = true)

五种通知类型

image-20210822203714084

使用@AfterReturning注解可指定如下两个常用属性。

  1. pointcut/value:这两个属性的作用是一样的,它们都属于指定切入点对应的切入表达式。一样既可以是已有的切入点,也可直接定义切入点表达式。当指定了pointcut属性值后,value属性值将会被覆盖。

  2. returning:该属性指定一个形参名,用于表示Advice方法中可定义与此同名的形参,该形参可用于访问目标方法的返回值。除此之外,在Advice方法中定义该形参(代表目标方法的返回值)时指定的类型,会限制目标方法必须返回指定类型的值或没有返回值。

AOP中获取自定义注解的参数值

package com.hollay.servicebase.annotation;

import java.lang.annotation.*;

/**
 * 自定义注解,用于作标注aop切点位置使用
 */

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface MyUvLogAop {
    String value() default "";
}

 //前台登录   电话和密码登录
 @MyUvLogAop("login")
 @PostMapping("login")
 public R login(@RequestBody Member member){
    //返回token值,使用jwt生成
 	String token = memberService.login(member);
	return R.ok().data("token",token);
 }
package com.hollay.servicebase.aop;

import com.hollay.commonutils.JwtUtils;
import com.hollay.commonutils.R;
import com.hollay.servicebase.annotation.MyUvLogAop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;

import java.util.Map;

/**
 * 关于网站访问量UV统计
 * UV:独立访问用户数:即UniqueVisitor,访问网站的一台电脑客户端为一个访客。00:00-24:00内相同的客户端只被计算一次。
 */

@Aspect
@Component
public class UvAspect {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    // 拿到 @MyUvLogAop 注解注释的方法,这里就是切点
    @Pointcut("@annotation(com.hollay.servicebase.annotation.MyUvLogAop)")
    private void pointCut(){
    }

    @After("pointCut()")
    public void doAfter() {
        System.out.println("*****************aop织入******************");
    }

    /**
     * 在目标方法正常完成后被织入
     * @param joinPoint
     * @param returnVal
     */
    @AfterReturning(returning = "returnVal", pointcut = "pointCut() && @annotation(myUvLogAop)")
    public void doAfterReturning(JoinPoint joinPoint, R returnVal, MyUvLogAop myUvLogAop) {
        String value = myUvLogAop.value();//获取自定义注解 @MyUvLogAop的参数值
        if ("login".equals(value)) {
            countLoginUser(returnVal);
        } else if ("play_video".equals(value)) {
            countPlayVideoNum();
        }
    }
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值