spring 文档摘录与注释

本文深入探讨Spring Boot的特性,包括FailureAnalyzer自定义异常处理,ApplicationRunner和CommandLineRunner接口的应用,SpringApplication与SpringApplicationBuilder的配置,以及AOP在启动任务和参数处理中的应用。同时,介绍了Java代理机制,如JDK动态代理和CGLIB,以及Spring的BeanWrapper、元数据阅读和资源加载。文章还涵盖了内容协商、类加载器和资源加载器的工作原理,以及AOP的Pointcut表达式和最佳实践。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

​ 以下内容均来自spring framework官网,记录自己不会的和不熟悉的,有自己思考的部分,不敢保证正确,用于个人知识梳理。版本是全新的6.0!

Spring Boot 特性

FailureAnalyzer

​ 在启动的时候,捕获异常,并且提供更加人性化的提示信息

使用

public class NullPointFailureAnalyzer extends AbstractFailureAnalyzer<NullPointerException> {
    @Override
    protected FailureAnalysis analyze(Throwable rootFailure, NullPointerException cause) {
        return new FailureAnalysis("错误描述", "解决错误的建议", cause);
    }
}

​ spring.factories

org.springframework.boot.diagnostics.FailureAnalyzer=\
xxx.xxx.NullPointFailureAnalyzer

SpringApplication

@SpringBootApplication
public class MyApplication {

    public static void main(String[] args) {
        SpringApplication application = new SpringApplication(MyApplication.class);
        application.setBannerMode(Banner.Mode.OFF);
        application.run(args);
    }

}

​ 这个定义的基本都可以在yaml中配置,就是在spring.main,不过这个支持代码级别的配置

SpringApplicationBuilder

new SpringApplicationBuilder()
        .sources(Parent.class)
        .child(Application.class)
        .bannerMode(Banner.Mode.OFF)
        .run(args);

​ 当创建一个ApplicationContext层次结构有一些限制。例如,Web组件必须包含在子上下文,和父母和孩子相同的环境用于上下文。

参数的处理

@Component
public class MyBean {

    public MyBean(ApplicationArguments args) {
        
    }

}

​ 这个和在启动类处理不一样,这个是在启动完成后,进行的参数处理,明显spring又封装了一层,不过这个是小问题,但是你看这个组件没有默认的无参构造器,所以spring用这个bean最多的构造器把组件注入进来了

ApplicationRunner或CommandLineRunner

​ spring官方推荐我们使用这个接口来做应用程序启动之后但开始接受流量之前运行的任务。

@Configuration

​ @Configuration(proxyBeanMethods = false)

​ 以前看的到时候没太看懂,现在知识储备够了,一下就懂了,简单来讲,就是如果这个为ture,也就是默认的,把这个类作为cglib代理,然后自调用的时候,就是你在一个@bean里面引用了同一个配置类下的另一个@bean的方法,是可以实现单利获取的,但是,如果你没有这样需求,或者你用其他方案解决了,就最好把这个设置为false

自定义对象序列化

​ 这个是通用的,不仅仅是对spring的默认序列化,可以理解为一个门面

@JsonComponent
public class MyJsonComponent {

    public static class Serializer extends JsonObjectSerializer<MyObject> {

        @Override
        protected void serializeObject(MyObject value, JsonGenerator jgen, SerializerProvider provider)
                throws IOException {
            jgen.writeStringField("name", value.getName());
            jgen.writeNumberField("age", value.getAge());
        }

    }

    public static class Deserializer extends JsonObjectDeserializer<MyObject> {

        @Override
        protected MyObject deserializeObject(JsonParser jsonParser, DeserializationContext context, ObjectCodec codec,
                JsonNode tree) throws IOException {
            String name = nullSafeValue(tree.get("name"), String.class);
            int age = nullSafeValue(tree.get("age"), Integer.class);
            return new MyObject(name, age);
        }

    }

}

路径匹配和内容协商

​ Spring Boot 默认选择禁用后缀模式匹配,这意味着像"GET /projects/spring-boot.json"这样的请求不会匹配到@GetMapping("/projects/spring-boot")映射,真有需要是通过内容协商来的,比如你这种的匹配过来,执行的方法真的很奇怪

