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来定义,具体定义规则如下:
- 当id存在的时候,不管name有没有,取id为bean的名称
- 当id不存在,此时需要看name,name的值可以通过 ,;或者空格 分割,最后会按照分隔符得到一个String数组,数组的第一个元素作为bean的名称,其他的作为bean的别名
- 当id和name都存在的时候,id为bean名称,name用来定义多个别名
- 当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工作的过程:
- Spring会扫描指定的包,且会递归下面子包,得到一批类的数组
- 然后这些类会经过上面的各种过滤器,最后剩下的类会被注册到容器中
其他
取长补短共享技术,共同探讨学习技术创建技术氛围加微信群Day9884125