spring理论

spring介绍:

  微信入群一块学习技术:Day9884125
  spring有三个核心概念:控制反转(IOC)、依赖注入(DI)、面向切面编程(AOP)。

控制反转和依赖注入

有两个类A和类B

public class A{
    //工作
    public void work(){
        
    }
}
public class B{
    //醒来方法
    public void wake(){
        
    }
}

  上面两个类中,调用A中工作方法,我们先得调用B类中得醒来方法。代码如下

public class A{
    private B b;
    
    //工作
    public void work(){
        b.wake();
    }
}

总结

1、A类中声明了一个B类型的属性b
2、A类中的work方法调用了B类中的wake方法,完成work的业务。

依赖关系

  当a对象完成某些操作时候,需要调用B中的某个方法来实现,说明a依赖于b,a和b是依赖关系。

spring容器

  spring容器概念,容器可以放很多东西,当程序启动的时候会创建spring容器。spring容器会创建出需要创建的对象及对象依赖关系,将创建好的放到容器中,程序需要使用的时候,可以直接到容器中查找获取,然后直接使用。

控制反转(IOC)

  使用者之前使用B对象的时候都需要自己去创建和组装,而现在这些创建和组装都是由spring容器去完成。对象构建的过程被反转了,所以称为控制反转。

依赖注入(DI)

  容器动态的将某个依赖关系注入到组件之中

bean概念

  由spring容器管理的对象统称为Bean对象。Bean就是普通的java对象。和我们自己new出来的是一样的。只是这些对象是由spring去创建和管理的。

spring容器对象

  spring内部提供了很多表示spring容器的接口和对象,下面是常见的接口和具体实现类。

BeanFactory接口

org.springframework.beans.factory.BeanFactory

  他是spring容器的顶层接口,提供了容器最基本的功能

BeanFactory常用的方法
//按bean的id或者别名查找容器中的
bean Object getBean(String name) throws BeansException 
//这个是一个泛型方法,按照bean的id或者别名查找指定类型的bean,返回指定类型的bean对象 
<T> T getBean(String name, Class<T> requiredType) throws BeansException; 
//返回容器中指定类型的bean对象 
<T> T getBean(Class<T> requiredType) throws BeansException; 
//获取指定类型bean对象的获取器,这个方法比较特别,以后会专门来讲
<T> ObjectProvider<T> getBeanProvider(Class<T> requiredType);

ApplicationContext接口

org.springframework.context.ApplicationContext

  ApplicationContext继承了BeanFactory接口,所以它包含了BeanFactory所有的功能,并且在它上面进行了扩展。

bean xml配置文件格式

  bean xml文件用来定义spring容器需要管理的bean,常见格式如下代码:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd">

<import resource="引入其他bean xml配置文件" />
<bean id="bean标识" class="完整类型名称"/>
<alias name="bean标识" alias="别名" />
</beans>

  beans是根元素,下面可以包含任意数量的import、bean、alias元素。

bean元素

  用来定义一个Bean对象

格式
<bean id="bean唯一标识" name="bean名称" class="完整类型名称" factory-bean="工厂bean名称" factory-method="工厂方法"/>
bean名称

  每个bean都有一个名称,叫bean名称,bean名称在一个spring容器中必须唯一,否则会报错,通过bean名称可以从spring容器中获取对应的bean对象。

bean名称别名定义规则

  名称和别名可以通过bean元素中的id和name来定义,具体定义规则如下:

  1. 当id存在的时候,不管name有没有,取id为bean的名称
  2. 当id不存在,此时需要看name,name的值可以通过 ,;或者空格 分割,最后会按照分隔符得到一个String数组,数组的第一个元素作为bean的名称,其他的作为bean的别名
  3. 当id和name都存在的时候,id为bean名称,name用来定义多个别名
  4. 当id和name都不指定的时候,bean名称自动生成,生成规则下面详细说明

id和name都未指定

  当id和name都未指定的时候,bean的名称和别名又是什么呢?此时由spring自动生成,bean名称为:

bean的class的完整类名#编号

上面的编号是从0开始的,同种类型的没有指定名称的依次递增。

alias元素

<alias name="需要定义别名的bean" alias="别名" />

具体代码如下:

<bean id="user6" class="com.javacode.lesson.demo2.UserModel" /> 
<alias name="user6" alias="user61" />
<alias name="user6" alias="user62" />

  第一行代码通过bean元素定义了一个名称为user6的UserModel对象,后面两行给user6这个bean定义了2个别名,分别是user61和user62

import元素

  当我们系统比较复杂时候,会分成很多模块,每个模块会对应一个bean xml文件,我们可以在一个总的bean xml中对其他bean xml进行汇总,相当于把多个bean xml内容合并到一个里面,可以通过import元素引入其他bean配置文件。

语法

<import resource="其他配置文件的位置" />

如下代码:

<?xml version="1.0" encoding="UTF-8"?> 
<beans xmlns="http://www.springframework.org/schema/beans" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xsi:schemaLocation="http://www.springframework.org/schema/beans 
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd"> 
    <import resource="user.xml" /> 
    <import resource="order.xml" /> 
</beans>

容器创建bean实例有多少种

spring容器内部创建bean常见的方式:

1、通过反射调用构造方法创建bean对象

2、通过静态工厂方法创建bean对象

3、通过实例工厂方法创建bean对象

4、通过FactoryBean创建bean对象

