标签 java 下的文章

Lombok介绍

Lombok 是一种 Java 实用工具,可用来帮助开发人员消除 Java 的冗长,尤其是对于简单的 Java 对象(POJO)。它通过注释实现这一目的。通过在开发环境中实现 Lombok,开发人员可以节省构建诸如 hashCode() 和 equals() 这样的方法以及以往用来分类各种 accessor 和 mutator 的大量时间。

SpringToolSuite4安装Lombok

下载 lombok.jar

可以通过下面地址进行下载:

https://repo1.maven.org/maven2/org/projectlombok/lombok/1.18.18/lombok-1.18.18.jar

拷贝lombok.jar至STS目录

目录:/Applications/SpringToolSuite4.app/Contents/MacOS

接着需要将lombok-1.18.18.jar重命名为lombok.jar
(下面截图名称没有改,实际操作时,请将lombok-1.18.18.jar改为lombok.jar)

7c2yiab8hz4.png

在STS路径运行命令

cd /Applications/SpringToolSuite4.app/Contents/MacOS

java -jar lombok.jar

- 阅读剩余部分 -

进程(process)和线程(thread)

实现并发最直接的方式是在操作系统级别使用进程。进程是运行在它自己的地址空间内的自包容的程序。
一个线程就是在进程中的一个单一的顺序控制流,单个进程可以拥有多个并发执行任务。

Runnable和Thread

通过Runnable定义任务

线程可以驱动任务,Java中是由Runnable接口提供,需要实现Runnable接口的run()方法。任务run()方法通常总会有某种形式的循环,使得任务一直运行下去直到不再需要。通常run()被写成无限循环的形式。
例如:

public class LiftOff implements Runnable {
    protected int countDown = 10;    //倒计数
    private static int taskCount;
    private final int id = taskCount ++;
    
    public LiftOff() {
        super();
    }
    public LiftOff(int countDown) {
        super();
        this.countDown = countDown;
    }
    
    public String status() {
        return String.format("#%d(%s).", id, (countDown > 0) ? countDown : "LiftOff!");
    }

    @Override
    public void run() {
        while(countDown-- > 0) {
            System.out.print(status());
            Thread.yield();
        }
    }
}

静态方法Thread.yield()

Thread.yield()线程调度器的一种建议,暗示:我已经执行完生命周期中最重要的部分了,现在正是将CPU资源切换给其他任务执行一段时间的时机。

Thread类

Runnable对象转变为工作任务的传统方式是将它提交给一个Thread的构造器。
使用方法如下:

public class BasicThreads {
    public static void main(String[] args) {
        Thread t = new Thread(new LiftOff());
        t.start();
        System.out.println("Waiting for LiftOff");
    }
}

Executor和线程池

Executor是Java SE5引入的,用来管理Thread对象,从而简化并发编程。Executor是启动任务的优选方法

ExecutorService

ExecutorService是具有生命周期的Executor,会管理如何构建恰当的上下文来执行Runnable对象。非常常见的情况是:单个Executor用来创建和管理系统中所有的任务。
在任何线程池中,在现有的线程的可能情况下,都会被自动复用

CachedThreadPool

CachedThreadPool为每个任务都创建一个线程,使用方法:

public class CachedThreadPool {
    public static void main(String[] args) {
        ExecutorService exec = Executors.newCachedThreadPool();
        for(int i = 0; i < 5; i ++) {
            exec.execute(new LiftOff());
        }
        exec.shutdown();
    }
}

- 阅读剩余部分 -

在项目开发时,我们用的是本地搭建的开发dev环境,开发完成打包部署到服务器时,用到的是服务器prod环境。可以借用Mavenprofilesfiltersresources,在运行或打包时指定选用的环境,实现不同环境自动使用各自环境的配置文件或配置信息。

  • profiles:定义环境变量的id;
  • filters:定义了变量配置文件的地址,其中地址中的环境变量就是上面profile中定义的值;
  • resources:定义哪些目录下的文件会被配置文件中定义的变量替换,另外可以指定目录下的文件打包到classes目录下。