​ 内容协商其实就是http请求头说我想要什么,然后有权重,然后spring把这个权重排序后去掉权值,看看我有什么,尽力给你最好的。

Ioc容器

org.springframework.beansorg.springframework.context包是 Spring Framework 的 IoC 容器的基础。

​ 进去看看吧,首先是发现了package-info.java,其他地方我也见过,今天看看到底是干什么的

package-info

package-info.java是一个Java文件,可以放到任意Java源码包执行(访问权限仅仅在本包,而且子包也不能,private也是这样的)。不过里面的内容有特定的要求,其主要目的是为了提供包级别相关的操作,比如包级别的注解、注释及公共变量。

@Target(ElementType.PACKAGE)

​ 有点像给这个包的所有类加上了这个注解,但其实spring的好多类都不支持,刚刚测试了一下,spring的工具类是扫描不到这个注解的,具体是做法是使用ioc。因为这个是我们最常见的,那不用ioc呢?结论是无论是否加载这个类,都是获取不到,只能用java原生的方法

包级别的常量

class PACKAGE_CONST{
    public static final String TEST="TEST";
}

​ 直接写在package-info,不能是public,只能包内引用。这个应该是最有用的一个特性了

​ 在spring中应该是主要用来做包的说明文档

代理

JDK代理

InvocationHandler

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

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

public class MyInvocationHandler implements InvocationHandler {
    //目标对象
    private Object target;

    public MyInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //调用前打印
        Object result=null;
        System.err.println("------begin----------");
        result=method.invoke(target, args);
        System.err.println("------end----------");
        return result;
    }
}
MyInvocationHandler handler = new MyInvocationHandler(传入被代理的对象,是对象不是接口);
        代理类 proxyService = (需要自己做一下类型转换) Proxy.newProxyInstance(类加载器, new Class[] { 被代理接口的反射 }, handler);
        proxyService.sayHello();

​ 要是这么看的话,我也能写spring的aop,我实现那个接口,然后判断是否是改被代理的,比如在容器中拿到了一个service,然后拿出了它的接口,现在该有的参数都用了,我用Proxy一封装,把原来的bean替换成代理的bean,这不就实现效果了吗?

​ 那原理是什么呢?想都不用想,肯定在Proxy.newProxyInstance里面。

@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
    throws IllegalArgumentException
{
    Objects.requireNonNull(h);

    final Class<?>[] intfs = interfaces.clone();
    final SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
    }

    /*
     * 查找或生成指定的代理类.
     */
    Class<?> cl = getProxyClass0(loader, intfs);
    //如果给定加载器定义的代理类实现给定接口存在,这将简单地返回缓存的副本;否则,它将通过 ProxyClassFactory 创建代理类

    /*
     * 使用指定的调用处理程序调用其构造函数.
     */
    try {
        if (sm != null) {
            checkNewProxyPermission(Reflection.getCallerClass(), cl);
        }

        final Constructor<?> cons = cl.getConstructor(constructorParams);
        final InvocationHandler ih = h;
        if (!Modifier.isPublic(cl.getModifiers())) {//如果不是公开接口
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    cons.setAccessible(true);
                    return null;
                }
            });
        }
        return cons.newInstance(new Object[]{h});
    } catch (IllegalAccessException|InstantiationException e) {
        throw new InternalError(e.toString(), e);
    } catch (InvocationTargetException e) {
        Throwable t = e.getCause();
        if (t instanceof RuntimeException) {
            throw (RuntimeException) t;
        } else {
            throw new InternalError(t.toString(), t);
        }
    } catch (NoSuchMethodException e) {
        throw new InternalError(e.toString(), e);
    }
}

GCLIB

MethodInterceptor

Object intercept(Object var1, Method var2, Object[] var3, MethodProxy var4) throws Throwable;

​ 看参数名就能看出来,我这波很丢脸,没有把源码下载成功分析,idea出问题了

Enhancer

Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(MyTarget.class);
enhancer.setCallback(new MyInterceptor());
MyTarget target = (MyTarget) enhancer.create();
//要是我的话,感觉设计一个builder模式更好,不过我也会在spring创建gclib了,但是要是能和切面结合在一起,那是真的难,spring aop用的是一个递归,结合java的异常处理机制实现的
target.printName();