通过反射调用构造方法创建bean对象

  调用类的构造方法获取对应的bean实例,是使用最多的方式,这种方式只需要在xml bean元素中指定class属性,spring容器就会自动调用该类型的构造方法来创建bean对象。

语法:
<bean id="bean名称" name="bean名称或者别名" class="bean的完整类型名称"> 
    <constructor-arg index="0" value="bean的值" ref="引用的bean名称" /> 
    <constructor-arg index="1" value="bean的值" ref="引用的bean名称" /> 
    <constructor-arg index="2" value="bean的值" ref="引用的bean名称" /> 
    .... 
    <constructor-arg index="n" value="bean的值" ref="引用的bean名称" /> 
</bean>

constructor-arg用于指定构造方法参数的值

index:构造方法中参数的位置,从0开始,依次递增

value:指定参数的值

ref:当插入的值为容器内其他bean的时候,这个值为容器中对应bean的名称

代码示例

UserBO类

@Getter
@Setter
@ToString
public class UserBO{
    private String name;
    private int high;
    
    public UserBO(){
        this.name = "我是通过UserBO的无参构造方法创建的。";
    }
    
    public UserBO(String name, String high){
        this.name = name;
        this.high = high;
    }
}

beans.xml代码:

<!-- 通过UserBO的默认构造方法创建UserBO对象 -->
<bean id="createBeanByConstructor" class="com.duay.manage.bo.UserBO" />

<!-- 通过UserBO有参构造方法创建UserBO对象 -->
<bean id="createBeanByHaveParamConstructor" class="com.duay.manage.bo.UserBO" >
    <constructor-arg index="0" value="我是通过UserBO的有参构造方法的对象创建的" />
    <constructor-arg index="1" value="180" />
</bean>

  bean xml中的两个bean。spring容器在创建这两个UserBO的时候,都会通过反射的方式去调用UserBO类中对应的构造函数来创建UserBO对象。

测试代码

public class Test{
    public static void main(String[] args){
        //bean配置文件位置
        String beanXml = "classpath:/com.duay.manage.beans.xml";
        //创建ClassPathXmlApplicationContext容器,给容器指定需要加载的bean配置文件
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(beanXml);
        //getBeanDefinitionNames用于获取容器中所有bean的名称
        for(String beanName : context.getBeanDefinitionNames()){
            System.out.println(beanName+":"+context.getBean(beanName));
        }
    }
}

通过静态工厂方法创建bean对象

  通过创建静态工厂,内部提供一些静态方法来生成所需要的对象。

语法

<bean id="bean名称" name="" class="静态工厂完整类名" factory-method="静态工厂的方法">
    <constructor-arg index="0" value="bean的值" ref="引用的bean名称" />
    <constructor-arg index="1" value="bean的值" ref="引用的bean名称" />
    <constructor-arg index="2" value="bean的值" ref="引用的bean名称" />
    ....
    <constructor-arg index="n" value="bean的值" ref="引用的bean名称" />
</bean>

class:指定静态工厂完整的类名

factory-method:静态工厂中的静态方法,返回需要的对象

constructor-arg:用于指定静态方法参数的值,用法和上面介绍的构造方法一样

  spring容器会自动调用静态工厂的静态方法获取指定的对象,将其放在容器中以供使用。

代码示例:

定义一个静态工厂类,用于生成UserBO对象

public class UserFactory{
    //静态无参方法创建UserBO
    public static UserBO buildUser(){
        System.out.println(UserFactory.class + ".buildUser()");
        
        UserBO userBO = new UserBO();
        userBO.setName("我是无参静态构造方法创建的。");
        return userBO;
    }
    
    //静态有参方法创建UserBO
    public static UserBO haveParamBuilderUser(String name, int high){
        System.out.println(UserFactory.class + ".haveParamBuilderUser()");
        
        UserBO userBO = new UserBO();
        userBO.setName(name);
        userBO.setHigh(high);
        return userBO;
    }
}

bean.xml代码

<!-- 通过工厂静态无参方法创建bean对象 --> 
<bean id="createBeanByFactoryMethod" class="com.duay.manage.UserFactory" factory-method="buildUser"/> 

<!-- 通过工厂静态有参方法创建bean对象 --> 
<bean id="createBeanByHaveParamFactoryMethod" class="com.duay.manage.UserFactory" factory-method="haveParamBuilderUser"> 
    <constructor-arg index="0" value="通过工厂静态有参方法创建UerModel实例对象"/> 
    <constructor-arg index="1" value="180"/> 
</bean>

  以上xml代码中,spring容器启动的时候会自动调用UserFactory中的静态方法buildUser获取UserBO对象,将其作为createBeanByFactoryMethod名称对应的bean对象放在spring容器中。

  第二个bean会调用UserFactory的haveParamBuilderUser方法,并且会传入2个指定的参数,得到返回的UserBO对象,将其作为createBeanByHaveParamFactoryMethod名称对应的bean对象放在spring容器中。

通过实例工厂方法创建bean对象

  让spring容器去调用某些对象的某些实例方法来生成bean对象放在容器中以供使用。

语法

<bean id="bean名称" factory-bean="需要调用的实例对象bean名称" factory-method="bean对象 中的方法"> 
    <constructor-arg index="0" value="bean的值" ref="引用的bean名称" /> 
    <constructor-arg index="1" value="bean的值" ref="引用的bean名称" /> 
    <constructor-arg index="2" value="bean的值" ref="引用的bean名称" /> 
    .... 
    <constructor-arg index="n" value="bean的值" ref="引用的bean名称" /> 