定义环境变量profiles

一般环境变量分:dev开发环境、prod发布环境,当然也可以类比添加其他的环境标志。
此处详细可参看:maven profile动态选择配置文件maven profile切换正式环境和测试环境

    <profiles>
        <!-- 开发测试环境 -->
        <profile>
            <id>dev</id>
            <activation>
                <!-- 设置默认激活dev环境的配置 -->
                <activeByDefault>true</activeByDefault>
            </activation>
            <properties>
                <profile.env>dev</profile.env>
            </properties>
        </profile>
        <!-- 产品发布环境 -->
        <profile>
            <id>prod</id>
            <properties>
                <profile.env>prod</profile.env>
            </properties>
        </profile>
    </profiles>

- 阅读剩余部分 -

背景:在业务中,经常需要在执行数据库操作后(事务提交完成),发送消息或事件来异步调用其他组件执行相应的业务操作。
比如:用户注册成功后,发送激活码或激活邮件,如果用户保存后就执行异步操作发送激活码或激活邮件,但是前面用户保存后发生异常,数据库进行回滚,用户实际没有注册成功,但用户收到激活码或激活邮件。此时,我们就迫切要求数据库事务完成后再执行异步操作。

@Autowired
private UserDao userDao;
@Autowired
private JmsProducer jmsProducer;

public User saveUser(User user) {
    // 保存用户
    userDao.save(user);

    // 发送激活码或激活邮件
    jmsProducer.sendEmail(user.getId());
}

// -------------------------------------
public void sendEmail(int userId) {
    /*
     * 获取待接收邮件的用户。(如果上面的保存用户方法还未提交事务,则实际数据还未插入到数据库中,此时会返回null)
    */
    User user = userDao.get(userId);
    // 可能抛NullPointException
    String email = user.getEmail();
    mailService.send(email);
}

解决方案

1、Spring 4.2之后,使用注解@TransactionalEventListener

/**
 * 业务Service
 */
@Service
@Transactional
public class FooService {
    @Autowired
    private  ApplicationEventPublisher applicationEventPublisher;

    public User saveUser(User user) {
        userDao.save(user);
        // 注册事件
        applicationEventPublisher.publishEvent(new SavedUserEvent(user.getId()));
    }
}

// -------------------------------------
/**
 * 保存用户事件
 */
public class SavedUserEvent {
    private int userId;
    
    public SavedUserEvent(int userId) {
        this.userId = userId;
    }
    
    // getter and setter
}

// ---------------------------------
/**
 * 事件侦听,处理对应事件
 */
@Component
public class FooEventListener() {
    @Autowired
    private UserDao userDao;
    @Autowired
    private MailService mailService;

    @TransactionalEventListener
    public sendEmail(SavedUserEvent savedUserEvent) {
        User user = userDao.get(userId);
        String email = user.getEmail();
        mailService.send(email);
    }
}

2.使用TransactionSynchronizationManager 和 TransactionSynchronizationAdapter

@Autowired
private UserDao userDao;
@Autowired
private JmsProducer jmsProducer;

public User saveUser(User user) {
    // 保存用户
    userDao.save(user);
    final int userId = user.getId();

    // 事务提交后调用
    TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
        @Override
        public void afterCommit() {
            jmsProducer.sendEmail(userId);
        }
    });
}

注意:上面的代码将在事务提交后执行.如果在非事务context中将抛出java.lang.IllegalStateException: Transaction synchronization is not active。
改进后代码:

@Autowired
private UserDao userDao;
@Autowired
private JmsProducer jmsProducer;

public User saveUser(User user) {
    // 保存用户
    userDao.save(user);
    final int userId = user.getId();

    // 兼容无论是否有事务
    if(TransactionSynchronizationManager.isActualTransactionActive()) {
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
            @Override
            public void afterCommit() {
                jmsProducer.sendEmail(userId);
            }
        });
    } else {
        jmsProducer.sendEmail(userId);
    }
}

3.在上面2的基础上扩展TransactionSynchronizationAdapter

