泛型 + 反射 + 注解 + 动态代理 + 设计模式 + Factory(BeanFactory,FactoryBean)

发布于:2024-07-05 ⋅ 阅读:(18) ⋅ 点赞:(0)

1.泛型

编译器可以对泛型参数进行检测,并且通过泛型参数可以指定传入的对象类型。比如 ArrayList<Person> persons = new ArrayList<Person>() 这行代码就指明了该 ArrayList 对象只能传入 Person 对象,如果传入其他类型的对象就会报错。

原生 List 返回类型是 Object ,需要手动转换类型才能使用,使用泛型后编译器自动转换

ArrayList list = new ArrayList();
    list.add("aaa");
System.out.println((String)list.get(0));
ArrayList<String> list = new ArrayList<>();
    list.add("aaa");

https://blog.csdn.net/weixin_45395059/article/details/126006369

泛型一般有三种使用方式:泛型类、泛型接口、泛型方法

泛型类

public class Generic<T>{

    private T key;

    public Generic(T key) {
        this.key = key;
    }

    public T getKey(){
        return key;
    }
}

T 是泛型标识被称作是类型参数,用于指代任何数据类型。泛型标识是任意设置的

泛型类中的静态方法和静态变量不可以使用泛型类所声明的类型参数

  • 泛型类中的类型参数的确定是在创建泛型类对象的时候(例如 ArrayList< Integer >)。
  • 而静态变量和静态方法在类加载时已经初始化,直接使用类名调用;在泛型类的类型参数未确定时,静态成员有可能被调用,因此泛型类的类型参数是不能在静态成员中使用的。

泛型接口

public interface Generator<T> {
    public T method();
}

实现:

class GeneratorImpl<T> implements Generator<T>{
    @Override
    public T method() {
        return null;
    }
}

泛型方法

public static < E > void printArray( E[] inputArray )
   {
         for ( E element : inputArray ){
            System.out.printf( "%s ", element );
         }
         System.out.println();
    }

2.反射

通过反射你可以获取任意一个类的所有属性和方法,你还可以调用这些方法和属性。
加载类,允许以变成 的方式解剖类中的各种成分(成员变量、方法、构造器等)
(1)加载类,获取字节码:class对象
(2)获取类的构造器:constructor对象
(3)获取成员变量:Field
(4)获取类成员方法:Method

(1)获取Class对象
在这里插入图片描述
在这里插入图片描述
True,True,True
c1,c2,c3都是相同的,都是Student类的字节码

(2)获取构造器
在这里插入图片描述

获取构造器的作用:依然是初始化对象返回

(3)获取成员变量
在这里插入图片描述
作用:赋值、取值
在这里插入图片描述
(4)获取成员方法
在这里插入图片描述
触发方法,Object obj 是要传一个对象 。这里可以方法可能会使用创建对象才有的变量,所以必须要有实例对象才能执行方法
在这里插入图片描述

反射做框架

对于任意一个对象,该框架都可以把对象的字段名和对应的值,保存到文件中去。
在这里插入图片描述
用反射设计一个框架来解决

//目标:保存任务一对象的字段和数据到文件中去
public static void saveobject(Object obj){
//1.obj是任务对象,到底有多少字段要保存
	Class c = obj,getClass();
//2.从类中提取它的全部成员变量
	Fielf[] fields = c.getDeclaredFields();
//3.遍历每个成员变量
	for(Field field:fields){
		//4.拿到成员变量的名字
		String name = field.getName;
		//5.拿到成员变量在对象中的数据
		String value = field.get(obj)+"" ;
	}

}

在苍穹外卖中的实例:

Object entity = args[0];//实体对象

//准备赋值的数据
LocalDateTime now = LocalDateTime.now();//时间
Long currentId = BaseContext.getCurrentId();//用户ID

//根据当前不同的操作类型,为对应的属性通过反射来赋值
if (value == OperationType.INSERT) {
    //插入:为4个公共字段赋值
    try {//反射拿到类方法
        Method setCreateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);
        Method setCreateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class);
        Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
        Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
        //通过反射赋值
        setCreateTime.invoke(entity, now);
        setUpdateTime.invoke(entity, now);
        setCreateUser.invoke(entity, currentId);
        setUpdateUser.invoke(entity, currentId);
    } catch (Exception e) {
        e.printStackTrace();
    }