</bean>

  spring容器以factory-bean的值为bean名称查找对应的bean对象,然后调用该对象中factory-method属性值指定的方法,将这个方法返回的对象作为当前bean对象放在容器中供使用。

代码示例

定义一个实例工厂
内部写两个方法用来创建UserBO对象。

public class UserFactory{
    public UserBO buildUser1{
        UserBO ub = new UserBO();
        ub.setName(bean实例方法创建对象);
        return ub;
    }
    
    public UserBO buildUser2(String name, int high){
        UserBO user = new UserBO();
        user.setName(name);
        user.setAge(age);
        return user;
    }
}

bean.xml代码

<!-- 定义一个工厂实例 --> 
<bean id="userFactory" class="com.duay.manage.factory.UserFactory"/> 

<!-- 通过userFactory实例的无参user方法创建UserModel对象 --> 
<bean id="createBeanByBeanMethod1" factory-bean="userFactory" factory- method="buildUser1"/> 

<!-- 通过userFactory实例的有参user方法创建UserModel对象 --> 
<bean id="createBeanByBeanMethod2" factory-bean="userFactory" factory- method="buildUser2">
    <constructor-arg index="0" value="通过bean实例有参方法创建UserModel实例对象"/> 
    <constructor-arg index="1" value="180"/> 
</bean>

  createBeanByBeanMethod1对应的bean是通过userFactory的buildUser1方法生成的。

  createBeanByBeanMethod2对应的bean是通过userFactory的buildUser2方法生成的。

通过FactoryBean来创建bean对象

  BeanFactory是spring容器的顶层接口。FactoryBean也是一个接口,它可以让容器通过这个的实现来创建我们需要的bean对象。

FactoryBean接口源码

public interface FactoryBean<T> { 
    /*** 返回创建好的对象 */ 
    @Nullable 
    T getObject() throws Exception; 
    /*** 返回需要创建的对象的类型 */ 
    @Nullable 
    Class<?> getObjectType(); 
    /*** bean是否是单例的 */ 
    default boolean isSingleton() { return true; } 
}

语法

<bean id="bean名称" class="FactoryBean接口实现类"/>
代码示例:

创建一个FactoryBean实现类

public class UserFactoryBean implements FactoryBean<UserBO>{
    int count = 1;
    @Nullable
    @Override
    //返回一个创建好的UserBO对象
    public UserBO getObject throws Exception{
        UserBO ub = new UserBO();
        ub.setName("我是通过FactoryBean创建的第"+count++ +"对象");
        return ub;
    }
    
    @Nullable
    @Override
    //返回对象的class对象
    public Class<?> getObjectType(){
        return UserBO.class;
    }
    
    @Override
    //返回true,表示创建的对象是单例的,我们每次从容器中获取的对象都是同一个
    public boolean isSingleton(){
        return true;
    }
}

bean xml代码

<!-- 通过FactoryBean 创建UserModel对象 -->
<bean id="createByFactoryBean"class="com.duay.manage.factory.UserFactoryBean"/>

测试代码:

public class Test{
    public static void main(String[] args){
        //bean配置文件位置
        String beanxml = "classpath:/com/duay.manage/beans.xml";
        //创建ClassPathXmlApplicationContext容器,给容器指定需要加载的bean配置文件
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(beanxml);
        
        //getBeanDefinitionNames用于获取容器中所有bean的名称
        for(String beanName : context.getBeanDefinitionNames()){
            System.out.println(beanName+":"+context.getBean(beanName));
        }
        
        //多次获取createByFactoryBean看看是否是同一个对象
        System.out.println("createByFactoryBean"+context.getBean("createByFactoryBean"));
        System.out.println("createByFactoryBean"+context.getBean("createByFactoryBean"));
    }
}

  最后两个输出语句输出的内容是一样的,说明返回的都是同一个UserBO对象。

将UserFactoryBean中的isSingleton调整一下,返回false

@Override
public boolean isSingleton(){
    return false;
}

  当以上方法返回false的时候,表示由这个FactoryBean创建的对象是多例的,我们每次从容器中getBean的时候都会去重新调用FactoryBean中的getObject方法获取一个新的对象。

bean初始化

bean初始化的两种方式

1、实时初始化

2、延迟初始化

bean实时初始化

  在容器启动过程中被创建组装好的bean,称为实时初始化的bean,spring中默认定义的bean都是实时初始化的bean,这些bean默认都是单例的。

实时初始化bean的优点

1、更早发现bean定义的错误:在容器启动的时候就会发现问题
2、查找bean更快:因为容器启动完毕之后,创建好的bean都被放在缓存中,当我们使用的时候,直接返回就可以了。

示例代码:

RealTimeBean类

//实时初始化的bean
public class RealTimeBean{
    public RealTimeBean(){
        System.out.println("我是实时初始化的bean。");
    }
}

构造函数中输出一段话,这段话在spring容器创建过程中输出。

RealTimeBean.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd">

    <bean id="RealTimeBean"class="com.duay.manage.bo.RealTimeBean"/>
</beans>

test代码