@Component("afterCommitExecutor")
public class AfterCommitExecutor extends TransactionSynchronizationAdapter implements Executor {
    private static final ThreadLocal<List<Runnable>> RUNNABLES = new ThreadLocal<List<Runnable>>();
    private ThreadPoolExecutor threadPool;

    @PostConstruct
    public void init() {
        Logs.debug("初始化线程池。。。");
        int availableProcessors = Runtime.getRuntime().availableProcessors();
        if (0 >= availableProcessors) {
            availableProcessors = 1;
        }
        int maxPoolSize = (availableProcessors > 5) ? availableProcessors * 2 : 5;
        Logs.debug("CPU Processors :%s MaxPoolSize:%s", availableProcessors, maxPoolSize);
        threadPool = new ThreadPoolExecutor(
            availableProcessors,
            maxPoolSize,
            60,
            TimeUnit.SECONDS,
            new LinkedBlockingQueue<Runnable>(maxPoolSize * 2),
            Executors.defaultThreadFactory(),
            new RejectedExecutionHandler() {
                @Override
                public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                    Logs.debug("Task:%s rejected", r.toString());
                    if (!executor.isShutdown()) {
                        executor.getQueue().poll();
                        executor.execute(r);
                    }
                }
            }
        );
    }

    @PreDestroy
    public void destroy() {
        Logs.debug("销毁线程池。。。");
        if (null != threadPool && !threadPool.isShutdown()) {
            threadPool.shutdown();
        }
    }

    @Override
    public void execute(@NotNull Runnable runnable) {
        if (!TransactionSynchronizationManager.isSynchronizationActive()) {
            runnable.run();
            return;
        }
        List<Runnable> threadRunnables = RUNNABLES.get();
        if (threadRunnables == null) {
            threadRunnables = new ArrayList<Runnable>();
            RUNNABLES.set(threadRunnables);
            TransactionSynchronizationManager.registerSynchronization(this);
        }
        threadRunnables.add(runnable);
    }

    @Override
    public void afterCommit() {
        Logs.debug("事务提交完成处理 ... ");
        List<Runnable> threadRunnables = RUNNABLES.get();
        for (int i = 0; i < threadRunnables.size(); i++) {
            Runnable runnable = threadRunnables.get(i);
            try {
                threadPool.execute(runnable);
            } catch (RuntimeException e) {
                Logs.error("", e);
            }
        }
    }

    @Override
    public void afterCompletion(int status) {
        Logs.debug("事务处理完毕 .... ");
        RUNNABLES.remove();
    }
}

业务层使用:

@Autowired
private UserDao userDao;
@Autowired
private JmsProducer jmsProducer;
@Autowired
private AfterCommitExecutor afterCommitExecutor;

public User saveUser(User user) {
    // 保存用户
    userDao.save(user);
    final int userId = user.getId();

    // 使用AfterCommitExecutor
    afterCommitExecutor.execute(new Runnable() {
        @Override
        public void run() {
            jmsProducer.sendEmail(userId);
        }
    });
  
}

参考文章

Spring的TransactionEventListener
Spring Event 事件中的事务控制
Java Code Examples org.springframework.transaction.support.TransactionSynchronizationAdapter
Spring Events | Baeldung
Thinking about IT: Transaction synchronization callbacks in Spring Framework

最近将之前关于Spring的书重新拿出来温故一番,并进行总结。

Bean容器

Spring的Bean容器其实不止一个,归结起来可以分为2类:

  • Bean工厂(由org.springframework.beans.factory.BeanFactory接口定义),是最简单的Bean容器,提供基本的DI支持;
  • 应用上下文(由org.springframework.context.ApplicationContext接口定义),基于BeanFactory构建,提供应用框架级别的服务。

常用应用上下文

  • AnnotationConfigApplicationContext:从一个或多个基于Java的配置类中加载Spring应用上下文;
  • AnnotationConfigWebApplicationContext:从一个或多个基于Java的配置类中加载Spring Web应用上下文;
  • ClassPathXmlApplicationContext:从类路径下的一个或多个Spring的XML配置文件中加载应用上下文;
  • FileSystemXmlApplicationContext:从文件系统下的一个或多个Spring的XML配置文件中加载应用上下文;
  • XmlWebApplicationContext:从Web应用下的一个或多个Spring的XML配置文件中加载应用上下文。

