0%

AOP(Aspect Orient Programming),一般称为面向切面编程,作为面向对象的一种补充,用于处理系统中分布于各个模块的横切关注点,比如事务管理、日志、缓存等等。AOP实现的关键在于AOP框架自动创建的AOP代理,AOP代理主要分为静态代理和动态代理,静态代理的代表为AspectJ;而动态代理则以Spring AOP为代表。静态代理是编译期实现,动态代理是运行期实现,可想而知前者拥有更好的性能。本文主要介绍Spring AOP的两种代理实现机制,JDK动态代理和CGLIB动态代理,关于Java代理

核心概念

  • 增强(Adivce)
  • 切点(Pointcut)
  • 连接点(Join point)
  • 切面(Aspect)
  • 引入(Introduction)
  • 织入(Weaving)

Spring 的 AOP 代理由 Spring 的 IoC 容器负责生成、管理,其依赖关系也由 IoC 容器负责管理。因此,AOP 代理可以直接使用容器中的其他 Bean 实例作为目标,这种关系可由 IoC 容器的依赖注入提供。
aop开发时,其中需要程序员参与的只有 3 个部分:

  • 定义普通业务组件。
  • 定义切入点,一个切入点可能横切多个业务组件。
  • 定义增强处理,增强处理就是在 AOP 框架为普通业务组件织入的处理动作。

什么是Spring AOP

Spring AOP面向切面编程,将日志、事务等相对独立且重复的功能抽取出来,利用Spring的配置文件或者注解的形式将这些功能织入进去,提高了复用性。

采用技术:AOP 实现的关键就在于 AOP 框架自动创建的 AOP 代理,AOP 代理则可分为静态代理和动态代理两大类,其中静态代理是指使用 AOP 框架提供的命令进行编译,从而在编译阶段就可生成 AOP 代理类,因此也称为编译时增强;而动态代理则在运行时借助于 JDK 动态代理、CGLIB 等在内存中“临时”生成 AOP 动态代理类,因此也被称为运行时增强。

Spring AOP 采用的是动态代理,在运行期间对业务方法进行增强,所以不会生成新类,对于动态代理技术,Spring AOP提供了对JDK动态代理的支持以及CGLib的支持。前者是基于反射技术的实现,后者是基于继承的机制实现。Spring AOP默认使用AOP代理的标准JDK动态代理。这使得任何接口(或接口集)都可以被代理。Spring AOP也可以使用CGLIB代理。这是代理类而不是接口所必需的。默认情况下,如果业务对象未实现接口,则使用CGLIB。由于优化的做法是编程接口而不是类,业务类通常实现一个或多个业务接口。

业务中的一些切面

Read more »

Spring IoC容器:Spring通过控制反转实现了松散耦合,对象们给出它们的依赖,而不是创建或查找依赖的对象们。开发人员在开发过程中,把对象的创建和销毁交给了spring容器,在使用的时候,只需要向容器申请就好了。这样就是的程序实现了解耦。

Spring IOC容器和bean介绍

IoC也被称为依赖注入(DI)。它是一个过程,对象通过构造函数参数,工厂方法的参数或在工厂方法构造或返回后在对象实例上设置的属性来定义它们的依赖关系,即它们使用的其他对象。容器在创建bean时会注入这些依赖关系。这个过程基本上是相反的,因此名为Inversion of Control(IoC),通过使用类的直接构造或诸如Service Locator模式之类的机制来控制其依赖关系的实例化或位置的bean本身。

org.springframework.beansorg.springframework.context包是Spring框架的IoC容器的基础。该 BeanFactory接口提供了一种能够管理任何类型对象的高级配置机制。 ApplicationContext 是BeanFactory 的一个子接口。它增加了与Spring的AOP功能更容易的集成; 消息资源处理(用于国际化),事件发布; 和特定于应用层的上下文(例如,WebApplicationContext 用于Web应用程序中)。

Spring容器

Sping的容器可以分为两种类型

  • BeanFactory:org.springframework.beans.factory.BeanFactory 接口是最简单的容器,提供了基本的 DI 支持。最常用的 BeanFactory 实现就是 XmlBeanFactory 类,它根据XML文件中的定义加载beans,该容器从XML文件读取配置元数据并用它去创建一个完全配置的系统或应用。

  • ApplicationContext 应用上下文:org.springframework.context.ApplicationContext 基于BeanFactory之上构建,并提供面向应用的服务。该接口org.springframework.context.ApplicationContext 表示 Spring IoC 容器,并负责实例化,配置和组装上述bean。容器通过读取配置元数据获取有关要实例化,配置和组装的对象的指示信息。配置元数据用XML,Java注释或Java代码表示。它允许您表示组成应用程序的对象以及这些对象之间丰富的相互依赖关系。

在Spring框架中的核心组件只有三个:Core、Context 和 Bean。它们构建起了整个Spring的骨骼架构,没有它们就不可能有AOP、Web等特性功能。

Read more »