//bean默认是实时初始化的,可以通过bean元素的lazy-init="true",将bean定义为延迟初始化
public class LazyBeanTest{
    public static void main(String[] args){
        System.out.println("spring容器启动中");
        String beanxml = "classpath:/com.duay.manage.RealTimeBean";
        //启动spring容器
        new ClassPathXmlApplicationContext(beanXml);
        System.out.println("spring容器启动完毕");
    }
}

延迟初始化的bean

  实时初始化bean都会在容器启动过程中创建好,如果程序中定义的bean非常多,并且有些bean创建的过程非常耗时,会导致系统消耗的资源比较多。使用spring开发的系统比较大的时候,整个系统启动耗时是比较长的,基本上多数都是在创建和组装bean。

spring对上面的问题提供的解决方案:bean延迟初始化

  延迟初始化,在容器启动的过程中不会创建,而是需要使用的时候才会去创建。

bean什么时候会被使用

1、被其他bean作为依赖进行注入的时候

2、开发者自己写代码向容器中查找bean的时候。如调用容器的getBean方法获取bean

上面这两种会导致延迟初始化的bean被创建

延迟bean的配置

  在bean定义的时候通过lazy-init属性来配置bean是否是延迟加载,true:延迟初始化,false:实时初始化

<bean lazy-init="是否是延迟初始化" />
示例代码

LazyInitBean类

public class LazyInitBean{
    public LazyInitBean(){
        System.out.println("我是延迟初始化的bean");
    }
}

LazyInitBean.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd">

    <bean id="lazyInitBean" class="com.duay.manage.LazyInitBean" lazy-init="true"/>
</beans>

其中的lazy-init="true"表示定义的这个bean是延迟初始化的bean

测试

public class Test{
    @Test
    public void lazyInitBean(){
        System.out.println("spring容器启动中");
        String beanXml = "classpath:/com/duay/manage.LazyInitBean.xml";
        //启动spring容器
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(beanXml);
        System.out.println("spring容器启动完毕");
        System.out.println("从容器中开始查找lazyInitBean");
        LazyInitBean lazy = context.getBean(LazyInitBean.class);
        System.out.println("LazyInitBean:"+lazyInitBean);
    }
}

  从以上代码中可以看出,LazyInitBean在容器启动过程中并没有创建,当我们调用context.getBean方法的时候,LazyInitBean才被创建的。

总结

   延迟初始化的bean无法在程序启动过程中迅速发现bean定义的问题,第一次获取的时候可能耗时会比较长。在实际工作中很少用。

java动态代理(jdk动态代理)&CGLIB代理

代理用的比较多,例如aop、spring中的事务

代理的用处

有一个接口IService,如下:

public interface IService{
    void method1();
    void method2();
    void method3();
}

接口有两个实现类ServiceA和ServiceB,如下:

public class ServiceA implements IService{
    @Override
    public void method1(){
        System.out.println("serviceA中的method1方法");
    }
    
    @Override
    public void method2(){
        System.out.println("serviceA中的method2方法");
    }
    
    @Override
    public void method3(){
        System.out.println("serviceA中的method3方法");
    }
}

public class ServiceB implements IService{
    @Override
    public void method1(){
        System.out.println("serviceB中的method1方法");
    }
    
    @Override
    public void method2(){
        System.out.println("serviceB中的method2方法");
    }
    
    @Override
    public void method3(){
        System.out.println("serviceB中的method3方法");
    }
}

Test类

public class ProxyTest{
    @Test
    public void method1(){
        IService serviceA = new ServiceA();
        IService serviceB = new ServiceB();
        serviceA.method1();
        serviceA.method2();
        serviceA.method3();
        
        serviceB.method1();
        serviceB.method2();
        serviceB.method3();
    }
}

  当调用IService接口中的任何方法的时候,需要记录方法的耗时。

  比较好的方式:可以为IService接口创建一个代理类,通过这个代理类来间接访问IService接口的实现类,在这个代理类中去做耗时及发送至监控的代码,代码如下:

//IService的代理类
public class ServiceProxy implements IService{
    //目标对象,被代理的对象
    private IService target;
    
    public ServiceProxy(IService target){
        this.target = target;
    }
    
    @Override
    public void method1(){
        long starTime = System.nanoTime();
        this.target.method1();
        long endTime = System.nanoTime();
        System.out.println(this.target.getClass()+".method1()方法耗时(纳秒):"+(endTime - starTime));
    }
    
    @Override
    public void method2(){
        long starTime = System.nanoTime();
        this.target.method2();
        long endTime = System.nanoTime();
        System.out.println(this.target.getClass()+".method1()方法耗时(纳秒):"+(endTime - starTime));
    }
    
    @Override
    public void method3(){
        long starTime = System.nanoTime();
        this.target.method3();
        long endTime = System.nanoTime();
        System.out.println(this.target.getClass()+".method1()方法耗时(纳秒):"+(endTime - starTime));
    }   
}

  ServiceProxy是IService接口的代理类,target为被代理的对象,即实际需要访问的对象,也实现了IService接口。当我们需要访问IService的其他实现类的时候,可以通过ServiceProxy来间接的进行访问,用法如下代码:

public class Test{
    @Test
    public void servieProxy(){
        IService serviceA = new ServiceProxy(new ServiceA());
        IService serviceB = new ServiceProxy(new ServiceB());
        
        serviceA.method1();
        serviceA.method2();
        serviceA.method3();
    
        serviceB.method1();
        serviceB.method2();
        serviceB.method3();
    }
}

  上面第一行和第二行代码,创建的是代理对象ServiceProxy,ServiceProxy构造方法中传入了被代理访问的对象,现在我们访问ServiceA和ServiceB,都需要经过ServiceProxy。

  上面的实现中我们没有去修改ServiceA和ServiceB中的方法,只是给IService接口创建了一个代理类,通过代理类去访问目标对象,需要添加的一些共有的功能都放在代理中,当有其他需求的时候,我们只需要修改ServiceProxy的代码,方便系统的扩展和测试。

通用代理的方式

1、jdk动态代理

2、cglib代理

jdk动态代理介绍

jdk中为实现代理提供了两个类:

java.lang.reflect.Proxy
java.lang.reflect.InvocationHandler

  jdk自带的代理使用上有要求限制,只能为接口创建代理类,如果需要给具体的类创建代理类,需要用cglib

java.lang.reflect.Proxy

  这是jdk动态代理主要的一个类

getProxyClass方法

为指定的接口创建代理类,返回代理类的Class对象

public static class<?> getProxyClass(ClassLoader loader, Class<?> ... interfaces)

注意

loader:定义代理类的类加载器

interfaces:指定需要实现的接口列表,创建的代理默认会按顺序实现interfaces指定的接口。

newProxyInstance方法

  创建代理类的实例对象

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)

  这个方法先为指定的接口创建代理类,然后会生成代理类的一个实例,最后一个参数是InvocationHandler类型,它是一个接口代码如下。

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

  上面方法会返回一个代理对象,当调用代理对象的任何方法的时候,会被InvocationHandler接口的invoke方法处理,所以主要代码需要写在invoke方法中

isProxy方法

判断指定的类是否是一个代理类

public static boolean isProxyClass(Class<?> c1)

getInvocationHandler方法

获取代理对象的InvocationHandler方法

public static InvocationHandler getInvocationHandler(Object proxy)
创建代理:方式一

步骤

1、调用Proxy.getProxyClass方法获取代理类的Class对象

2、使用InvocationHandler接口创建代理类的处理器

3、通过代理类和InvocationHandler创建代理对象

4、上面已经创建好代理对象了,我们就可以使用代理对象了

代码示例

接口IService

public interface IService{
    void method1();
    void method2();
    void method3();
}

创建IService接口的代理对象

public class Test{
    @Test
    public void method1() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException{
        //获取接口对应的代理类
        Class<IService> proxyClass = (Class<IService>)Proxy.getProxyClass(IService.class.getClassLoader(), IService.class);
        //创建代理类的处理器
        InvocationHandler invocationHandler = new InvocationHandler(){
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
                System.out.println("我是InvocationHandler,被调用的方法是:"+ method.getName());
                return null;
            }
        };
        //创建代理实例
        IService proxyService = proxyClass.getConstructor(InvocationHandler.class).newInstance(invocationHandler);
        //调用代理的方法
        proxyService.method1();
        proxyService.method2();
        proxyService.method3();
    }
}
创建代理:方式二

步骤

1、使用InvocationHandler接口创建代理类的处理器

2、使用Proxy类的静态方法newProxyInstance直接创建代理对象

3、使用代理对象

代码示例
public class Test{
    @Test
    public void method1() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException{
        //获取接口对应的代理类
        Class<IService> proxyClass = (Class<IService>)Proxy.getProxyClass(IService.class.getClassLoader(), IService.class);
        //创建代理类的处理器
        InvocationHandler invocationHandler = new InvocationHandler(){
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
                System.out.println("我是InvocationHandler,被调用的方法是:"+ method.getName());
                return null;
            }
        };
        //创建代理实例
        IService proxyService = proxyClass.getConstructor(InvocationHandler.class).newInstance(invocationHandler);
        //调用代理的方法
        proxyService.method1();
        proxyService.method2();
        proxyService.method3();
    }
}

任意接口中的方法耗时统计

基于jdk动态代理实现的通用的代理,解决统计所有接口方法耗时的问题

package proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * 统计所有接口的耗时
 * 使用jdk动态代理
 * 主要的代码再处理器InvocationHandler上面实现
 */
public class CountAllInterfacesConsumProxy implements InvocationHandler {
    //需要对统计耗时的接口
    private Object needProxyInterface;

    public CountAllInterfacesConsumProxy(Object needProxyInterface){
        this.needProxyInterface = needProxyInterface;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        long starTime = System.nanoTime();
        Object result = method.invoke(this.needProxyInterface, args);
        long endTime = System.nanoTime();
        System.out.println(this.needProxyInterface.getClass().getClass()+".m1()方法耗时(纳秒)"+(endTime - starTime));
        return result;
    }

    /**
     * 用来创建targetInterface接口的代理对象
     * @param target               需要被代理的对象,需要实现targetInterface接口
     * @param targetInterface      被代理的接口
     * @param <T>
     * @return
     * invoke方法中通过method.invoke(this.target, args)调用目标方法,然后统计方法的耗时
     */
    public static <T> T createProxy(Object target, Class<T> targetInterface){
        if (!targetInterface.isInterface()){
            throw new IllegalStateException("targetInterface必须是接口类型");
        }else if(!targetInterface.isAssignableFrom(target.getClass())){
            throw new IllegalStateException("target必须是targetInterface接口的实现类");
        }
        return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(),
        target.getClass().getInterfaces(), new CountAllInterfacesConsumProxy(target));
    }
}

