暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Spring之使用Java的方式配置Spring和proxy代理模式

狗哥学编程 2021-09-27
1557

spring-07-appconfig 使用Java的方式配置Spring

现在完全不适用Spring的xml配置了,全权交给java来做!

JavaConfig是Spring的一个子项目,它通过 Java 类的方式提供 Bean 的定义信息,在Spring4之后,它成为了核心功能!

1、编写实体类:

package com.gwg.pojo;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

//这个注解意思,就是说明这个类被Spring接管了,注册到了容器中。
@Component
public class User {
   private String name;

   public String getName() {
       return name;
  }

   @Value("狗哥")      //属性注入值
   public void setName(String name) {
       this.name = name;
  }

   @Override
   public String toString() {
       return "User{" +
           "name='" + name + '\'' +
           '}';
  }
}

2、、新建一个config配置包,编写一个MyConfig配置类

package com.gwg.config;

import com.gwg.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

//@Configuration 代表一个配置类 相当于beans.xml

@Configuration      //这个也会被Spring容器托管,注册到容器中,因为它本来就是一个@Component
@ComponentScan("com.gwg.pojo")
@Import(MyConfig2.class)
public class MyConfig {

   //注册一个bean,就相当于之前写的一个bean
   //这个方法名就是bean标签中的id属性
   //这个方法的返回值,就相当于bean标签中的class属性
   /*<beans>
   <bean id="getUser" class="com.gwg.pojo.User"/>
</beans>*/
   @Bean
   public User getUser(){
       return new User();  //就是要返回要注入到bean的对象
  }
}

3、测试

public class MyTest {
   public static void main(String[] args) {
       //如果完全使用了配置类方式配置,只能通过AnnotationConfigApplicationContext上下文获取容器,通过配置类的class对象加载!
       ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
       User getUser = (User) context.getBean("getUser");       //就是bean标签中的id属性
       System.out.println(getUser.getName());
  }
}

导入其他配置如何做呢?

1、我们再编写一个配置类!

@Configuration 代表这是一个配置类
public class MyConfig2 {
}

2、在之前的配置类中我们来选择导入这个配置类

@Configuration
@Import(MyConfig2.class)  //导入合并其他配置类,类似于配置文件中的 inculde 标签

关于这种Java类的配置方式,我们在之后的SpringBoot 和 SpringCloud中还会大量看到,我们需要知道这些注解的作用即可!

spring-08-proxy代理模式

为什么要学习代理模式?因为这是SpringAOP的底层!【SpringAOP和SpringMVC】

代理模式的分类:

  • 静态代理

  • 动态代理

10.1、spring-08-proxy---demo01静态代理

角色分析:

  • 抽象角色:一般会使用接口或者抽象类来解决

  • 真实角色,被代理的角色。

  • 代理角色:代理真实角色,代理真实角色后,会做一些附属操作。

  • 客户:访问代理对象的人!

代码步骤:

  1. 接口

    //租房
    public interface Rent {
       public void rent();
    }
  2. 真实角色

    //房东
    public class Host implements Rent {

       @Override
       public void rent() {
           System.out.println("房东要出租房子");
      }
    }
  3. 代理角色

    package com.gwg.demo01;

    //代理
    public class Proxy implements Rent {
       private Host host;

       public Proxy() {
      }

       public Proxy(Host host) {
           this.host = host;
      }

       @Override
       public void rent() {
           seeHouse();
           host.rent();
           heTong();
           fare();
      }

       //看房
       public void seeHouse(){
           System.out.println("中介带你看房");
      }

       //收中介费
       public void fare(){
           System.out.println("收中介费");
      }

       //签合同
       public void heTong(){
           System.out.println("签合同");
      }
    }
  4. 客户端访问代理角色

    public class Client {
       public static void main(String[] args) {
           //房东要租房子
           Host host = new Host();
           //代理,中介帮房东租房子,代理角色会有一些附属操作
           Proxy proxy = new Proxy(host);
           //不用找房东,直接找中介
           proxy.rent();
      }
    }

代理模式的好处:

  • 可以使真实的角色的操作更加纯粹,不用去关注一些公共的业务!

  • 公共业务就交给了代理角色,实现了业务的分工。

  • 公共业务扩展的时候,方便集中管理。

缺点:

  • 一个真实角色就会产生一个代理角色,代码量会翻倍、开发效率会变低。

10.2、spring-08-proxy---demo02 静态代理的深入理解

实际业务

public interface UserService {
   //增删改查
   public void add();
   public void delete();
   public void update();
   public void query();
}

业务实现类

