Spring和Springboot相关知识点整理

2.3.1 与Spring的关系

这是Spring AOP的两种实现方式。根据官方文档:

默认使用JdkProxy

对于被代理对象没有实现任何接口,使用Cglib

可以强制指定使用Cglib。

这样就可以解释为什么有的bean实现了接口,有的没有,但是在同一个工程中可以并存了。

2.3.2 示例代码

本节代码改写自参考文献[5]。

//用户管理接口

public interface UserManager {

    //新增用户抽象方法

    void addUser(String userName,String password);

    //删除用户抽象方法

    void delUser(String userName);

}

//用户管理实现类,实现用户管理接口

public class UserManagerImpl implements UserManager{

    @Override

    public void addUser(String userName) {

        System.out.println("调用了新增的方法!");

        System.out.println("传入参数为 userName: "+userName+" password: "+password);

    }

    @Override

    public void delUser(String userName) {

        System.out.println("调用了删除的方法!");

        System.out.println("传入参数为 userName: "+userName);

    } 

}

import java.lang.reflect.InvocationHandler;

import java.lang.reflect.Method;

import java.lang.reflect.Proxy;

import com.lf.shejimoshi.proxy.entity.UserManager;

import com.lf.shejimoshi.proxy.entity.UserManagerImpl;

//JDK动态代理实现InvocationHandler接口

public class JdkProxy implements InvocationHandler {

    private Object target ;//需要代理的目标对象


    @Override

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

        System.out.println("JDK动态代理,监听开始!");

        Object result = method.invoke(target, args);

        System.out.println("JDK动态代理,监听结束!");

        return result;

    }

    //定义获取代理对象方法

    // 因为只是在main()里测试,声明为private了

    private Object getJDKProxy(Object targetObject){

        this.target = targetObject;

        return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), targetObject.getClass().getInterfaces(), this);

    }


    public static void main(String[] args) {

        JdkProxy jdkProxy = new JdkProxy();

        UserManager user = (UserManager) jdkProxy.getJDKProxy(new UserManagerImpl());//获取代理对象

        user.addUser("admin");

    } 

}

import java.lang.reflect.Method;

import com.lf.shejimoshi.proxy.entity.UserManager;

import com.lf.shejimoshi.proxy.entity.UserManagerImpl;

import net.sf.cglib.proxy.Enhancer;

import net.sf.cglib.proxy.MethodInterceptor;

import net.sf.cglib.proxy.MethodProxy;

//Cglib动态代理,实现MethodInterceptor接口

public class CglibProxy implements MethodInterceptor {

    private Object target;//需要代理的目标对象


    //重写拦截方法

    @Override

    public Object intercept(Object obj, Method method, Object[] arr, MethodProxy proxy) throws Throwable {

        System.out.println("Cglib动态代理,监听开始!");

        Object invoke = method.invoke(target, arr);//方法执行,参数:target 目标对象 arr参数数组

        System.out.println("Cglib动态代理,监听结束!");

        return invoke;

    }

    //定义获取代理对象方法

    public Object getCglibProxy(Object objectTarget){

        //为目标对象target赋值

        this.target = objectTarget;

        Enhancer enhancer = new Enhancer();

        //设置父类,因为Cglib是针对指定的类生成一个子类,所以需要指定父类

        enhancer.setSuperclass(objectTarget.getClass());

        enhancer.setCallback(this);// 设置回调

        Object result = enhancer.create();//创建并返回代理对象

        return result;

    }


    public static void main(String[] args) {

        CglibProxy cglib = new CglibProxy();

        UserManager user =  (UserManager) cglib.getCglibProxy(new UserManagerImpl());

        user.delUser("admin");

    }


}

2.3.3 比较

比较一下两者的区别,这也是常见的面试问题。

JdkProxy Cglib

依赖 被代理对象实现了接口(所有接口的方法数总和必须>0[4]) 引入asm、cglib jar ;不能是final类和方法

原理 反射,实现被代理对象接口的匿名内部类,通过InvocationHandler.invoke()包围被代理对象的方法 引入asm、cglib jar,代理类实现MethodInterceptor,通过底层重写字节码来实现

效率 创建快,运行慢(见下方说明2) 创建慢,运行快

说明

Cglib是如何修改字节码,从代码上是看不出来的。使用的是ASM技术,修改class文件,可以自行查阅。

JDK1.8及以后,JdkProxy的运行速度已经比Cglib快了(之前则是慢于Cglib),测试代码可见参考文献[6]。

2.3.4 关于org.apoalliance.intercept.MethodInterceptor

我读了一下之前所用的日志拦截器源码,发现其实现的是这节标题的接口:

class CommonInterceptor implements MethodInterceptor {

      @Override

      public Object invoke(MethodInvocation invocation) throws Throwable {

            try {

                  // 拦截器内部逻辑

                  result = invoication.proceed();

            catch(Throwable e) {

                  // 异常处理

            }

            return result;

      }

}

声明代理链

@Configuration

public class InterceptorConfig {

      @Bean

      public CommonInterceptor serviceInterceptor() {

            CommonInterceptor bean = new CommonInterceptor();

            return bean;

      }

      // 代理名称后缀为servie的实现类

      @Bean