ProxyFactory

​ spring 对两者做的集合,依据你提供的信息决定使用那种动态代理方式,我们实际用的时候,肯定是要用这个,毕竟方便一些

MetadataReaderFactory

​ 可以传入一个Resource然后读取java文件的所有信息,包括注解,然后把这个加载出来以后,你就可以利用反射获取它的运行时信息,这样我们就可以完成对一个类的全部分析了。有一个带有缓存的实现类,可以用这个。

@Lookup

​ 标注在方法上,每次都从容器中重新获取那个bean,明显适合于多例,而且比用applicationContext好很多。利用的是方法的返回类型,要是有多个实例,盲猜测用的是方法名。这个注解属性可能会建议一个要查找的目标 bean 名称。如果未指定,目标 bean 将根据注解方法的返回类型声明进行解析。猜测完美正确。

ClassLoader

​ jvm启动的时候,并不会一次性加载所有的class文件,而是根据需要去动态加载。

​ 看的太入迷了,我这边直接总结一下,java的类加载器是有继承体系的,最终的父类是用c++写的,就是用来加载内部核心类的。我们平时获取的类加载器就是用来加载我们自己写的类,但是有的类不在我们的目录下,或者有解密的需求,我们就可以自己写一个类加载器,去指定的路径加载我们想要的类。

​ 具体就是继承ClassLoader,重写findClass,把class文件读入内存,然后使用defineClass来生成这个java类的反射。个人觉得重复的步骤有点多,spring应该是会提供给我们一个更方便的工具。

​ 然后简单说一下双亲委派,流程就是当我们要加载一个类的时候,先从缓存里面找,找不到就交给父类,一个一个递归下去,然后父类在自己的路径上寻找类,找到就加载,找不到就给子类。因为不同的类加载器它对应的地址是不一样的。

ResourceLoader

​ Spring中整合了获取资源的工具,就是使用Resource接口。此接口是Spring为了统一读取诸如本地文件、classpath项目路径下的文件、url互联网上的文件等不同类型渠道的资源,封装隐藏如打开流、关闭流、报错处理等大量重复模板代码,而专程设计提供的接口类。

public class ResourceTest implements ApplicationContextAware{
	
	ApplicationContext applicationContext ;
	
	public void getResourceTest() {
		//通过applicationContext,只一步getResource(),就可以获取资源
		Resource resource = applicationContext.getResource("classpath:spring-mvc.xml");
		//TODO: 用此resource来获取想要的资源
		//......
	}
	
	@Override
	public void setApplicationContext(ApplicationContext applicationContext)
			throws BeansException {		
		this.applicationContext = applicationContext;		
	}
}

BeanWrapper

对Bean进行包装

​ 在其构造方法中将bean传入,这个对象其实是任意的,不一定是spring的bean

对Bean的属性进行访问以及设置

​ 支持读和写,支持用.来解析嵌套属性,支持用[]来索引集合,支持使用[""]来获取map,这个js的最佳写法,和mybatis的类似

类型转换

​ 设置属性的过程中,必然涉及到类型转换


内省

​ 专门针对javabean的反射,Introspector是jdk提供的类,我们尝试一下,可以获取以get开头的方法,包括getClass也弄进去了

官方例子

BeanWrapper company = new BeanWrapperImpl(new Company());
// setting the company name..
company.setPropertyValue("name", "Some Company Inc.");
// ... can also be done like this:
PropertyValue value = new PropertyValue("name", "Some Company Inc.");
company.setPropertyValue(value);

// ok, let's create the director and tie it to the company:
BeanWrapper jim = new BeanWrapperImpl(new Employee());
jim.setPropertyValue("name", "Jim Stravinsky");
company.setPropertyValue("managingDirector", jim.getWrappedInstance());

// retrieving the salary of the managingDirector through the company
Float salary = (Float) company.getPropertyValue("managingDirector.salary");

格式装换器

​ 比如日期的格式,有的时候数据的格式也可以,比如有会用1.999.222这样的数字格式