- 阅读剩余部分 -

XML文档树图

xml_nodetree.gif

<?xml version="1.0" encoding="utf-8"?>
<bookstore> 
  <book category="COOKING"> 
    <title lang="en">Everyday Italian</title>  
    <author>Giada De Laurentiis</author>  
    <year>2005</year>  
    <price>30.00</price> 
  </book>  
  <book category="CHILDREN"> 
    <title lang="en">Harry Potter</title>  
    <author>J K. Rowling</author>  
    <year>2005</year>  
    <price>29.99</price> 
  </book>  
  <book category="WEB"> 
    <title lang="en">Learning XML</title>  
    <author>Erik T. Ray</author>  
    <year>2003</year>  
    <price>39.95</price> 
  </book> 
</bookstore>

一个xml文档由元素节点,属性节点,文本节点构成,其中bookstore被称为文档元素或根元素,也是一个元素节点。XML 文档中的每个成分都是一个节点Node

- 阅读剩余部分 -

Intellij IDEA功能很强大,而且是越用越爱不释手的,下文用IDEA来表示:Intellij IDEA。

自动生成serialVersionUID

默认IDEA是关闭了继承Serializable接口类生成serialVersionUID的警告,如果要让IDEA自动生成,需要进行如下操作:

  1. Files → Settings → Editor → Inspections → Serializable class without 'serialVersionUID',勾选上。默认提醒级别是:Warning,可以根据需要设置成:Error。

IDEA_serialVersionUID.jpg

  1. 将光标放到类名上,按Atl + Enter键,就会提示生成serialVersionUID了。

关闭自动保存和标志修改文件为星号

IDEA默认自动保存文件,而且及时修改了文件也没有*标记。

- 阅读剩余部分 -

Maven运行参数

运行VM参数

-Xms128m -Xmx256m -Xmn32m -XX:PermSize=32M -XX:MaxPermSize=256m
  • XmsXmx:程序运行进程堆内存能占用的最小大小和最大大小;
  • Xmn:用来设置堆内新生代的大小。通过这个值我们也可以得到老生代的大小:-Xmx减去-Xmn;
  • Xss:设置每个线程可使用的内存大小
  • XX:PermSizeXX:MaxPermSize:设置永久代的最小大小和最大大小。注意:Java 8移除该参数,而是用本地元空间Metaspace,大小由-XX:MetaspaceSize-XX:MaxMetaspaceSize进行设置。

常规参数

  • -o:是否离线
  • -U:是否更新Snapshots
  • -X -e:Debug Output
  • -DskipTests=true:编译test,但是忽略允许test(compiles the tests, but skips running them)
  • -Dmaven.test.skip=true:是否忽略test(skips compiling the tests and does not run them)
