2024年1月

1.Aware介绍

在Spring当中有一些内置的对象是未开放给我们使用的,例如Spring的上下文ApplicationContext、环境属性Environment,BeanFactory等等其他的一些内置对象,而在我们可以通过实现对应的Aware接口去拿到我们想要的一些属性,一般命名都是xxxAware,在创建对象的时候, 会调用接口规定的方法注入到相关组件:Aware。

Bean生命周期内常见的:BeanNameAware、ApplicationContextAware、BeanFactoryAware 。

  • BeanNameAware
    接口只有一个方法 setBeanName()。实现 BeanNameAware 接口的 bean,在 bean 加载的过程中可以获取到该 bean 的 id。
  • ApplicationContextAware
    接口只有一个方法 setApplicationContext()。实现 ApplicationContextAware 接口的 bean,可以在 bean 加载的过程中可以获取到 Spring 的 ApplicationContext,从而能够获取任意 bean 及大量 IOC 容器信息
  • BeanFactoryAware
    接口只有一个方法 setBeanFactory()。实现 BeanFactoryAware 接口的 bean,可以在 bean 加载的过程中可以获取到加载该 bean 的 BeanFactory
public class TestBean implementsBeanNameAware, ApplicationContextAware, BeanFactoryAware{
  //获取当前bean的name
@Override
public voidsetBeanName(String name) {
System.out.println(
"setBeanName()...beanName=" +name);
}
  //获取当前ApplicationContext
@Override
public void setApplicationContext(ApplicationContext applicationContext) throwsBeansException {
System.out.println(
"setApplicationContext()...applicationContext=" +applicationContext);
}
  //获取当前BeanFactory
@Override
public void setBeanFactory(BeanFactory beanFactory) throwsBeansException {
System.out.println(
"setBeanFactory()...beanFactory=" +beanFactory);
}
}

测试类:

@SpringBootApplication
@EnableAspectJAutoProxy
public classDemoApplication{public static voidmain(String[] args) {//创建一个AnnotationConfigApplicationContext容器 AnnotationConfigApplicationContext context = newAnnotationConfigApplicationContext();//注册TestBean02到容器中 context.register(TestBean02.class);//刷新容器,启动应用上下文 context.refresh();//关闭容器,销毁应用上下文 context.close();
}
}

结果:

总结:这些接口提供了一种在Bean中与Spring容器交互的机制,但需要注意的是,过度使用 Aware 接口可能导致代码与Spring框架的紧耦合。在实际应用中,更推荐使用依赖注入的方式来获取所需的资源,以保持代码的灵活性和清晰性。 Aware 接口通常在一些特定场景下使用,例如需要在Bean初始化时获取容器信息的情况。

2.通过自定义Aware来实现数据源的自动注入

目标:完成不使用比如@Autowired和@Resource来实现自动注入 主要实现是通过自定义Aware接口搭配BeanPostProcessor接口实现功能。

  • 1.创建数据源对象DataBaseConfig:
public classDataBaseConfig {//数据库url
    privateString url;//数据库名称
    privateString name;publicString getUrl() {returnurl;
}
public voidsetUrl(String url) {this.url =url;
}
publicString getName() {returnname;
}
public voidsetName(String name) {this.name =name;
}
@Override
publicString toString() {return "DataBaseConfig{" + "url='" + url + '\'' + ", name='" + name + '\'' + '}';
}
}
  • 2.创建自定义Aware接口:DataBaseAware负责自动注入DataBase对象
public interface DataBaseAware extends Aware { //仅标记//自动注入DataBase对象
    voidsetDataBase(DataBaseConfig dataBaseConfig);
}
  • 3.核心内容就是通过自定义BeanPostProcessor来实现对DataBase对象的自动注入:DataBaseConfigBeanPostProcessor
public class DataBaseConfigBeanPostProcessor implementsBeanPostProcessor, ApplicationContextAware {
  //实现ApplicationContextAware只为获取Bean
privateApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throwsBeansException {//获取当前容器 this.applicationContext=applicationContext;
}

@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throwsBeansException {//获取database对象 Object config = applicationContext.getBean("dataBaseConfig");if (bean==null){ //已经注入bean了 return bean; //直接返回 }if (config instanceof DataBaseConfig &&bean instanceofDataBaseAware){
((DataBaseAware) bean).setDataBase((DataBaseConfig) config);
}
returnbean;
}
}
  • 4.通过实现
    DataBaseAware
    接口并实现相关方法,来接收并访问
    DataBaseConfig
    对象的依赖:MyDataBase