这些框架中也大量使用了动态代理,而动态代理的实现也依赖反射

3.注解

Annotation 其实就是代码里的特殊标记, 这些标记可以在编译, 类加载, 运行时被读取, 并执行相应的处理。
通过使用Annotation, 程序员可以在不改变原有逻辑的情况下, 在源文件中嵌入一些补充信息
代码分析工具、开发工具和部署工具可以通过这些补充信息进行验证或者进行部署

原文链接:https://blog.csdn.net/weixin_52533007/article/details/127487847

  1. 注解本质是一个继承了Annotation 的特殊接口:
  2. @注解(...)其实就是一个实现类对象,实现了该注解以及Annotation接口
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)//之作用在源码阶段
public @interface Override {

}

public interface Override extends Annotation{

}

  1. 注解只有被解析之后才会生效,常见的解析方法有两种:
  • 编译期直接扫描:编译器在编译 Java 代码的时候扫描对应的注解并处理,比如某个方法使用@Override 注解,编译器在编译的时候就会检测当前的方法是否重写了父类对应的方法。
  • 运行期通过反射处理:像框架中自带的注解(比如 Spring 框架的 @Value、@Component)都是通过反射来进行处理的。

自定义注解

在这里插入图片描述
实例:自定义注解
Annotation.AutoFill.java

@Target(ElementType.METHOD)//指定注解只能加载方法上
@Documented
@Retention(RetentionPolicy.RUNTIME)//控制下面的注解一直保留到运行时
public @interface AutoFill {
    //通过枚举-指定当前属性OperationType就两种 Update 和 Insert
    OperationType value();
}

mapper:

 @AutoFill(OperationType.INSERT)//这里本质是在创建注解的实现类对象
 @Insert("insert into category(type, name, sort, status, create_time, update_time, create_user, update_user)" +
            " VALUES" +
            " (#{type}, #{name}, #{sort}, #{status}, #{createTime}, #{updateTime}, #{createUser}, #{updateUser})")
 void insert(Category category);

元注解

在这里插入图片描述

注解示例: 模拟junit程序

对有@MyTest注解的方法执行

public static void mai(String[] args){
	AnnotationTest4 a= new AnnotationTest4();
	//1.得到Class对象
	Class c = AnnotationTest4.class;
	//2.提取全部成员方法
	Method[] methods = c.getDeclareMethods();
	//3.遍历数组中的方法,看否存在@MyTest注解,存在则触发执行
	for(Method method:methods){
		if(method.isAnnotationPresent(MyTest.class)){
			method.invoke(a);
		}
	}
}

注解用来标记某个程序,让其他程序根据注解信息决定怎么去对待它

4.动态代理

使用代理对象来代替对真实对象(real object)的访问,这样就可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能。
代理模式的主要作用是扩展目标对象的功能,比如说在目标对象的某个方法执行前后你可以增加一些自定义的操作。

从 JVM 角度来说,动态代理是在运行时动态生成类字节码,并加载到 JVM 中的
说到动态代理,Spring AOP、RPC 框架应该是两个不得不提的,它们的实现都依赖了动态代理。
(1)来生成一个代理对象

  • loader :类加载器,用于加载代理对象。
  • interfaces : 被代理类实现的一些接口;
  • h : 实现了 InvocationHandler 接口的对象;
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        ......
    }

(2)要实现动态代理的话,还必须需要实现InvocationHandler 来自定义处理逻辑。 当我们的动态代理对象调用一个方法时,这个方法的调用就会被转发到实现InvocationHandler 接口类的 invoke 方法来调用。

public interface InvocationHandler {

    /**
     * 当你使用代理对象调用方法的时候实际会调用到这个方法
     */
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

invoke() 方法有下面三个参数:

  • proxy :动态生成的代理类
  • method : 与代理类对象调用的方法相对应
  • args : 当前 method方法的参数
  • 也就是说:你通过Proxy 类的 newProxyInstance()创建的代理对象在调用方法的时候,实际会调用到实现InvocationHandler 接口的类invoke()方法。 你可以在invoke() 方法中自定义处理逻辑,比如在方法执行前后做什么事情

以下代码是将上述两者结合表示:
在这里插入图片描述
创建 statProxy来代理的BigStar对象
所以3.的return method.invoke是要用调用BigStar的方法

方便进行模版设计

5.工厂

Spring框架在其设计和实现中使用了多种设计模式,这些设计模式有助于提高代码的可重用性、灵活性和可维护性。以下是一些在Spring中常用的设计模式:

  1. 工厂模式:Spring通过BeanFactory和ApplicationContext来创建对象,其中BeanFactory就是简单工厂模式的体现。工厂模式隐藏了对象实例化的复杂性,并提供一个统一的方式来获取对象。
    应用场景:
    ①当需要一个对象时,我们不需要知道该对象所对应的具体类,只要知道哪个具体工厂可以生成该对象,实例化这个具体工厂即可创建该对象。
    ②类的数目不固定,随时有新的子类增加进来,或者是还不知道将来需要实例化哪些具体类。
    ③定义一个创建对象接口,由子类决定要实例化的类是哪一个;客户端可以动态地指定工厂子类创建具体产品。
    客户只需要知道所需产品的具体工厂,而无须知道具体工厂的创建产品的过程,甚至不需要知道具体产品的类名。
//打工人
public abstract class Worker {
}
 
//志愿者
public class Volunteer extends Worker {
}
 
//大学生
class  Undergraduate extends Worker {
}
 
//工厂
public interface IFactory {
	Worker CreateWorker();
}
//志愿者工厂
public class VolunteerFactory implements IFactory {
	@Override
	public Worker CreateWorker() {
		return new Volunteer();
	}
 
}
 
//大学生工厂
class UndergraduateFactory implements IFactory {
	@Override
	public Worker CreateWorker() {
		return new Undergraduate();
	}
 
}
IFactory factory = new UndergraduateFactory();//new对象类型用接口类型
Worker student = factory.CreateWorker();
	
IFactory factory2 = new VolunteerFactory();//new对象类型用接口类型
Worker volunteer = factory2.CreateWorker();	
  1. 单例模式:在Spring中,默认情况下,bean以单例的形式存在。这意味着对于每个bean定义,Spring容器只会创建一个共享的实例。这样做可以减少系统资源的消耗,并减少垃圾回收器的压力。
  2. 代理模式:Spring的面向切面编程(AOP)功能使用到了JDK的动态代理和CGLIB字节码生成技术。这使得可以在不修改源代码的情况下,为对象添加额外的行为。
  3. 策略模式:例如,Resource接口的不同实现类针对不同的资源文件实现了不同的资源获取策略。策略模式允许在运行时选择算法或操作的具体实现。
  4. 模板方法模式:Spring中的JdbcTemplate、RestTemplate等都是模板方法模式的应用。它们提供了一个操作的框架,而具体的实现步骤则可以由子类来提供,从而实现代码复用和减少重复代码。

Spring的IOC和DI用了什么设计模式

Spring 的 IoC(控制反转)和 DI(依赖注入)使用了工厂模式、单例模式和代理模式等设计模式。具体如下:

(1)工厂模式:IoC 容器负责创建对象,当需要创建一个对象时,只需配置好相应的配置文件或注解,无需关心对象的创建过程。IoC 容器就像一个工厂,它根据配置文件或注解来生成和管理对象实例。
(2)单例模式:在 Spring 框架中,默认情况下单例模式,确保每个 Bean 的生命周期内只有一个共享的实例
(3)代理模式:AOP(面向切面编程)使用代理模式来实现。通过代理模式,可以在不修改原始类代码的情况下,为对象提供额外的功能,例如事务管理和日志记录。
综上所述,这些设计模式共同工作,使得 Spring 框架能够提供一个高度解耦、易于扩展和维护的应用程序架构。

(BeanFactory,FactoryBean)