//真实对象
public class UserServiceImpl implements UserService{
   @Override
   public void add() {
       System.out.println("新增了一个用户");
  }

   @Override
   public void delete() {
       System.out.println("删除了一个用户");
  }

   @Override
   public void update() {
       System.out.println("修改了一个用户");
  }

   @Override
   public void query() {
       System.out.println("查询到了一个用户");
  }
}

使用代理模式添加打印日志功能

public class UserServiceProxy implements UserService {

   private UserServiceImpl userService;

   public void setUserService(UserServiceImpl userService) {
       this.userService = userService;
  }

   @Override
   public void add() {
       log("add");
       userService.add();
  }

   @Override
   public void delete() {
       log("delete");
       userService.delete();
  }

   @Override
   public void update() {
       log("update");
       userService.update();
  }

   @Override
   public void query() {
       log("query");
       userService.query();
  }

   //日志方法
   public void log(String msg){
       System.out.println("[DEBUG]使用了"+msg+"方法");
  }
}

客户端

public class Client {
   public static void main(String[] args) {
       UserServiceImpl userService = new UserServiceImpl();

       UserServiceProxy proxy = new UserServiceProxy();
       proxy.setUserService(userService);

       proxy.add();
       proxy.delete();
       proxy.update();
       proxy.query();
  }
}

10.3、动态代理

  • 动态代理和静态代理角色一样(抽象、真实、代理角色)

  • 动态代理类是动态生成的,不是我们直接写的!

  • 动态代理分为两类:基于接口的动态代理,基于类的动态代理

    • 基于接口的动态代理----JDK的动态代理(原生的)【使用这个实现】

    • 基于类:cglib

    • java字节码实现:javassist

需要了解两个类:Proxy:代理、InvocationHandler:调用处理程序

Proxy:

Proxy
提供了创建动态代理类和实例的静态方法,它也是由这些方法创建的所有动态代理类的超类。

为某个接口创建代理Foo :
Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),
new Class<?>[] { Foo.class },handler);

动态代理类 (以下简称为代理类 )是一个实现在类创建时在运行时指定的接口列表的类,具有如下所述的行为。代理接口是由代理类实现的接口。代理实例是代理类的一个实例。每个代理实例都有一个关联的调用处理程序对象,它实现了接口InvocationHandler
 。通过其代理接口之一的代理实例上的方法调用将被分派到实例调用处理程序的invoke
方法,传递代理实例,  java.lang.reflect.Method
被调用方法的java.lang.reflect.Method
对象以及包含参数的类型Object
 Object的数组。调用处理程序适当地处理编码方法调用,并且返回的结果将作为方法在代理实例上调用的结果返回。

  • 代理类具有以下属性:

    • 代理类是公共的,最终的,而不是抽象的,如果所有代理接口都是公共的。

    • 如果任何代理接口是非公开的,代理类是非公开的,最终的,而不是抽象的

    • 代理类的不合格名称未指定。 然而,以字符串"$Proxy"
      开头的类名空间应该保留给代理类。

    • 一个代理类扩展了java.lang.reflect.Proxy

    • 代理类完全按照相同的顺序实现其创建时指定的接口。

    • 如果一个代理类实现一个非公共接口,那么它将被定义在与该接口相同的包中。 否则,代理类的包也是未指定的。 请注意,程序包密封不会阻止在运行时在特定程序包中成功定义代理类,并且类也不会由同一类加载器定义,并且与特定签名者具有相同的包。

    • 由于代理类实现了在其创建时指定的所有接口,  getInterfaces
      在其
      对象上调用getInterfaces
      将返回一个包含相同列表接口的数组(按其创建时指定的顺序),在其
      对象上调用getMethods
      将返回一个数组的方法
      对象,其中包括这些接口中的所有方法,并调用getMethod
      将在代理接口中找到可以预期的方法。

    • Proxy.isProxyClass
      方法将返回true,如果它通过代理类  -  由Proxy.getProxyClass
      返回的类或由Proxy.newProxyInstance
      返回的对象的类  - 否则为false。

    • 所述java.security.ProtectionDomain
      代理类的是相同由引导类装载程序装载系统类,如java.lang.Object
       ,因为是由受信任的系统代码生成代理类的代码。 此保护域通常将被授予java.security.AllPermission

    • 每个代理类有一个公共构造一个参数,该接口的实现InvocationHandler
       ,设置调用处理程序的代理实例。而不必使用反射API来访问公共构造函数,也可以通过调用Proxy.newProxyInstance
      方法来创建代理实例,该方法将调用Proxy.getProxyClass
      的操作与调用处理程序一起调用构造函数。