命令参数命令全称备注
mvn -am--also-make表示同时处理选定模块所依赖的模块
mvn -amd--also-make-dependents表示同时处理依赖选定模块的模块
mvn -B--batch-mode在非交互(批处理)模式下运行(该模式下,当Mven需要输入时,它不会停下来接受用户的输入,而是使用合理的默认值)
mvn -b--builder 设置build构建时的参数
mvn -C--strict-checksums如果校验码不匹配的话,构建失败
mvn -c--lax-checksums如果校验码不匹配的话,产生告警
mvn -cpu--check-plugin-updates对任何相关的注册插件,强制进行最新检查(无效,只保留向后兼容)
mvn -D--define 定义系统属性
mvn -e--errors控制Maven的日志级别,产生执行错误相关消息
mvn -emp--encrypt-master-password 加密主安全密码,存储到Maven settings文件里
mvn -ep--encrypt-password 加密服务器密码,存储到Maven settings文件里
mvn -f--file 强制使用备用的POM文件(或者目录下的pom.xml)
mvn -fae--fail-at-end仅影响构建结果,允许不受影响的构建继续
mvn -ff--fail-fast遇到构建失败就直接退出
mvn -fn--fail-never无论项目结果如何,构建从不失败
mvn -gs--global-settings 全局配置文件的备用路径
mvn -gt--global-toolchains 全局构建链文件的备用路径
mvn -h--help显示帮助信息
mvn -l--log-file 设置构建输出的日志文件(禁用输出颜色)
mvn -llr--legacy-local-repository使用Maven 2遗留的本地存储库行为,即不使用_remote. Repository。也可以使用-Dmaven.legacyLocalRepo=true来激活
mvn -N--non-recursive不递归到子项目中
mvn -npr--no-plugin-registry无效,只保留向后兼容
mvn -npu--no-plugin-updates无效,只保留向后兼容
mvn -o--offline离线模式
mvn -P--activate-profiles 激活的profile,如果多个以逗号分隔
mvn -pl--projects 选项后可跟随{groupId}:{artifactId}或者所选模块的相对路径(多个模块以逗号分隔)
mvn -q--quiet静默输出,仅显示错误信息
mvn -rf--resume-from 表示从指定模块开始继续处理
mvn -s--settings 用户设置文件的替代路径
mvn -t--toolchains 用户构建链文件的备用路径
mvn -T--threads 构建的线程数
mvn -U--update-snapshots强制更新snapshot类型的插件或依赖库(否则maven一天只会更新一次snapshot依赖)
mvn -up--update-plugins无效,只保留向后兼容
mvn -v--version显示版本信息
mvn -V--show-version显示版本信息后继续执行Maven其他目标
mvn -X--debug控制Maven的日志级别,产生执行调试信息

打包成可执行jar

方法一:maven-jar-pluginmaven-dependency-plugin插件

<groupId>your-groupid</groupId>
<artifactId>your-artifactid</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>

<build>
    <plugins>
        <plugin>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.8.1</version>
            <configuration>
                <source>${maven.compiler.source}</source>
                <target>${maven.compiler.target}</target>
                <compilerVersion>${maven.compiler.compilerVersion}</compilerVersion>
                <encoding>${project.build.sourceEncoding}</encoding>
            </configuration>
        </plugin>
        <plugin>
            <artifactId>maven-jar-plugin</artifactId>
            <version>3.2.0</version>
            <configuration>
                <archive>
                    <manifest>
                        <addClasspath>true</addClasspath>
                        <classpathPrefix>libs/</classpathPrefix>
                        <mainClass>your.MainClass</mainClass>
                    </manifest>
                </archive>
            </configuration>
        </plugin>
        <plugin>
            <artifactId>maven-dependency-plugin</artifactId>
            <version>3.2.0</version>
            <executions>
                <execution>
                    <id>copy-dependencies</id>
                    <phase>prepare-package</phase>
                    <goals>
                        <goal>copy-dependencies</goal>
                    </goals>
                    <configuration>
                        <outputDirectory>${project.build.directory}/libs</outputDirectory>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

maven-jar-plugin插件

配置mainClass和指定classpath

addClasspath: 是否在manifest文件中添加classpath。默认为false。如果为true,则会在manifest文件中添加classpath,这样在启动的时候就不用再手动指定classpath了。如下所示,文件中增加了Class-Path一行

maven-dependency-plugin插件

将所依赖的jar包复制到指定目录。

# 打包
mvn package

# 启动
java -jar your-target.jar

方法二:maven-assembly-plugin插件

<groupId>your-groupid</groupId>
<artifactId>your-artifactid</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>

<build>
    <plugins>
        <plugin>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.8.1</version>
            <configuration>
                <source>${maven.compiler.source}</source>
                <target>${maven.compiler.target}</target>
                <compilerVersion>${maven.compiler.compilerVersion}</compilerVersion>
                <encoding>${project.build.sourceEncoding}</encoding>
            </configuration>
        </plugin>
        <plugin>
            <artifactId>maven-assembly-plugin</artifactId>
            <version>3.3.0</version>
            <configuration>
                <descriptorRefs>
                    <descriptorRef>jar-with-dependencies</descriptorRef>
                </descriptorRefs>
                <archive>
                    <manifest>
                        <mainClass>your.MainClass</mainClass>
                    </manifest>
                </archive>
            </configuration>
            <executions>
                <execution>
                    <phase>package</phase>
                    <goals>
                        <goal>single</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

