此篇总结下自己所学的知识。
1.使用AOP对controller层的异常进行统一处理
代码如下:
@ControllerAdvice
public class ControllerExceptionHandler {
@ExceptionHandler({Exception.class})
public Map handleException(Exception ex) {
Map<String, Object> resultMap = new HashMap();
resultMap.put("code", 500);
resultMap.put("message", "发生了内部异常,请查看相关日志.");
return resultMap;
}
}
使用@ControllerAdvice和@ExceptionHandler就可以对controller 捕获的异常进行统一处理了,当然是可以指定包名的。
上面的示例代码是没有打印日志的,这个日志肯定是要加的。
2.对集合按照某个字段进行排序
如果我们查询数据库得到了一个集合,需要按照集合的某个字段进行排序,在数据库使用order by不能满足需求。、
那么我们可以在集合上实现Comparable接口,实现compareTo方法,然后使用Collections.sort 排序就可以了
示例代码如下:
@Data
public class StudentVo implements Comparable<StudentVo> {
private String name;
private int age;
private String address;
@Override
public int compareTo(StudentVo studentVo) {
return this.age-studentVo.age;
}
}
测试代码如下:
@Test
public void testStudentVoComparable() {
List<StudentVo> studentVoList = new ArrayList<>();
StudentVo studentVo1 = new StudentVo();
studentVo1.setName("张三");
studentVo1.setAge(20);
studentVoList.add(studentVo1);
StudentVo studentVo2 = new StudentVo();
studentVo2.setName("李四");
studentVo2.setAge(25);
studentVoList.add(studentVo2);
StudentVo studentVo3 = new StudentVo();
studentVo3.setName("王二");
studentVo3.setAge(15);
studentVoList.add(studentVo3);
Collections.sort(studentVoList);
for (StudentVo studentVo : studentVoList) {
System.out.println("年龄:"+studentVo.getAge());
}
}
排序结果:
年龄:15
年龄:20
年龄:25
3.使用动态代理简单的实现一个AOP
spring AOP 原理是由动态代理实现的,可以说是对方法进行了增强,但是因为使用了反射,所以也需要考虑到反射的开销。
Spring AOP有很多注解,诸如:@Before、@After、@Around等。
这里我们来写代码来模拟下。
思路: 首先我们先写一个接口里面有一个方法,然后写一个实现类去实现这个方法,随便打印一段话。
然后写一个Before 和After的接口,这里为了简便就不写成注解了。
再写一个代理工厂,工厂的构造方法有参数 1.需要代理的类 2.before接口 3.after接口
然后写具体的代理方法,直接使用Proxy.newProxyInstance。这个方法有三个参数 ClassLoader、Class<?>[],invocationHandler.
第一个参数是this.getClass().getClassLoader() 。第二个是 this.target.getClass().getInterfaces()。
第三个直接new InvocationHandler(){重写invoke方法}。
代码如下:
public interface AfterAdvice {
void after();
}
public interface BeforeAdvice {
void before() ;
}
public interface People {
void say();
}
public class ChinesePeople implements People {
@Override
public void say() {
System.out.println("我说中文。");
}
}
public class ProxyFactory {
private Object target;
private BeforeAdvice beforeAdvice;
private AfterAdvice afterAdvice;
public Object createProxy() {
ClassLoader loader = this.getClass().getClassLoader();
Class<?>[] interfaces = this.target.getClass().getInterfaces();
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
if (beforeAdvice != null) {
beforeAdvice.before();
}
Object targetObject = method.invoke(target,args);
afterAdvice.after();
return targetObject;
}
};
Object object = Proxy.newProxyInstance(loader,interfaces,handler);
return object;
}
public Object getTarget() {
return target;
}
public void setTarget(Object target) {
this.target = target;
}
public BeforeAdvice getBeforeAdvice() {
return beforeAdvice;
}
public void setBeforeAdvice(BeforeAdvice beforeAdvice) {
this.beforeAdvice = beforeAdvice;
}
public AfterAdvice getAfterAdvice() {
return afterAdvice;
}
public void setAfterAdvice(AfterAdvice afterAdvice) {
this.afterAdvice = afterAdvice;
}
}
public class ProxyDemo {
public void demo() {
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(new ChinesePeople());
BeforeAdvice beforeAdvice = new BeforeAdvice() {
@Override
public void before() {
System.out.println("before ...");
}
};
AfterAdvice afterAdvice = new AfterAdvice() {
@Override
public void after() {
System.out.println("after ...");
}
};
proxyFactory.setBeforeAdvice(beforeAdvice);
proxyFactory.setAfterAdvice(afterAdvice);
People people = (People) proxyFactory.createProxy();
people.say();
}
}
4. 使用MQ 和反射来异步处理数据
需求是这样的, controller层会调用service层,serivce层会调用dao层来插入或者更新数据,像这种情况,如果频繁的调用这种请求,会对数据库是一种不小的压力,所以希望使用MQ来达到异步处理数据、削峰的目的。
思路是这样的。controller层不去调用service层,而调用MQ的生产者,将数据以JSON的形式存储MQ里面,数据包括类名,方法名,入参,入参类名。如果有多个入参的时候,可以组装成一个map 或者弄一个VO去存储。
消费者就获取数据,然后通过反射来调用方法,来插入或者更新数据。
核心代码如下:
@Data
public class MQEventVo<T extends BaseVo> {
//类名
private String className;
//方法名
private String methodName;
private Class<T> methodClass;
//vo入参
private T vo;
public MQEventVo(String className,
String methodName,
T vo,
Class<T> methodClass) {
this.className = className;
this.methodName = methodName;
this.vo = vo;
this.methodClass = methodClass;
}
}
/**
* 点对点消费用户队列 并利用反射执行service方法
*
* @param message 要处理的消息
*/
@JmsListener(destination = QUEUE_USER, containerFactory = QUEUE_LISTENER_CONTAINER_FACTORY)
public <T extends BaseVo>void handlerQueue(String message) {
log.info("MQCustomer start to handler queue message:" + message);
try {
JSONObject jsonObject = JSONObject.parseObject(message);
String className = jsonObject.getString("className");
String methodName = jsonObject.getString("methodName");
String methodClassStr = jsonObject.getString("methodClass");
Class methodClass = Class.forName(methodClassStr);
JSONObject voObject = (JSONObject) jsonObject.get("vo");
T insertData = (T)JSON.toJavaObject(voObject,methodClass);
Method mh = ReflectionUtils.findMethod(SpringContextUtil.getBean(className).getClass(),
methodName, methodClass);
ReflectionUtils.invokeMethod(mh, SpringContextUtil.getBean(className), insertData);
} catch (Exception ex) {
ex.printStackTrace();
}
}
ActiveMQ的配置如下:
package cdm.mq.demo.config;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.command.ActiveMQTopic;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.apache.activemq.command.ActiveMQQueue;
import org.springframework.jms.config.DefaultJmsListenerContainerFactory;
import org.springframework.jms.config.JmsListenerContainerFactory;
import javax.jms.ConnectionFactory;
import javax.jms.Queue;
import javax.jms.Topic;
@Configuration
public class ActiveMQConfig {
public static final String TOPIC_USER = "user.topic";
public static final String QUEUE_USER = "useR.queue";
public static final String QUEUE_LISTENER_CONTAINER_FACTORY = "queueListenerContainerFactory";
public static final String TOPIC_LISTENER_CONTAINER_FACTORY = "topicListenerContainerFactory";
//消息队列的1对多模式
@Bean
public Topic topic() {
return new ActiveMQTopic(TOPIC_USER);
}
//消息队列的点对点模式
@Bean
public Queue queue() {
return new ActiveMQQueue(QUEUE_USER);
}
@Value("${spring.activemq.broker-url}")
private String host;
@Bean
public ConnectionFactory getActiveMqConnection() {
return new ActiveMQConnectionFactory(host);
}
@Bean(name = QUEUE_LISTENER_CONTAINER_FACTORY)
public JmsListenerContainerFactory queueListenerContailerFactory(ConnectionFactory connectionFactory) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
factory.setPubSubDomain(false);
return factory;
}
@Bean(name = TOPIC_LISTENER_CONTAINER_FACTORY)
public JmsListenerContainerFactory topicListenerContainerFactory(ConnectionFactory connectionFactory) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
factory.setPubSubDomain(true);
return factory;
}
}
application.yml文件如下
spring:
datasource:
url: jdbc:mysql://localhost:3306/master0?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
activemq:
broker-url: tcp://localhost:61616
user: admin
password: admin
mybatis:
mapper-locations: classpath*:mapper/*.xml
6. 使用策略模式来减少if else 详见如下:
核心就是实现ApplicationListener
https://blog.csdn.net/zzti_erlie/article/details/102988486
7.使用CountDownLatch来处理线程之间的顺序执行问题
需求是这样的,我现在有N个线程(N大于1),每个线程都有一个number,依次从小到大,现在要这些线程按照number的顺序
去依次输出自己的number。就可以用到计数器了。
在网上找到的示例感觉都不准确,所以这里以一道LeetCode来演示CountDownLatch是如何工作的。
package yc.leetcode;
import java.util.concurrent.CountDownLatch;
public class Problem1114 {
public static void main(String[] args) {
Foo foo = new Problem1114.Foo();
Thread t1 = new Thread(()->{
try {
foo.first(()->{
System.out.println("one");
});
} catch (InterruptedException e) {
e.printStackTrace();
}
},"线程1");
Thread t2 = new Thread(()-> {
try {
foo.second(() -> {
System.out.println("two");
});
} catch (InterruptedException e) {
e.printStackTrace();
}
},"线程2");
Thread t3 = new Thread(()-> {
try {
foo.third(() -> {
System.out.println("three");
});
} catch (InterruptedException e) {
e.printStackTrace();
}
},"线程3");
t2.start();
t3.start();
t1.start();
}
private static class Foo {
private CountDownLatch countDownLatch1;
private CountDownLatch countDownLatch2;
public Foo() {
countDownLatch1 = new CountDownLatch(1);
countDownLatch2 = new CountDownLatch(1);
}
public void first(Runnable printFirst) throws InterruptedException {
// printFirst.run() outputs "first". Do not change or remove this line.
printFirst.run();
countDownLatch1.countDown();
}
public void second(Runnable printSecond) throws InterruptedException {
countDownLatch1.await();
// printSecond.run() outputs "second". Do not change or remove this line.
printSecond.run();
countDownLatch2.countDown();
}
public void third(Runnable printThird) throws InterruptedException {
countDownLatch2.await();
// printThird.run() outputs "third". Do not change or remove this line.
printThird.run();
}
}
}
8.ThreadLocal 为什么建议为static
ThreadLocal即为本地线程变量,它只存储当前线程的变量,对于当前线程来说,所以的对象都是共享的,设置为静态的,
可让当前线程直接就共享此静态变量。即当第一次加载的时候,就分配了一块存储空间,所以此类的对象都可以共享
操作此静态变量。
9.volatile
volatile 使用在多线程中,多修饰变量,此变量直接保存在内存中,保证了此对象的可见性,对于一写多读,
是可以解决可见性问题的。但是对于多写多读是无法解决线程安全问题的。
如果是count++操作,可以使用原子类。比如AtomicLong或者AtomicInteger.
10. finally 中不要加return 因为加了return之后 就不会走 catch中的return了。
执行顺序是 先走try 然后走catch,走里面的return,只是不会返回数据 ,然后走finally,如果finally中有return 直接返回,程序结束。
11. 统计程序运算时间
1.System.currentTimeMillis()
2.System.nanoTime()
3.
Instant start = Instant.now();
Instant end = Instant.now();
long timeElapsed = Duration.between(start, finish).toMillis();
4.使用工具类
StopWatch watch = new StopWatch();
watch.start();
watch.stop();
System.out.println("Time Elapsed: " + watch.getTime() + "ms");
12 线程池原理
ThreadPoolExecutor 首先是线程池的参数,核心线程数、最大线程数、空闲线程保持存活时间、
空闲线程等待时间单元、线程阻塞队列、拒绝策略、创建线程工厂。
一个任务进来后,会判断当前线程数是否大于等于核心线程数,没有则创建一个核心线程,如果有,则任务会进入到等待队列,如果被拒绝,并且当前线程数小于最大线程数,那么会创建新的线程,如果大于最大线程数,则进行执行拒绝策略。
当等待队列是无解队列时,这个时候的最大线程数是无效的。比如LinkedBlockingQueue.
workQueue任务队列):用于保存等待执行的任务的阻塞队列。可以选择以下几个阻塞队列。
ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。
LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列
SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。
PriorityBlockingQueue:一个具有优先级的无限阻塞队列。
13.反射原理
1.将java文件保存到本地磁盘
2.编译生成class文件
3.JVM将class文件放入到JVM内存中去
4.使用反射,就可以通过class文件来获取到这个类的所有属性,方法
反射的三种方式,Person.class Class.forName ,Person person = new Person() ;person.getClass();
14.