//测试类
package proxy;
public class TestProxy {
    public static void main(String[] args) {
        TestProxy tp = new TestProxy();
        tp.CountCostTimeProxy();
    }

    public void CountCostTimeProxy(){
        IService serviceA = CountAllInterfacesConsumProxy.createProxy(new ServiceA(), IService.class);
        IService serviceB = CountAllInterfacesConsumProxy.createProxy(new ServiceB(), IService.class);

        serviceA.method1();
        serviceA.method2();
        serviceA.method3();

        serviceB.method1();
        serviceB.method2();
        serviceB.method3();
    }
}

当其他的接口,也需要统计耗时的功能,此时无需再去创建新的代理类即可实现同样的功能。如下代码:

UserService接口

package proxy;
public interface UserService {
    /** 插入用户信息 */
    void insert(String name);
}

UserService接口实现类

package proxy;

public class UserServiceImpl implements UserService{

    @Override
    public void insert(String name) {
        System.out.println(String.format("用户[name:%s]插入成功!", name));
    }
}

测试类

package proxy;
public class TestOtherProxy {
    public static void main(String[] args) {
        TestOtherProxy top = new TestOtherProxy();
        top.userService();
    }

    public void userService(){
        //第一个参数是实现类,第二个参数是接口
        UserService userService = CountAllInterfacesConsumProxy.createProxy(new UserServiceImpl(), UserService.class);
        userService.insert("java代理类验证");
    }
}

  当我们有一个新的接口的时候,不需要我们再去创建一个代理类了,只需要CountAllInterfacesConsumProxy.createProxy创建一个新的代理对象就可以了。

Proxy使用注意事项

1、jdk中的Proxy只能为接口生成代理类,如果想要给某个类创建代理类,Proxy是无法做到的,只能使用cglib了

2、Proxy类中提供的几个常用静态方法需要掌握

3、通过Proxy创建代理对象,当调用代理对象任意方法的时候,会被InvocationHandler接口中的invoke方法进行处理,它很重要。

cglib代理介绍

  jdk动态代理只能为接口创建代理,使用上有局限性。实际的场景中我们的类不一定有借口,此时需要普通类也实现代理功能,就需要用cglib了。

  cglib本质上它是通过动态的生成一个子类去覆盖所要代理的类(不能是final修饰的类和方法)。Enhancer是cglib最长用的一个类,他可以代理普通类,也可以代理接口。

cglib是开源项目

https://github.com/cglib/cglib

cglib常见用法示例

1. 拦截所有的方法
public class Demo{
    public void method1(){
        System.out.println("Demo的method1方法");
    }
    
    public void method2(){
        System.out.println("Demo的method2方法");
    }
}

创建Demo的代理,实现打印每个方法的调用日志

public class CglibTest{
    public static void main(String[] args) {
        CglibTest cg = new CglibTets();
        cg.test();
    }

    public void test(){
        //使用Enhancer来给某个类创建代理类,步骤
        //创建Enhancer对象
        Enhancer enhancer = new Enhancer();
        //通过setSuperclass来设置父类型,即需要给哪个类创建代理类
        enhancer.setSuperclass(Demo.class);
        /**
         *设置回调,需实现org.springframework.cglib.proxy.Callback接口,
         *此处使用的是org.springframework.cglib.proxy.MethodInterceptor,
         *也是一个接口,实现了Callback接口,
         *当调用代理对象的任何方法的时候,都会被MethodInterceptor接口的invoke方法处理
         */
         enhancer.setCallback(new MethodInterceptor(){
             /**
              *代理对象方法拦截器
              *@param o  代理对象
              *@param method  被代理的类的方法,即Demo中的方法
              *@param objects  调用方法传递的参数
              *@param methodProxy  方法代理对象
              *@return
              *@throws Throwable
              */
              @Override
              public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable{
              System.out.println("调用方法:"+method);
              //可以调用MethodProxy的invokeSuper调用被代理类的方法
              Object result = methodProxy.invokeSuper(o, objects);
              return result;
              }
         });
         //获取代理对象,调用enhancer.create方法获取代理对象,这个方法返回的是Object类型的,所以需要强转一下
         Demo proxy = (Demo) enhancer.create();
         //调用代理对象的方法
         proxy.method1();
         proxy.menthod2();
    }
}

  enhancer.setSuperclass用来设置代理类,就是需要给哪个类创建代理类。

  enhancer.setCallback传递的是MethodInterceptor接口类型的参数,MethodInterceptor接口有个intercept方法,这个方法会拦截代理对象所有的方法调用。

  Object result = methodProxy.invokeSuper(o, objects);可以调用被代理类,也就是Demo类中具体的方法,它调用的是父类,实际对某个类创建代理,cglib底层通过修改字节码的方式为Demo类创建一个子类。

2. 拦截所有方法

创建一个类

public class Demo1{
    publlic void method1(){
        System.out.println("我是Demo1的method1方法");
        this.method2();
    }
    
    publlic void method2(){
        System.out.println("我是Demo1的method2方法");
    }
}

这个类的method1方法中调用了method2方法。下面对Demo1创建代理。

public class Test{
    public static void main(String[] args){
        test2();
    }
    