在实际项目开发中,线上数据和开发测试的环境不可能是一样的,不然每次上线还要进行删除测试数据,而进行版本迭代的时候,开发人员可以之间操作线上用户数据进行开发,作为一个开发人员,你觉得这样合理吗?
针对上面的问题,在实际项目开发过程中,一般就会出现不同的开发环境,比如有开发环境,测试环境,线上环境。但是对于一些中小型公司来说,其实数据库、Redis什么的,只需要两套,一套是线上正式环境,一套开发测试环境。

SpringBoot

对于SpringBoot来说,进行多环境开发配置,很简单的,看下面的:

通过修改配置文件的命名,在application.yml中选择自己需要加载那那个配置文件。并在相对于的配置文件中加入下面的配置。

1
2
project:
name: production

注意: application.yml中的名字必须和你命名的配置问价后面部分相同。

1
2
3
4
## 配置正式和测试库,分别是production/test
spring:
profiles:
active: test

对于启动jar包,选择不同的配置环境:

1
java -jar <jar包> --spring.profiles.active=test/production
Read more »

之前有点傻傻的分不清内存模型和内存结构的区别,以为都是指的是JVM。直到前段时间在一篇博客上看到这两者的区别,才知道这两者指的是不同的东西。

内存结构

内存结构就是我们常说的JVM,比如,堆、栈等。这些就是Java的内存结构。

关于JVM可以看 深入理解Java虚拟机(周志明) 写的这本书。下面两篇博客是我看这本书的一些笔记:

内存模型

Java内存模型(即Java Memory Model,简称JMM)本身是一种抽象的概念,并不真实存在,它描述的是一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式。Java虚拟机定义JMM来屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的内存访问效果。由于JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作内存(有些地方称为栈空间),用于存储线程私有的数据,而Java内存模型中规定所有变量都存储在主内存,主内存是共享内存区域,所有线程都可以访问,但线程对变量的操作(读取赋值等)必须在工作内存中进行,首先要将变量从主内存拷贝的自己的工作内存空间,然后对变量进行操作,操作完成后再将变量写回主内存,不能直接操作主内存中的变量,工作内存中存储着主内存中的变量副本拷贝,前面说过,工作内存是每个线程的私有数据区域,因此不同的线程间无法访问对方的工作内存,线程间的通信(传值)必须通过主内存来完成,其简要访问过程如下图:

简单的总结,Java多线程之间通过共享内存来进行通信,但由于采用共享通信,在通信过程中会出现可见性、原子性、顺序性等问题,而JMM就是围绕多线程通信以及其相关的一系列特性建立的模型。

Read more »

之前接手一个别人刚写好的项目,丢到服务器上跑了没几天,服务器上别的服务都不能使用了。查看阿里云控制台,发现内存爆满了,只能先把这个服务先下,保证别的服务正常使用,还好是在测试阶段,还没正式上线。
对于内存泄露,一开始以为是Java I/O操作没有关闭,导致Java JVM内存泄露。但是发现项目中的I/O操作都已经把做了close操作。最后使用 jconsole 来监控本地内存的变化,发现是不断的创建线程,导致内存的不足,本地长时间跑项目出现下面的错误,这个和通过jconsole监控内存得出的结果差不多。

1
java.lang.OutOfMemoryError: unable to create new native thread

于是就到项目中看了下,项目中对于线程的创建,终于知道什么问题了。项目中线程的创建没有通过线程池,而是直接new Thread和通过 ScheduledExecutorService 定时创建线程,导致线程耗尽内存。

在阿里的Java开发手册中,就指出项目中的对于线程的创建,必须通过线程池,不能私自创建线程。关于线程池可以看这篇博客。

Read more »

声明:如果本文有错误,希望指出。

在Java中,map是一个非常常用的。在平时,一般使用,HashMap就可以了,但是HashMap不是线程安全的。JDK为我们解决了这个问题,它为HashMap提供了一个线程安全的高效版本 —— ConcurrentHashMap。在ConcurrentHashMap中,无论是读操作还是写操作都能保证很高的性能:在进行读操作时(几乎)不需要加锁,而在写操作时通过锁分段技术(JAVA8之前)只对所操作的段加锁而不影响客户端对其它段的访问。特别地,在理想状态下,ConcurrentHashMap 可以支持 16 个线程执行并发写操作(如果并发级别设为16),及任意数量线程的读操作

JDK8之前的实现

segment

在Java8之前的ConcurrentHashMap中,采用的是分段加锁来解决线程安全问题。默认情况下内部按并发级别为16来创建。对于每个segment的容量,默认情况也是16。当然并发级别(concurrentLevel)和每个段(segment)的初始容量都是可以通过构造函数设定的。
下面是segment的代码:

1
2
3
4
5
static class Segment<KV> extends ReentrantLock implements Serializable {
private static final long serialVersionUID = 2249069246763182397L;
final float loadFactor;
Segment(float lf) { this.loadFactor = lf; }
}

Segment继承了ReentrantLock,表明每个segment都可以当做一个锁。这样对每个segment中的数据需要同步操作的话都是使用每个segment容器对象自身的锁来实现。只有对全局需要改变时锁定的是所有的segment。

读写