maven-assembly-plugin插件

将所有的东西都打包到一个jar包中。

mvn package打包完成后,会在target文件夹下生成两个jar包,一个是不带依赖的jar包,一个是后缀有-dependencies带有依赖的jar包
# 打包
mvn package

# 启动
java -jar your-target-dependencies.jar

常用插件

Jetty

  • 指定端口: -Djetty.port=xxx(更改xxx设置为自定义端口,比如:8081)

Intellij IDEA常用快捷整理表

Ctrl

快捷键说明
Ctrl + F当前文件进行文本查找
Ctrl + R当前文件进行文本替换
Ctrl + Z撤销
Ctrl + Y删除光标所在行 或 删除选中行
Ctrl + X剪切光标所在行 或 剪切选择内容
Ctrl + C复制光标所在行 或 复制选择内容
Ctrl + D复制光标所在行 或 复制选择内容,并把复制内容插入光标位置下面
Ctrl + W递进式选择代码块。可选中光标所在的单词或段落,连续按会在原有选中的基础上再扩展选中范围
Ctrl + E显示最近打开的文件记录列表
Ctrl + N根据输入名称查找Class类文件
Ctrl + G跳转到指定行
Ctrl + J插入自定义动态代码模板
Ctrl + P方法参数提示显示
Ctrl + Q光标所在的变量 / 类名 / 方法名等上面(也可以在提示补充的时候按),显示文档内容
Ctrl + U前往当前光标所在的方法的父类的方法 / 接口定义
Ctrl + B进入光标所在的方法/变量的接口或是定义处,等效于Ctrl + 左键单击
Ctrl + K版本控制提交项目,需要此项目有加入到版本控制才可用
Ctrl + T版本控制更新项目,需要此项目有加入到版本控制才可用
Ctrl + H显示当前类的层次结构
Ctrl + O选择可重写的方法
Ctrl + I选择可继承的方法
Ctrl + +展开代码
Ctrl + -折叠代码
Ctrl + /注释光标所在行代码,会根据当前不同文件类型使用不同的注释符号
Ctrl + [移动光标到当前所在代码的花括号开始位置
Ctrl + ]移动光标到当前所在代码的花括号结束位置
Ctrl + F1在光标所在的错误代码处显示错误信息
Ctrl + F3调转到所选中的词的下一个引用位置
Ctrl + F4关闭当前编辑文件
Ctrl + F8Debug模式下,设置光标当前行为断点,如果当前已经是断点则去掉断点
Ctrl + F9执行 Make Project 操作
Ctrl + F11选中文件 / 文件夹,使用助记符设定 / 取消书签
Ctrl + F12弹出当前文件结构层,可以在弹出的层上直接输入,进行筛选
Ctrl + Tab编辑窗口切换,如果在切换的过程又加按上delete,则是关闭对应选中的窗口
Ctrl + End跳到文件尾
Ctrl + Home跳到文件头
Ctrl + Space基础代码补全,默认在 Windows 系统上被输入法占用,需要进行修改,建议修改为 Ctrl + ,Alt + /
Ctrl + Delete删除光标后面的单词或是中文句
Ctrl + BackSpace删除光标前面的单词或是中文句
Ctrl + 1,2,3...,9定位到对应数值的书签位置
Ctrl + 左键单击在打开的文件标题上,弹出该文件路径
Ctrl + 光标定位按 Ctrl 不要松开,会显示光标所在的类信息摘要
Ctrl + ←光标跳转到当前单词 / 中文句的左侧开头位置
Ctrl + →光标跳转到当前单词 / 中文句的右侧开头位置
Ctrl + ↑等效于鼠标滚轮向前效果
Ctrl + ↓等效于鼠标滚轮向后效果

- 阅读剩余部分 -