    public static void test2(){
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Demo1.class);
        enhancer.setCallback(new MethodInterceptor(){
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable{
            System.out.println("调用方法:"+method);
            Object result = methodProxy.invokeSuper(o, objects);
            return result;
            }
        });
        Demo1 proxy = (Demo1) enhancer.create();
        proxy.method1();
    }
}
3. 拦截所有方法并返回固定值

当调用某个类的任何方法,都希望返回一个固定的值。需要使用FixedValue接口。

enhancer.setCallback(new FixedValue(){
    @Override
    public Object loadObject() throws Exception{
        return "鲁班一号";
    }
});

上面创建的代理对象,调用其任意方法返回的都是"鲁班一号"。

代码示例

创建一个类

public class Demo2{
    public String method1(){
        System.out.println("Demo2的method1的方法");
        return "method1";
    }
    
    public String method2(){
        System.out.println("Demo2的method2的方法");
        return "method2";
    }
}

示例代码

public class Test(){
    public static void main(String[] args){
        test3();
    }
    
    public static void test3(){
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Demo2.class);
        enhancer.setCallback(new FixedValue(){
            @Override
            public Object loadObject() throws Exception{
                return "鲁班一号";
            }
        });
        
        Demo2 proxy = (Demo2) enhancer.create();
        System.out.println(proxy.method1());
        System.out.println(proxy.method2());
        System.out.println(proxy.toString());
    }
}
4. 直接放行,不做任何操作

  Callback接口下面有个子接口org.springframework.cglib.proxy.NoOp,将这个作为Callback的时候,被调用的方法会直接放行,像没有任何代理一样。代码示例如下,

public class Test(){
    public static void main(String[] args){
        test4();
    }
    
    public static void test4(){
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Demo2.class);
        enhancer.setCallback(NoOp.INSTANCE);
        
        Demo2 proxy = (Demo2) enhancer.create();
        System.out.println(proxy.method1());
        System.out.println(proxy.method2());
    }
}
5. 不同的方法使用不同的拦截器
public class Demo4{
    public void insert1(){
        System.out.println("insert1方法")
    }
    
    public void insert2(){
        System.out.println("insert2方法")
    }
    
    public String get1(){
        System.out.println("get1方法");
    }
    
    public String get2(){
        System.out.println("get2方法");
    }
}

给上面的类创建一个代理,实现下面的功能:

1、以insert开头的方法需要统计方法耗时

2、以get开头的方法直接返回固定字符串"鲁班被杀了"

public class Test{
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(Demo4.class);
    
    //创建2个Callback
    Callback[] callbacks = {
        //这个用来拦截所有insert开头的方法
        new MethodInterceptor(){
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable{
            long starTime = System.nanoTime();
            Object result = methodProxy.invokeSuper(o, objects);
            long endTime = System.nanoTime();
            System.out.println(method+",耗时(纳秒):"+(endTime - starTime));
            
            return result;
            }
        },
        
        //下面这个用来拦截所有get开头的方法,返回固定值的
        new FixedValue(){
            @Override
            public Object loadObject() throws Exception{
                return "鲁班被杀了";
            }
        }
    };
    
    enhancer.setCallbackFilter(new CallbackFilter(){
        @Override
        public int accept(Method method){
            return 0;
        }
    });
    
    //调用enhancer的setCallbacks传递Callback数组
    enhancer.setCallbacks(callbacks);
    
    /**
     * 设置过滤器CallbackFilter
     * CallbackFilter用来判断调用方法的时候callbacks数组中的哪个Callback来处理当前方法
     * 返回的是callbacks数组的下标
     */
     enhancer.setCallbackFilter(new CallbackFilter(){
         @Override
         public int accept(Method method){
             //获取当前调用的方法的名称
             String methodName = method.getName();
             /**
              * 方法名称以insert开头
              * 返回callbacks中的第1个Callback对象来处理当前方法
              * 否则使用第二个Callback处理被调用的方法
              */
              return methodName.startsWith("insert") ? 0 : 1;
             Demo4 proxy = (Demo4) enhancer.create();
             System.out.println("-----------------------");
             proxy.insert1();
             System.out.println("-----------------------");
             proxy.insert2();
             System.out.println(proxy.get1());
             System.out.println("-----------------------");
             System.out.println(proxy.get2());
         }
     });
}

  由于要求不同的方法做不同的处理,所以需要有两个Callback对象,当调用代理对象的方法的时候,具体会走哪个Callback,此时会通过CallbackFilter中的accept来判断,这个方法返回callbacks数组的索引。

6. 对第五个示例的优化

  cglib中有个CallbackHelper类,可以对案例5的代码进行优化,CallbackHelper类相当于对一些代码进行了封装,方便实现案例5,示例大妈如下:

public class Test{
    public static void main(String[] args){
        test();
    }
    