InvocationHandler:

InvocationHandler
是由代理实例的调用处理程序实现的接口  。

每个代理实例都有一个关联的调用处理程序。 当在代理实例上调用方法时,方法调用将被编码并分派到其调用处理程序的invoke
方法。

invoke(Object proxy, 方法 method, Object[] args)
 处理代理实例上的方法调用并返回结果。

Object invoke(Object proxy, Method method, Object[] args) throws Throwable

处理代理实例上的方法调用并返回结果。 当在与之关联的代理实例上调用方法时,将在调用处理程序中调用此方法。

参数

proxy
- 调用该方法的代理实例

method
-所述方法
对应于调用代理实例上的接口方法的实例
。 方法
对象的声明类将是该方法声明的接口,它可以是代理类继承该方法的代理接口的超级接口。

args
 -包含的方法调用传递代理实例的参数值的对象的阵列,或null
如果接口方法没有参数。 原始类型的参数包含在适当的原始包装器类的实例中,例如java.lang.Integer
java.lang.Boolean
 。

  • 结果

    从代理实例上的方法调用返回的值。 如果接口方法的声明返回类型是原始类型,则此方法返回的值必须是对应的基本包装类的实例;  否则,它必须是可声明返回类型的类型。 如果此方法返回的值是null
    和接口方法的返回类型是基本类型,那么NullPointerException
    将由代理实例的方法调用抛出。 如上所述,如果此方法返回的值,否则不会与接口方法的声明的返回类型兼容,一个ClassCastException
    将代理实例的方法调用将抛出。

  • 异常

    Throwable
    -  从代理实例上的方法调用抛出的异常。 异常类型必须可以分配给接口方法的throws
    子句中声明的任何异常类型java.lang.RuntimeException
    检查的异常类型java.lang.RuntimeException
    java.lang.Error
     。如果检查的异常是由这种方法是不分配给任何的中声明的异常类型throws
    接口方法的子句,则一个UndeclaredThrowableException
    包含有由该方法抛出的异常将通过在方法调用抛出代理实例。

实例:

//租房
public interface Rent {
public void rent();
}
//房东
public class Host implements Rent {

@Override
public void rent() {
System.out.println("房东要出租房子");
}
}

代理类:

//自动生成代理类
public class ProxyInvocationHadler implements InvocationHandler {

被代理的接口
private Rent rent;

public void setRent(Rent rent) {
this.rent = rent;
}

生成得到代理类
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(), rent.getClass().getInterfaces(), this);
}

处理代理实例,返回结果
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

动态代理的本质就是使用反射机制实现
seeHouse();
Object result = method.invoke(rent, args);
fare();
return result;
}

public void seeHouse(){
System.out.println("中介看房子");
}

public void fare(){
System.out.println("收中介费");
}
}

测试:

public class Client {
public static void main(String[] args) {
真实角色
Host host = new Host();

代理角色;;现在没有
ProxyInvocationHadler pih = new ProxyInvocationHadler();
通过调用程序处理角色来处理要调用的接口对象
pih.setRent(host);

Rent proxy = (Rent) pih.getProxy();//proxy就是动态生成的,并没有写
proxy.rent();
}
}

工具类:

//自动生成代理类
public class ProxyInvocationHadler implements InvocationHandler {

被代理的接口
private Object target;

public void setTarget(Object target) {
this.target = target;
}

生成得到代理类
public Object getProxy() {
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
target.getClass().getInterfaces(), this);
}

处理代理实例,返回结果
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
动态代理的本质就是使用反射机制实现
log(method.getName());
Object result = method.invoke(target, args);
return result;
}

public void log(String msg){
System.out.println("执行了"+msg+"方法");
}

}

测试:

public static void main(String[] args) {
//真实角色
UserServiceImpl userService = new UserServiceImpl();
//代理角色,不存在
ProxyInvocationHadler pih = new ProxyInvocationHadler();
pih.setTarget(userService);//设置要代理的对象
//动态生成代理类
UserService proxy = (UserService) pih.getProxy();

proxy.add();
proxy.delete();
proxy.update();
proxy.query();
}

动态代理的好处:

  • 可以使真实的角色的操作更加纯粹,不用去关注一些公共的业务!

  • 公共业务就交给了代理角色,实现了业务的分工。

  • 公共业务扩展的时候,方便集中管理。

  • 一个动态代理类代理的是一个接口,一般就是对应的一类业务。

  • 一个动态代理类可以代理多个类,只要是实现了同一个接口即可。


自学推荐:B站遇见狂神说


文章转载自狗哥学编程,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论