public class MyDataBase implements DataBaseAware{ //定义接口实现子类
    privateDataBaseConfig config;
@Override
public voidsetDataBase(DataBaseConfig dataBaseConfig) {this.config=dataBaseConfig;
}
publicDataBaseConfig getConfig() {returnconfig;
}
}
  • 5.测试配置类:对所有bean进行注册,AwareDataBaseConfig
@Configurationpublic classAwareDataBaseConfig {
@Bean
publicDataBaseConfig dataBaseConfig(){
DataBaseConfig dataBaseConfig
= newDataBaseConfig();
dataBaseConfig.setName(
"测试数据库");
dataBaseConfig.setUrl(
"jdbc:mysql://localhost:3306/test");returndataBaseConfig;
}
@Bean
publicDataBaseConfigBeanPostProcessor dataBaseConfigBeanPostProcessor(){return newDataBaseConfigBeanPostProcessor();
}
@Bean
publicMyDataBase myDataBase(){return newMyDataBase();
}
}
  • 6.启动类测试效果:
@SpringBootApplication
@ComponentScan(
"com.example.demo.aware")public classDemoApplication{public static voidmain(String[] args) {//创建一个AnnotationConfigApplicationContext容器 AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AwareDataBaseConfig.class);
MyDataBase bean
= context.getBean(MyDataBase.class);
System.out.println(bean.getConfig().getClass());
//关闭容器,销毁应用上下文 context.close();
}
}

最终效果图:

总结:

为什么不使用@Autowired` 和 `@Resource` 来用于实现自动依赖注入。这两者通常用于将其他组件或配置信息注入到一个类中,而不需要手动编写大量的依赖注入代码。相比之下,`Aware` 接口是一种更为灵活和手动的依赖注入方式 但代码量要大的多 如果对于已经提供了BeanPostProcessor实现类是一种方便的做法。

  • 更灵活的自定义逻辑:** 使用 `Aware` 接口,你可以在对象初始化的不同阶段执行自定义的逻辑。这对于需要更复杂的依赖注入场景或特定的初始化逻辑非常有用。
  • 特定场景的定制化:** 有时候,你可能需要在对象创建或初始化的过程中执行一些特定的逻辑,而不仅仅是简单的属性注入。`Aware` 接口允许你在需要的时候执行这些逻辑。
  • 不依赖于注解:** 某些项目或团队可能更喜欢避免使用过多的注解,而更倾向于在代码中显式地表达依赖关系。这种情况下,使用 `Aware` 接口可以更符合团队的编码风格。
  • 更细粒度的控制:** `Aware` 接口提供了更细粒度的控制,允许你手动管理依赖注入的过程。这对于一些复杂的业务逻辑可能更容易实现。

总的来说,选择使用 `Aware` 接口还是注解方式取决于项目的需求和开发团队的偏好。注解方式通常更简洁和方便,而 `Aware` 接口提供了更多的自定义和控制的能力。在实际项目中,根据具体情况选择最适合的方式是很重要的。

在将园子的第一款周边鼠标垫
设计图
发出来给大家预览后,上周五我们让厂家印制样品,样品只印制了带文字的蓝色款。

今天收到了厂家寄过来的鼠标垫样品,尺寸是25cm*30cm,厚度是0.5cm,正面是佳积布,背面是天然橡胶(橡胶味道很小),从样品上看我们对这款鼠标垫比较满意。

在寄过来之前厂家拍了照片,实物比照片好看,我们又重新拍了照片,依然没有实物好看,分享一张样品照片给大家看看。

接下来就是确定价格与首批印制数量。

企业公章图片在电子签章业务中应用广泛,在电子签章应用过程中首先需要生成公章图片,然后再使用公章图片结合数字签名技术完成电子签,这样就实现了从可视化到不可篡改的数字化电子签章功能,以下是企业公章图片生成源代码。

点击查看代码
​
import com.resrun.utils.Base64;
import org.apache.pdfbox.io.IOUtils;
import org.springframework.stereotype.Service;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;

/**
 * @Description: 企业签章业务
 * @Package: com.resrun.service.image
 * @ClassName: EntSealGenerateService
 * @copyright 北京资源律动科技有限公司
 */
@Service
public class EntSealGenerateService {



    /**
     * @Description #生成企业签章
     * @Param [topText, middleText]
     * @return byte[]
     **/
    public byte[] generateEntSeal(String topText,String middleText){

        byte[] bytes = export2pic("png", middleText,topText);
        return bytes;
    }




    /**
     * 印章名称距中心点偏移量,按照y轴方向
     */
    private int nameOffset = 50;
    /**
     * 印章宽度
     */
    private int width = 200;
    /**
     * 印章高度
     */
    private int height = 200;
    /**
     * 印章中心标志(默认为五角星)外接圆半径
     */
    private float radius = 30;
    /**
     * 印章名称颜色
     */
    private Color nameColor = Color.RED;
    /**
     * 印章所属单位
     */
//    private String firm;
    /**
     * 印章所属单位颜色
     */
    private Color firmColor = Color.RED;
    private float firmScale = 0.7F;
    /**
     * 边框线宽
     */
    private float borderWidth = 5F;
    /**
     * 边框颜色
     */
    private Color borderColor = Color.RED;
    /**
     * 印章标记(默认为五角星)线宽
     */
    private float signBorderWidth = 3F;
    /**
     * 印章标记颜色
     */
    private Color signBorderColor = Color.RED;
    /**
     * 印章标记填充颜色
     */
    private Color signFillColor = Color.RED;


    public void draw(Graphics2D g2d,String middleText,String topText) {
        // 把绘制起点挪到圆中心点
        g2d.translate(width / 2, height / 2);

        Stroke stroke = g2d.getStroke();// 旧的线性
        // 填充五角星
        Polygon polygon = getPentaclePoints(radius);
        if (signFillColor != null) {
            g2d.setColor(signFillColor);
            g2d.fill(polygon);
        }
        // 绘制五角星边框
        g2d.setStroke(new BasicStroke(signBorderWidth));
        g2d.setColor(signBorderColor);
        g2d.draw(polygon);

        // 绘制印章边框
        g2d.setFont(nameFont);
        g2d.setColor(borderColor);
        g2d.setStroke(new BasicStroke(borderWidth));
        g2d.drawOval(-width / 2, -height / 2, width, height);
        g2d.setStroke(stroke);

        // 绘制印章名称
        g2d.setFont(nameFont);
        g2d.setColor(nameColor);   //g2d.setStroke(new BasicStroke(10F));
        FontMetrics fm = g2d.getFontMetrics();
        int w = fm.stringWidth(middleText);// 名称宽度
        int h = fm.getHeight();// 名称高度
        int y = fm.getAscent() - h / 2;// 求得中心线经过字体的高度的一半时的字体的起绘点
        g2d.drawString(middleText, -w / 2, y + nameOffset);

        // 绘制印章单位
        g2d.setFont(getFirmFont(topText));
        g2d.setColor(firmColor);
        fm = g2d.getFontMetrics();
        h = fm.getHeight();// 字高度

        int count = topText.length();// 字数
        int r = width / 2;// 半径,就假设此印章是个矩形,方便计算
        float angle;
        float start;

        if(count>1 && count <=10){
            angle = 20f;// 字间角度
            start = 90+(360 - angle*(count-1))/2;// 以x轴正向为0,顺时针旋转
        }else if(count > 10){
            angle = (360 - firmAngle) / (count-1);// 字间角度
            start = 90+(360 - angle*(count-1))/2;// 以x轴正向为0,顺时针旋转
        }else{
            angle = 0f;// 字间角度
            start = 90+(360 - angle*(2-1))/2;// 以x轴正向为0,顺时针旋转
        }
        double vr = Math.toRadians(90);// 垂直旋转弧度
        char[] chars = topText.toCharArray();
        for (int i = 0; i < count; i++) {
            char c = chars[i];// 需要绘制的字符
            int cw = fm.charWidth(c);// 此字符宽度
            float a = start + angle * i;// 现在角度
            double radians = Math.toRadians(a);
            g2d.rotate(radians);// 旋转坐标系,让要绘制的字符处于x正轴
            float x = r - h;// 绘制字符的x坐标为半径减去字高度
            g2d.translate(x, 0);// 移动到此位置,此时字和x轴垂直
            g2d.rotate(vr);// 旋转90度,让字平行于x轴
            g2d.scale(firmScale, 1);// 缩放字体宽度
            g2d.drawString(String.valueOf(c), -cw / 2, 0);// 此点为字的中心点
            // 将所有设置还原,等待绘制下一个
            g2d.scale(1 / firmScale, 1);
            g2d.rotate(-vr);
            g2d.translate(-x, 0);
            g2d.rotate(-radians);
        }
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    }

    /**
     * 获取具有指定半径外接圆的五角星顶点
     *
     * @param radius
     *            圆半径
     */
    private Polygon getPentaclePoints(float radius) {
        if (radius <= 0)
            return null;
        float lradius = radius * 0.381966f;// 根据radius求内圆半径
        double halfpi = Math.PI / 180f;
        Point[] points = new Point[10];
        for (int i = 0; i < points.length; i++) {
            if (i % 2 == 1)
                points[i] = new Point(
                        (int) (Math.sin(halfpi * 36 * i) * radius),
                        (int) (Math.cos(halfpi * 36 * i) * radius));
            else
                points[i] = new Point(
                        (int) (Math.sin(halfpi * 36 * i) * lradius),
                        (int) (Math.cos(halfpi * 36 * i) * lradius));
        }
        Polygon polygon = new Polygon();
        for (Point p : points) {
            polygon.addPoint(p.x, p.y);
        }
        return polygon;
    }

    public int getWidth() {
        return width;
    }

    public int getHeight() {
        return height;
    }


    private Font nameFont = new Font("宋体", Font.PLAIN, 19);

    private Font getFirmFont(String topText){
        Font font = null;
        int len = topText.length();
        System.out.println(len);
        if(len==1){
            setFirmAngle(355);
            font = new Font("宋体", Font.PLAIN, 25);
        }else if(len>1 && len<=3){
            setFirmAngle(310);
            font = new Font("宋体", Font.PLAIN, 25);
        }else if(len>3 && len<=6){
            setFirmAngle(250);
            font = new Font("宋体", Font.PLAIN, 25);
        }else if(len>6 && len <=10){
            setFirmAngle(200);
            font = new Font("宋体", Font.PLAIN, 25);
        }else if(len>10 && len<=13){
            setFirmAngle(180);
            font = new Font("宋体", Font.PLAIN, 25);
        }
        else if(len>13 && len<=20){
            font = new Font("宋体", Font.PLAIN, 25);
            setFirmAngle(120);
        }else if(len>20 && len <= 25){
            font = new Font("宋体", Font.PLAIN, 23);

            setFirmAngle(80);
        }else if(len>25 && len < 30){
            setFirmAngle(80);
            font = new Font("宋体", Font.PLAIN, 19);
        }else if(len>=30 && len <= 40){
            setFirmAngle(80);
            font = new Font("宋体", Font.PLAIN, 19);
        }else{
            setFirmAngle(10);
            font = new Font("宋体", Font.PLAIN, 17);
        }
        return font;
    }



    private int firmAngle;
    public void setFirmAngle(int firmAngle){
        this.firmAngle = firmAngle;
    }


    /**
     * 导出此印章为透明背景的图片字节数组.
     *
     * @param format
     *            图片类型,如果为null,则默认为png
     * @return 数组
     * @throws FileNotFoundException
     * @throws IOException
     *             写出图像数据出现问题
     */
    public byte[] export2pic(String format,String middleText,String topText)  {
        int fix = 5;// 宽高修正,如果宽高就为图片宽高,可能边框线被切割
        BufferedImage bi = new BufferedImage(getWidth() + fix * 2, getHeight()
                + fix * 2, 3);


        Graphics2D g2d = bi.createGraphics();
        //防锯齿状毛刺算法
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.translate(fix, fix);
        draw(g2d,middleText,topText);

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try {
            ImageIO.write(bi, format == null ? "png" : format, baos);
            return baos.toByteArray();
        } catch (IOException e) {
            throw new RuntimeException(topText+":生成企业签章失败",e);
        }finally {
            try {
                if(baos!=null)
                    baos.close();
            } catch (IOException e) {
            }
        }

    }
}

​

在日常编程中,我们经常遇到一些看似简单却隐藏着复杂逻辑的问题。

比如,你是否想过为什么在 Java 中表达式
1000==1000
会返回 false,而
100==100
却返回 true 呢?

Integer a = 100;
Integer b = 100;
System.out.println(a == b); // 输出:true

Integer c = 1000;
Integer d = 1000;
System.out.println(c == d); // 输出:false

1、源码追溯

解决问题,一定要深入本质,而解决编程问题,深入本质的方法就是对源码一探究竟。

可能大家不知道
Integer a = 100
这种代码是看哪个源码,不要紧,我们可以看下其编译后的 class 文件。

很明显,我们得看 Integer 类的 valueOf 方法:

继续看 IntegerCache :

为了防止大家不好理解,我这里为这个方法添加了详细注释:

private static class IntegerCache {
    // 缓存的下界值,固定为-128
    static final int low = -128;

    // 缓存的上界值,可以通过系统属性进行配置
    static final int high;

    // 缓存数组,用于存储从low到high范围内的Integer对象
    static final Integer cache[];

    static {
        // 默认情况下,缓存的上界是127
        int h = 127;

        // 尝试从系统属性java.lang.Integer.IntegerCache.high中获取自定义的上界值
        String integerCacheHighPropValue =
            sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
        if (integerCacheHighPropValue != null) {
            try {
                // 将字符串转换为整数
                int i = parseInt(integerCacheHighPropValue);
                // 确保自定义的上界至少为127,以包含Java规范要求的缓存范围
                i = Math.max(i, 127);
                // 确保上界不超过Integer.MAX_VALUE - (-low) - 1,以防止数组大小超出Integer的最大值
                h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
            } catch( NumberFormatException nfe) {
                // 如果字符串无法解析为整数,忽略该属性并保持默认的上界值
            }
        }
        // 设置高界值
        high = h;

        // 初始化缓存数组,数组大小根据low和high计算得出
        cache = new Integer[(high - low) + 1];
        int j = low;
        for(int k = 0; k < cache.length; k++) {
            // 创建Integer对象并填充数组
            cache[k] = new Integer(j++);
        }

        // 断言确保缓存的上界至少为127,符合Java语言规范
        assert IntegerCache.high >= 127;
    }

    // 私有构造器,防止外部实例化这个内部类
    private IntegerCache() {}
}

2、源码解读

其实这部分源码不难理解,首先对于 valueOf 方法,当传入的整型值在 -128-127 之间时,返回的是 IntegerCache 里面的值。

这个 IntegerCache 是在 Java 的 Integer 类中的一个内部静态类 ,它缓存了 -128 到 127 之间的整数。

当我们声明一个 Integer 对象并赋予一个在这个范围内的值时,Java 实际上会返回一个预先创建好的对象引用。

这种机制可以有效减少内存的使用,并提高性能。

3、解答问题

看懂了源码,在回到上面的问题,为什么表达式
1000==1000
会返回 false,而
100==100
却返回 true 呢?

当我们使用
Integer
对象比较两个数时,实际上是在比较对象的内存地址。由于“100”在缓存范围内,两个“100”实际上引用的是同一个对象,所以返回 true。

相反,“1000”不在缓存范围内,即使数值相同,两个“1000”也是不同的对象,因此内存地址不同,返回 false。

4、正确比较

其实对于 Integer 这种包装类比较大小,我们应该使用
equals()
方法来比较两个
Integer
对象的数值,而不是直接使用
==
操作符,除非我们确实想比较对象的引用。

Integer a = 100;
Integer b = 100;
System.out.println(a.equals(b)); // 输出:true

Integer c = 1000;
Integer d = 1000;
System.out.println(c.equals(d)); // 输出:true

这点在
阿里开发手册
中也有详细说明:

如果你是一名Java开发人员,我还是比较推荐你去阅读这个手册,对我们日常编码规范还是挺有帮助的。

下载链接:
https://pan.quark.cn/s/30d2c2c4239a