    public static void test(){
        Enhancer enhancer = new Enhancer();
        //创建2个Callback
        Callback countTimeCallback = (MethodInterceptor) (Object o, Method method, Object[] objects, MethodProxy methodProxy) ->{
            long starTime = System.nanoTime();
            Object result = methodProxy.invokeSuper(o, objects);
            long endTime = System.nanoTime();
            System.out.println(method+".耗时(纳秒):"+(endTime - starTime));
            return result;
        };
        //下面这个用来拦截所有get开头的方法,返回固定值
        Callback callback = (FixedValue) () -> "鲁班被杀了";
        
        CallbackHelper callbackHelper = new CallbackHelper(Demo.class, null){
            @Override
            protected Object getCallback(Method method){
                return method.getName().startsWith("insert") ? countTimeCallback : callback;
            }
        };
        enhancer.setSuperclass(Demo4.class);
        //调用enhancer的setCallbacks传递Callback数组
        enhancer.setCallbacks(callbackHelper.getCallbacks());
        
        /**
         * 设置CallbackFilter,用来判断某个方法具体走哪个Callback
         */
         enhancer.setCallbackFilter(callbackHelper);
         Demo4 proxy = (Demo4) enhancer.create();
         System.out.println("-----------------------");
         proxy.insert1();
         System.out.println("-----------------------");
         proxy.insert2();
         System.out.println("-----------------------");
         System.out.println(proxy.get1());
         System.out.println("-----------------------");
         System.out.println(proxy.get2());
    }
}
6. 实现通用的统计任意类方法耗时代理类
public class CountTimeProxy implements MethodInterceptor{
    //需代理对象
    private Object proxy;
    
    public CountTimeProxy(){
        this.proxy = proxy;
    }
    
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable{
        long starTime = System.nanoTime();
        //调用被代理对象(即target)的方法,获取结果
        Object result = method.invoke(proxy, objects);
        System.out.println(method + ",耗时(纳秒):" + (endTime - starTime));
        return result;
    }
    
    //创建任意类的代理对象
    public static <T> T createProxy(T target) { 
        CostTimeProxy costTimeProxy = new CostTimeProxy(target); 
        Enhancer enhancer = new Enhancer(); 
        enhancer.setCallback(costTimeProxy); 
        enhancer.setSuperclass(target.getClass()); 
        return (T) enhancer.create(); }
}

  可以使用上面的静态方法createProxy类为目标对象proxy创建一个代理对象,被代理的对象自动实现方法调用耗时统计。

代码示例:

@Test
public void test1(){
    //创建Service1代理
    Service1 service1 = CountTimeProxy.createProxy(new Service1());
    service1.method1();
    
    //创建Service2代理
    Service2 service2 = CountTimeProxy.createProxy(new Service2());
    System.out.println(service2.method1());
}

Cglib和Jdk动态代理的区别

1、jdk动态代理只能够对接口进行代理,不能对普通的类进行代理(因为所有生成的代理类的父类为Proxy,java类继承机制不允许多重继承)。cglib能够代理普通类。

2、jdk动态代理使用java原生的反射api进行操作,在生成类上比较高效。cglib使用ASM框架直接对字节码进行操作,在类的执行过程比较高效。

注解

  注解是给机器看的。它是对代码的一种增强,可以在代码编译或者运行期间获取注解信息,然后根据这些信息做各种事情。

@Configuration注解和@Bean注解

@Configuration用法

@Configuration这个注解可以加在类上,可以让这个类等同于bean xml配置文件

@Configuration
public class ConfigBean{
    
}

等同于bean 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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd">

</beans>

获取@Configuration修饰的类,如下演示:

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConfigBean.class);

当我们在ConfigBean类中注册bean,我们就需要用到@Bean注解了。

@Bean注解用法

  这个注解类似于bean xml配置文件中的bean元素,用来在spring容器中注册一个bean。
  @Bean注解用在方法上,表示通过方法来定义一个bean,默认将方法名称作为bean名称,将方法返回值作为bean对象,注册到spring容器中。

@Bean
public User user1(){
    return new User();
}

代码示例:
User类

public class User{
}

Bean配置类

@Configuration
public class ConfigBean{
    //bean名字为方法默认值:user1
    @Bean
    public User user1(){
        reutrn new User();
    }
    
    //bean名字通过value制定了:user21
    @Bean("user21")
    public User user2(){
        return new User();
    }
    
    //bean名字为user31,2个别名:[userb31, userb32]
    @Bean({"user31", "userb31", "userb32"})
    public User user2(){
        return new User();
    }
}

测试类

public class test{
    public static void main(String[] args){
        test1();
        
    }
    
    public static void test1(){
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConfigBean.class);
        for(String beanName : context.getBeanDefinitionNames()){
            //别名
            String[] aliases = context.getAliases(beanName);
            System.out.println(String.format("bean名称:%s,别名:%s,bean对象:%s",beanName,Arrays.asList(aliases),context.getBean(beanName)));
        }
    }
}

去掉@Configuration和不去掉的区别

1、有没有@Configuration注解,@Bean都会起效,都会将@Bean修饰的方法作为bean注册到容器中

2、被@Configuration注解的bean有被cglib处理过,变成了一个代理对象,没有@Configuration注解的没有被cglib处理。

3、被@Configuration修饰的类,spring容器会通过cglib给这个类创建一个代理,代理会拦截所有被@bean修饰的方法,默认情况下确保这些方法值被调用一次,从而保证这些bean是同一个bean。确保bean是单利的。

@ComponentScan和@ComponentScans

目前为止介绍的bean注册方式:

1、xml中bean元素的方式

2、@Bean注解标注方法的方式

当大量bean批量注册时候,就用到了@ComponentScan了

@ComponentScan工作的过程:

  1. Spring会扫描指定的包,且会递归下面子包,得到一批类的数组
  2. 然后这些类会经过上面的各种过滤器,最后剩下的类会被注册到容器中

其他

取长补短共享技术,共同探讨学习技术创建技术氛围加微信群Day9884125

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值