对于ConcurrentHashMap,在读取的时候不使用锁,它没有使用同步控制,交给segment去找

Read more »

之前对于MySQL数据库的字符串排序没有深入了解,今天帮同事看这个问题,就深入的了解下。先看下数据库的表,我想查询字母倒序,字符串倒序的一个数据。

根据上面的要求,第一个版本的SQL是下面的样子

1
select level from test order by level desc limit 1

结果发现最后的数据是 B9。咦,这是什么问题,网上看了下,字符的排序规则是先对第一个字符串排序,再下一个字符串,就这样排序,所以就出现之前的问题。于是出现了第二个版本的SQL查询。

1
2
3
4
SELECT MID(level,2)
from test
order by left(level,1) DESC , CAST(MID(level,2) AS UNSIGNED) DESC
limit 1

从上面的SQL中可以看出,对于字符串的数字,可以使用CAST(MID(level,2) AS UNSIGNED)来转化。在网上查询资料的时候,也发现了下面两种查询

1
2
select * from table where 1  order by id+0 desc;  
select * from table where 1 order by id*1 desc;

数据库空格对字符串的查找的影响

MySQL 数据库对字符串影响,空格在字符串的前面和中间是会影响字符串,在字符串后面的空格,MySQL 会忽略。

Read more »

声明:如果本文有错误,希望指出。

数据库中的各种锁可以前往数据库锁机制查看。Java中提供的各种锁可以实现并发编程。
锁是用来控制多个线程访问共享资源的方式,一辩来说,一个锁能够防止多个线程同时访问共享资源。

可重入锁

可重入锁 ,也叫做递归锁,指的是同一线程外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响。

在JAVA环境下 ReentrantLock 和 Synchronized 都是 可重入锁
可重入锁最大的作用是避免死锁。

自旋锁

自旋锁是采用让当前线程不停地在循环体内执行,当循环的条件被其他线程改变时才能进入临界区。

自旋锁只是将当前线程不停地执行循环体,不进行线程状态的改变,所以响应速度更快。但当线程数不断增加时,性能下降明显,因为每个线程都需要执行,会占用CPU时间片。如果线程竞争不激烈,并且保持锁的时间段。适合使用自旋锁。

独享锁

独享锁是指该锁一次只能被一个线程所持有。ReentrantLock 、Synchronized 都是独享锁。

Read more »

如果本文有错误,希望指出。

今天在重新看阿里Java手册的时候,看到了ThreadLocal,就想对ThreadLocal进一步了解下。

在讲ThreadLocal之前,先去了解了下SimpleDateFormat为什么不是线程安全的。先来看下SimpleDateFormat的部分源码,这个在网上应该也有讲解。

可以看这个 **原因**,讲解的挺详细的。

ThreadLocal

ThreadLocal为变量在每个线程中都创建了一个副本,所以每个线程可以访问自己内部的副本变量,不同线程之间不会互相干扰。Java中的ThreadLocal类允许我们创建只能被同一个线程读写的变量。因此,如果一段代码含有一个ThreadLocal变量的引用,即使两个线程同时执行这段代码,它们也无法访问到对方的ThreadLocal变量。

Read more »

如果本文有错误,希望指出。

在项目中,线程是一种稀缺资源,频繁的创建和销毁,对于系统的性能有着很大的消耗。线程池是线程资源复用的典范之作,通过维护一个一定数量的线程集合,在需要运行线程任务的时候直接从这个集合中取出一个线程去运行任务,而不是重新创建一个。这点在阿里Java手册上也提出了:

使用线程池可以带来一系列好处:

  • 降低资源消耗:通过池化技术重复利用已创建的线程,降低线程创建和销毁造成的损耗。
  • 提高响应速度:任务到达时,无需等待线程创建即可立即执行。
  • 提高线程的可管理性:线程是稀缺资源,如果无限制创建,不仅会消耗系统资源,还会因为线程的不合理分布导致资源调度失衡,降低系统的稳定性。使用线程池可以进行统一的分配、调优和监控。
  • 提供更多更强大的功能:线程池具备可拓展性,允许开发人员向其中增加更多的功能。比如延时定时线程池ScheduledThreadPoolExecutor,就允许任务延期执行或定期执行。

在Java中提供了几种线程池创建的方法,不过这几种方法都是最后通过 ThreadPoolExecutor 来创建线程的。在开始讲Java提供的几种之前,先讲下 ThreadPoolExecutor 中几个参数的含义。

ThreadPoolExecutor

内部工作原理

  • corePoolSize :池中所保存的线程数,包括空闲线程,除非设置 allowCoreThreadTimeOut 参数,否则创建后会一直存活
  • maximumPoolSize:池中允许的最大线程数
  • keepAliveTime:非核心线程空闲线程等待新任务的最长时间,即线程数多余核心数的线程,会被销毁掉
  • unit:keepAliveTime 参数的时间单位
  • workQueue :执行前用于保持任务的队列。此队列仅保持由 execute方法提交的 Runnable任务
  • threadFactory:执行程序创建新线程时使用的工厂
  • handler :由于超出线程范围和队列容量而使执行

线程池运行流程

Read more »