      public BeanNameAutoProxyCreator servieBeanNameAutoProxyCreator() {

            BeanNameAutoProxyCreator creator = new BeanNameAutoProxyCreator();

            creator.setName("*ServieImpl");

            creator.setInterceptorNames("serviceInterceptor");

            creator.setProxyTargetClass(true);

            return creator;

      }

}

查了一些资料,apoalliance包下只是aop的接口规范,不是具体的实现,不要把这里的MethodInterceptor和cglib的MethodInterceptor搞混。

2.4 构造方法注入和设值注入的区别

注:设值注入指的是通过setter注入。

之前看参考文献[7],感觉很难懂,试着改换了一种说法:

如果只设置基本类型(int、long等)的值,建议设置默认值而不是通过任何一种注入完成

构造注入不支持大部分的依赖注入。构造注入仅在创建时执行,设值注入的值在后续也可以变化。

设值注入可以支持尚未完整的被依赖的对象,构造注入则不行。可以通过构造注入决定依赖关系,因此如果依赖关系不会发生变更也可以选择依赖注入。

2.5 ApplicationContext事件

可以通过实现ApplicationEvent类和ApplicationListener接口,进行ApplicationContext的事件处理。这是标准的发送者-监听者的模型,可以用来处理业务逻辑,将代码解耦。

但是,发送和接收实际上是同步的,如果有事务,会在同一个事务内,并不能作为异步处理机制[8]。

示例代码见参考文献[9]。

3. SpringBoot

注:工作中我对SpringBoot是偏应用的,研究并不是很深入。

3.1 如何快速搭建一个SpringBoot项目

见参考文献[10]。实际的过程是借助Spring Initializer这个网络应用程序来生成SpringBoot项目。

3.2 SpringBoot的关键注解

所谓核心注解,这里指的是相对Spring本身新增的一些注解,来看看它们有什么作用。

恰好这里提到的注解,都可以打在SpringBoot的启动类(不限于启动类),用下面的代码片段来进行说明。

3.2.1 代码示例

package com.example.demo;

import org.mybatis.spring.annotation.MapperScan;

import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;

import org.springframework.context.annotation.Import;

import org.springframework.context.annotation.PropertySource;

@PropertySource(value = "classpath:application.properties")

@MapperScan("com.example.demo.dal")

@SpringBootApplication(scanBasePackages = {"com.example.demo"})

@Import({DemoConfig1.class, DemoConfig2.class,})

public class DemoApplication {

public static void main(String[] args) {

SpringApplication.run(DemoApplication.class, args);

}

}

3.2.2 @PropertySource

从指定的文件读取变量。示例代码会从resource目录下读取application.properties变量的值。文件的格式如下,既可以用常量,也可以用变量替换,来对不同环境的值作区分。

name=value

env.name=$env.value

如何使用这个值?在要使用的地方获取即可。

@PropertySource(value = "classpath:application.properties")

class TestClass {

@Resource

private Environment environment;

      @Test

      public void test() {

            String value = environment.getProperty("name"));

      }

}

3.2.2.1 与@Value配合使用

使用@Value可以把配置文件的值直接注入到成员变量中。

@PropertySource("classpath:application.properties")

public class PropertyConfig {

    @Value("${name}")

    private String value;

    ...

}

3.2.2.2 通过@Import引用

3.2.1的示例代码中,如果类上没有@PropertySource,但DemoConfig1或DemoConfig2中有@PropertySource,通过@Import可以将它们加载的变量也读出来。

@Import的作用在下文会继续介绍。

3.2.2.3 .properties和.yml配置文件

@PropertySource只能导入.properties配置文件里的内容,对于.yml是不支持的。看了一些文章,得出结论是yml文件是不需要注解就能导入,但是需要路径。

Springboot有两种核心配置文件,application和bootstrap,都可以用properties或yml格式。区别在于bootstrap比application优先加载,并且不可覆盖。

3.2.3 @MapperScan

这实际上是一个mybatis注解,作用是为指定路径下的DAO接口,通过sqlmapping.xml文件,生成实现类。

3.2.4 @SpringBootApplication

@SpringBootApplication是由多个注解组合成的。源码如下:

@Target(ElementType.TYPE)

@Retention(RetentionPolicy.RUNTIME)

@Documented

@Inherited

@SpringBootConfiguration

@EnableAutoConfiguration

@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),

@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })

public @interface SpringBootApplication {

      // 略

}

简单介绍一下这些注解。

#### 3.2.4.1 元注解

最上面四行都是元注解。回忆一下它们的作用<sup>[12]</sup>:

* @Target 注解可以用在哪。TYPE表示类型,如类、接口、枚举

* @Retention 注解的保留时间期限。只有RUNTIME类型可以在运行时通过反射获取其值

* @Documented 该注解在生成javadoc文档时是否保留

* @Inherited 被注解的元素,是否具有继承性,如子类可以继承父类的注解而不必显式的写下来。

#### 3.2.4.2 @SpringBootConfiguration

标注这是一个SpringBoot的配置类,和@Configuration功能是相通的,从源码也可以看出它直接使用了@Configuration。

#### 3.2.4.3 @EnableAutoConfiguration

这个注解是实现自动化配置的核心注解,定义如下

```java

@Target(ElementType.TYPE)

@Retention(RetentionPolicy.RUNTIME)

@Documented

@Inherited

@AutoConfigurationPackage

@Import(AutoConfigurationImportSelector.class)

public @interface EnableAutoConfiguration {

      // 略

}

龙华大道1号 www.kinghill.cn/LongHuaDaDao1Hao/index.html

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。