代理模式
在许多项目上为了不修改核心底层代码,又希望可以添加更多功能,就可以让一个类去代理核心类,去给他添加更多功能,这也就是代理模式
在代理模式(Proxy Pattern)中,一个类代表另一个类的功能。这种类型的设计模式属于结构型模式。
在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口。
静态代理模式
- 抽象角色(Image) : 一般使用接口或者抽象类来实现
- 真实角色(Reallmage) : 被代理的角色
- 代理角色(Proxylmage) : 代理真实角色 ; 代理真实角色后 , 添加一些附属的操作 .
- 用户(ProxyPatternDemo) : 使用代理角色来进行一些操作
代码体现:
抽象角色(Image):
public interface Image {
public void commodity1();
public void commodity2();
}
真实角色(Reallmage):
public class Factory implements Image{
public void commodity1() {
System.out.println("商品出售1");
}
public void commodity2() {
System.out.println("商品出售2");
}
}
代理角色(Proxylmage):
public class Shop implements Image {
private Factory factory;
public Shop() { }
public Shop(Factory factory) {
this.factory = factory;
}
//商品出售
public void commodity1(){
wrap();
Factory.commodity1();
}
public void commodity2(){
wrap();
Factory.commodity2();
//包装
public void wrap(){
System.out.println("包装商品");
}
}
客户(ProxyPatternDemo):
public class Client {
public static void main(String[] args) {
Factory factory = new Factory();
Shop shop = new Shop(factory);
shop.commodity1();
shop.commodity2();
}
}
动态代理模式
动态代理中的角色和静态代理一样,只是动态代理中的代理类也就是代理角色(Proxylmage)是动态生成的
动态代理有三类:
- JDK动态代理:基于接口的动态代理
- cglib:基于类的动态代理
- javasist: java字节码生成 (主流)
以JDK动态代理来讲述下动态代理
以下JDK动态代理 简称 动态代理
动态代理需要理解掌握两个类:InvocationHandler类 和 Proxy类
InvocationHandler类(调用处理程序)
InvocationHandler是由代理实例的调用处理程序实现的接口 代理类都必须继承这个接口且实现invoke方法
invoke方法:
Object invoke(Object proxy, Method method, Object[] args);
proxy:调用该方法的代理实例
method:所述方法对应于调用代理实例上的接口方法的实例
args:包含的方法调用传递代理实例的参数值的对象的阵列,或null(例如java.lang.Integer)
Proxy类( )(代理)
Proxy类提供了创建动态代理类和实例 的静态方法
每个代理实例都有一个关联的调用处理程序对象,且实现了InvocationHandler接口
Proxy.newProxyInstance
(this.getClass().getClassLoader(),image.getClass().getInterfaces(),this);
获得该类的类加载器 获得该接口的的接口类 该类
实例
抽象角色(Image):
public interface Image {
public void commodity1();
public void commodity2();
}
真实角色(Reallmage):
public class Factory implements Image{
public void commodity1() {
System.out.println("商品出售1");
}
public void commodity2() {
System.out.println("商品出售2");
}
}
代理角色(Proxylmage) ProxyInvocationHandler:
public class ProxyInvocationHandler implements InvocationHandler {
private Image image;
public void setImage(Image image) {
this.image = image;
}
//生成代理类,重点是第二个参数,获取要代理的抽象角色!静态代理类为代理一个角色,现在可以代理一类角色
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
image.getClass().getInterfaces(),this);
}
// 处理代理实例上的方法调用并返回结果
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//核心:本质利用反射实现!
Object result = method.invoke(image, args);
wrap();
return result;
}
public void wrap(){
System.out.println("包装商品");
}
}
客户(ProxyPatternDemo):
public class Client {
public static void main(String[] args) {
Factory factory = new Factory();
ProxyInvocationHandler shop = new ProxyInvocationHandler();
shop.setImage(factory);
Image proxy = (Image)shop.getProxy();
proxy.commodity1();
proxy.commodity2();
}
}
动态代理通用
我们可以把代理角色中
private Image image; 改为 private Object target
把类中所有的image都由target代替,这样我们这个代理角色就可以代理多种抽象类,而不需要去频繁创建和改动他们
Spring中的AOP实现
spring中的AOP(面向切面编程) 就是依托 代理模式来实现的。
利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
AOP中存在多个名词来帮助理解面向切面编程的原理:
- 横切关注点: 跨越应用程序多个模块的方法或功能。
- 切面(ASPECT): 横切关注点 被模块化 的特殊对象。即,它是一个类。
- 通知(Advice): 切面必须要完成的工作。即,它是类中的一个方法。
- 目标(Target): 被通知对象。
- 代理(Proxy): 向目标对象应用通知之后创建的对象。
- 切入点(PointCut): 切面通知 执行的 “地点”的定义。
- 连接点(JointPoint): 与切入点匹配的执行点。
要想使用AOP的话,需要导入相应的jar包或者maven依赖
maven依赖:
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>
Spring中AOP的实现方式有三种:
- 第一种是通过Spring的API接口来实现
- 第二种是通过自定义类来实现,
- 第三种是通过注解来实现。
抽象类:
public interface Image {
public void commodity1();
public void commodity2();
}
真实角色(Reallmage):
public class Factory implements Image{
public void commodity1() {
System.out.println("商品出售1");
}
public void commodity2() {
System.out.println("商品出售2");
}
}
1:通过Spring的API接口实现
通过Spring的API接口实现的话,得需要了解MethodBeforeAdvice(前置增强)和AfterReturningAdvice(后置增强)这两个接口。他们分别对应着方法实现前和方法实现后的作用
MethodBeforeAdvice(前置增强)类
public class Before implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] objects, Object object) throws Throwable {
//method : 要执行的目标对象的方法
//objects : 被调用的方法的参数
//Object : 目标对象
System.out.println("包装")
System.out.println( object.getClass().getName() );
System.out.println( method.getName())
//通过Spring的API接口和参数我们可以获取类的方法和类别,也能进行操作。
}
}
AfterReturningAdvice(后置增强)类:
public class After implements AfterReturningAdvice {
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
//returnValue 返回值
//method被调用的方法
//args 被调用的方法的对象的参数
//target 被调用的目标对象
System.out.println("结账");
System.out.println(target.getClass().getName());//被调用的类名
System.out.println(method.getName());//被调用的方法名
System.out.println(returnValue);//一般为null
}
}
还要去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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--注册-->
<bean id="factory" class="com.nicht.service.Factory"/>
<bean id="before" class="com.nicht.log.Before"/>
<bean id="after" class="com.nicht.log.After"/>
<!--aop的配置-->
<aop:config>
<!--切入点 expression:表达式匹配要执行的方法-->
<aop:pointcut id="pointcut" expression="execution(* com.nicht.service.Factory.*(..))"/>
<!--其中execution( * * * * *)分别代表 修饰词 返回值 类名 方法名 参数 其中*代表都行 -->
<!--执行环绕; advice-ref执行方法 . pointcut-ref切入点-->
<aop:advisor advice-ref="before" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="after" pointcut-ref="pointcut"/>
</aop:config>
</beans>
客户(ProxyPatternDemo):
public class Test {
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Image image = (Image) context.getBean("factory");
//因为AOP是使用了代理模式,所以代理的还是接口
image.commodity1();
}
}
2:通过自定义类来实现
通过自定义类我们就不需要去实现Spring的API接口了,但自定义类的话,也无法方便的去获取类的反射来对类的属性进行操作
创建自己的一个切入类:
public class test {
public void before(){
System.out.println("包装");
}
public void after(){
System.out.println("结账");
}
}
在bean.xml中配置:
<!--注册-->
<bean id="factory" class="com.nicht.service.Factory"/>
<bean id="test" class="com.nicht.service.test"/>
<!--aop的配置-->
<aop:config>
<!--第二种方式:使用AOP的标签实现-->
<aop:aspect ref="test">
<aop:pointcut id="test1" expression="execution(* com.nicht.service.Factory.*(..))"/>
<aop:before pointcut-ref="test1" method="before"/>
<aop:after pointcut-ref="test1" method="after"/>
</aop:aspect>
</aop:config>
3:通过注解来实现
通过注解的话,我们就无需在xml中进行注册和配置,但灵活性会降低,且之后的维护难度会加大。
使用注解来实现的话,需要了解四个注解
- @Aspect:切面声明,标注在类、接口(包括注解类型)或枚举上。
- @Before :前置通知, 在目标方法(切入点)执行之前执行。
- @After :后置通知, 在目标方法(切入点)执行之后执行
- @Around :环绕通知:目标方法执行前后分别执行一些代码,发生异常的时候执行另外一些代码
创建一个自定义的切入类:
package com.nicht.service;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class AnnotationPointcut {
// After , Before 和 Around中也要添加要进行增强的方法路径
@Before("execution(* com.nicht.service.Factory.*(..))")
public void before(){
System.out.println("包装");
}
@After("execution(* com.nicht.service.Factory.*(..))")
public void after(){
System.out.println("结账");
}
@Around("execution(* com.nicht.service.Factory.*(..))")
//在目标前后的前置和后置增强前在添加代码
public void around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("环绕前");
//执行目标方法proceed,围绕于jp
Object proceed = jp.proceed();
System.out.println("环绕后");
}
}
在xml中设置注解的使用
<bean id="annotationPointcut" class="com.nicht.config.AnnotationPointcut"/>
<aop:aspectj-autoproxy/>