  1. BeanFactory:是所有Spring Bean的容器根接口,给Spring 的容器定义一套规范,给IOC容器提供了一套完整的规范,负责管理Bean的生命周期、配置元数据和依赖注入。

(1)BeanFactory的主要功能包括:

  • Bean的实例化和管理:BeanFactory负责创建、初始化和管理Bean的生命周期。它会根据配置文件中定义的Bean定义来创建Bean的实例。
  • 依赖注入:BeanFactory负责解决Bean之间的依赖关系,确保每个Bean都能获取它所依赖的其他Bean。
  • 配置元数据的管理:BeanFactory会读取和管理应用程序的配置元数据,通常以XML、注解或Java配置的方式定义Bean及其属性。
  • 延迟初始化:BeanFactory支持延迟初始化,即只有在需要时才创建Bean实例。
  • AOP支持:BeanFactory支持面向切面编程(AOP),允许在Bean的生命周期中应用切面。

BeanFactory是Spring IOC容器的基础,但它通常不会直接使用,而是通过其更高级的实现来使用,如ApplicationContext
(2)BeanFactory和ApplicationContext有什么区别?
BeanFactory和ApplicationContext是Spring的两大核心接口,都可以当做Spring的容器。其中ApplicationContext是BeanFactory的子接口。

两者区别如下:

  1. 功能上的区别。BeanFactory是Spring里面最底层的接口,包含了各种Bean的定义,读取bean配置文档,管理bean的加载、实例化,控制bean的生命周期,维护bean之间的依赖关系。
    ApplicationContext接口作为BeanFactory的派生,除了提供BeanFactory所具有的功能外,还提供了更完整的框架功能,如继承MessageSource、支持国际化、统一的资源文件访问方式、同时加载多个配置文件等功能。

  2. 加载方式的区别。BeanFactroy采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化。这样,我们就不能发现一些存在的Spring的配置问题。如果Bean的某一个属性没有注入,BeanFacotry加载后,直至第一次使用调用getBean方法才会抛出异常。

    ApplicationContext是在容器启动时,一次性创建了所有的Bean。这样,在容器启动时,我们就可以发现Spring中存在的配置错误,这样有利于检查所依赖属性是否注入。 ApplicationContext启动后预载入所有的单例Bean,那么在需要的时候,不需要等待创建bean,因为它们已经创建好了。

相对于基本的BeanFactory,ApplicationContext 唯一的不足是占用内存空间。当应用程序配置Bean较多时,程序启动较慢

  1. FactoryBean:是一个bean通常是用来创建比较复杂的bean,一般的bean 直接用xml配置即可,但如果一个bean的创建过程中涉及到很多其他的bean 和复杂的逻辑,直接用xml配置比较麻烦,这时可以考虑用FactoryBean,可以隐藏实例化复杂Bean的具体的细节.

BeanFactory 和 FactoryBean区别
(1)用途:
BeanFactory是Spring IoC容器的核心接口,负责管理Bean的生命周期和依赖注入。
FactoryBean是一个特殊的Bean,充当其他Bean的工厂,用于自定义Bean的创建过程。
(2)创建对象:
BeanFactory负责创建Bean对象
FactoryBean是一个Bean,它的实例本身是一个工厂,负责创建特殊的比较复杂的Bean的对象。
(3)自定义性:
BeanFactory通常不需要自定义实现,而是由Spring框架提供的。
FactoryBean需要自定义实现,您需要编写一个类,实现FactoryBean接口,并重写getObject方法来定义Bean的创建逻辑。
(4)懒加载:
BeanFactory默认支持懒加载,直到被请求时才初始化。
FactoryBean可以通过返回代理对象来实现懒加载,它控制何时创建实际的Bean实例对象。

总结:不是所有的Bean都是由FactoryBean创建的。大多数普通的Bean由BeanFactory(或ApplicationContext)创建,而FactoryBean通常用于创建特殊类型的Bean,或者对Bean的创建过程进行自定义控制。如果您只需要普通Bean,不需要实现FactoryBean接口。


网站公告

今日签到

点亮在社区的每一天
去签到