public final class DateFormatter implements Formatter<Date> {

    private String pattern;

    public DateFormatter(String pattern) {
        this.pattern = pattern;
    }

    public String print(Date date, Locale locale) {
        if (date == null) {
            return "";
        }
        return getDateFormat(locale).format(date);
    }

    public Date parse(String formatted, Locale locale) throws ParseException {
        if (formatted.length() == 0) {
            return null;
        }
        return getDateFormat(locale).parse(formatted);
    }

    protected DateFormat getDateFormat(Locale locale) {
        DateFormat dateFormat = new SimpleDateFormat(this.pattern, locale);
        dateFormat.setLenient(false);
        return dateFormat;
    }
}
public final class NumberFormatAnnotationFormatterFactory
        implements AnnotationFormatterFactory<NumberFormat> {

    public Set<Class<?>> getFieldTypes() {
        return new HashSet<Class<?>>(asList(new Class<?>[] {
            Short.class, Integer.class, Long.class, Float.class,
            Double.class, BigDecimal.class, BigInteger.class }));
    }

    public Printer<Number> getPrinter(NumberFormat annotation, Class<?> fieldType) {
        return configureFormatterFrom(annotation, fieldType);
    }

    public Parser<Number> getParser(NumberFormat annotation, Class<?> fieldType) {
        return configureFormatterFrom(annotation, fieldType);
    }

    private Formatter<Number> configureFormatterFrom(NumberFormat annotation, Class<?> fieldType) {
        if (!annotation.pattern().isEmpty()) {
            return new NumberStyleFormatter(annotation.pattern());
        } else {
            Style style = annotation.style();
            if (style == Style.PERCENT) {
                return new PercentStyleFormatter();
            } else if (style == Style.CURRENCY) {
                return new CurrencyStyleFormatter();
            } else {
                return new NumberStyleFormatter();
            }
        }
    }
}

AOP

@Pointcut

​ 这是可以相互组合的,这样的语义性更强

@Pointcut("execution(public * *(..))")
private void anyPublicOperation() {} 

@Pointcut("within(com.xyz.myapp.trading..*)")
private void inTrading() {} 

@Pointcut("anyPublicOperation() && inTrading()")
private void tradingOperation() {} 

表达式

​ 总结一下以前自己没太用过的

bean(*Service)

​ 可以支持依据bean的名称切面,当然你可以多个配合起来,这个是运行时完成的,你不要太考虑这个效率,保证可读性和正确性是最重要的

within(com.xyz.myapp.web…*)

​ 这个包,以及包括子包的所有bean方法

this(com.xyz.service.AccountService)

​ 实现AccountService接口,当前AOP代理对象类型的执行方法,父类未重载的方法不会被处理和他类似有target(com.xyz.service.AccountService),区别是不是代理对象,父类未重载的方法也被处理。

​ 总结梳理一下吧,如果父类或接口有方法,但是我没有重写,那么target仍然会代理这个方法,你理解这个英文的意识,target只看重这个目标,而this只会代理自己重写的方法

​ 支持泛型,但是不支持集合泛型参数,execution(* …Sample+.sampleGenericMethod(*)) && args(param)

@within和@target

​ 标准在类上的注解,和上面的一样还是会涉及到继承的关系。

​ @within:只要你没有重写我的方法,我就无限的往子类aop

​ @target:看这个单词的意思,在于this<target<within之间,继承后直接失去效果,标注在哪个类就只对那个类有效果,如果没有继承性的话,明显这个最灵活,而且within只支持不重写,一重写就无意义了。

@DeclareParents

​ 可以往类中添加新的方法

 @DeclareParents(value="com.xzy.myapp.service.*+", defaultImpl=DefaultUsageTracked.class)//默认要添加的方法,被写在了这个类中,强制转换后就能调用相关方法,目前建议还是使用其他技术替换实现
    public static UsageTracked mixin;//UsageTracked是一个接口

    @Before("com.xyz.myapp.CommonPointcuts.businessService() && this(usageTracked)")

官方建议

  1. 尽量不要使用Around
  2. 尽量利用args和@annotation,把这些会用到的参数传递advice中
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值