一,Java开发部分问题
1.异常,log4j日志和 static final 修饰符
自定义异常:自带的异常可能满足不了我们业务的需求, 这个时候我们可以自定义异常来进行处理。
通常情况下,只需要在控制层最外层try异常,处理异常:记录日志,跳转500页面等…
服务层,持久层,其他调用方法如果异常,尽管抛出异常
/**
*
* @Description: 自定义使用异常,重写Exception构造方法即可
*/
public class CustomUseException extends Exception{
}
/**
* 执行
*/
public boolean run(Map<String,String> planMap) {
try {
//任务模板id
String modelId = planMap.get("modelId");
if (!StringUtils.isBlank(modelId)) {
//todo
}
else {
//使用throw 抛出创建的异常
throw new CustomUseException("错误:计划程序没有定义模板");
}
} catch (Exception e) {
//记录日志一定要明确关键信息,使发生异常时能快速定位错误来源,原因
//栗子1:此方法用于执行计划程序,计划程序id,名称则是关键信息。
//发生异常时,一看异常描述就能定位是那个计划程序
//栗子2:如果是修改用户信息,用户名称,id则是关键信息
LOG.error("来源:计划程序名称="+planMap.get("name")+",ID="+planMap.get("id"), e);
}
return true;
}
异常分类
- 1.由于用户行为导致的异常(没有通过验证器,没有查询到结果)
通常不需要记录日志,需要向用户返回信息(比如:登录密码错误,删除系统管理员等) - 2.服务器自身异常(代码错误,调用外部接口错误)
必须记录日志,同时响应前台:可以跳转500页面,或给出提示信息
/**
* 静态LOG对象,定义成static final,logger变量不可变,读取速度快
* static修饰变量是类变量,不管new了多少个实例,也只创建一次
* 一般没有应用到实例的方法或者变量都要求定义成static,避免内存不断被分配和回收
* 不做修改的变量用final修饰符,访问控制符private根据实际情况定义,LOG对象只需在本类使用
*/
private static final Logger LOG = Logger.getLogger(DianmingPlan.class);
/**
* 共有配置可以提取到.properties配置文件,利于后期维护
* 共有变量可以提取成一个常量类Constant,使用public static final定义
* 访问控制符public根据实际情况定义,这里需要都能访问
*/
public static final String PRJ_SOURCE = SysProp.getProp("prj_source");
try{
//dothing
//记录debug级别的信息,debug信息通常用于开发者调试
//LOG.debug("");
//记录info级别的信息
//LOG.info("");
//记录error级别的信息
//LOG.error(e.getMessage(), e);
}catch(SQLException sqlex){
LOG.error("绑定QQ失败!",sqlex);
throw new Exception("绑定QQ失败,请重试!"+sqlex.getMessage() );
}
以下配置参考博客:log4j.properties配置使用详解
#配置根Logger:level,appenderName1,appenderName2
#level是日志级别,Log4j建议只使用四个级别,优先级从高到低分别是ERROR、WARN、INFO、DEBUG
#定义了INFO级别,则应用程序中所有DEBUG级别日志信息将不被打印
#appenderName是日志信息输出位置,可以同时指定多个输出目的地
log4j.rootLogger=INFO,consoleApender,LogFileApender
#appender有以下5种,分别可以将日志信息输出到5个不同的平台
#org.apache.log4j.ConsoleAppender(控制台)
#org.apache.log4j.FileAppender(文件)
#org.apache.log4j.DailyRollingFileAppender(每天产生一个日志文件)
#org.apache.log4j.RollingFileAppender(文件大小到达指定尺寸的时候产生一个新的文件)
#org.apache.log4j.WriterAppender(将日志信息以流格式发送到任意指定的地方)
#日志信息输出到控制台
log4j.appender.consoleApender=org.apache.log4j.ConsoleAppender
#输出信息的格式,ConversionPattern=%m%n:指定怎样格式化指定的消息
log4j.appender.consoleApender.layout=org.apache.log4j.PatternLayout
log4j.appender.consoleApender.layout.ConversionPattern=[%p][%d{yy/MM/dd HH:mm:ss}]%m%n
#指定日志消息的输出最低层次
log4j.appender.consoleApender.Threshold=DEBUG
#日志信息输出到文件
log4j.appender.LogFileApender=org.apache.log4j.DailyRollingFileAppender
#保存log的文件路径
log4j.appender.LogFileApender.File=${catalina.base}/pm_logs/pm.log
#默认 true,添加到末尾,false在每次启动时进行覆盖
log4j.appender.LogFileApender.Append=true
#单个log文件大小,超过大小就又会生成 1个日志,单位:KB,MB,GB
log4j.appender.LogFileApender.MaxFileSize=10MB
#输出信息的格式,ConversionPattern=%m%n:指定怎样格式化指定的消息
log4j.appender.LogFileApender.layout=org.apache.log4j.PatternLayout
log4j.appender.LogFileApender.layout.ConversionPattern=[spdd-run] %-d{yyyy-MM-dd HH\:mm\:ss} %p [%t] %c{1}.%M(%L) | %m%n
#指定日志消息的输出最低层次
log4j.appender.LogFileApender.Threshold=DEBUG
#debug模式下输出sql
log4j.logger.java.sql.ResultSet=DEBUG
2.单例模式
单例模式(确保实例只有一个,尤其是多线程环境),参考博客:静态内部类单例原理
单例模式有饿汉模式、懒汉模式、双重锁懒汉模式、静态内部类模式、枚举模式。
推荐使用静态内部类模式:
/**
* 单例之静态内部类模式
* 类级内部类指有static修饰的成员内部类,如果没有static修饰的成员式内部类被称为对象级内部类,
* 类级内部类相当于外部类的static成分,它的对象与外部类对象间不存在依赖关系,因此可以直接创建,而对象级内部类的实例,是绑定在外部对象实例中的
* 类级内部类相当于外部类的成员,只有在第一次被使用的时候才会被装载。
*/
public class SingleTon {
/**
* 1.私有构造方法
*/
private SingleTon() {
}
/**
* 静态内部类的优点是:
* 1.外部类加载时并不需要立即加载内部类,内部类不被加载则不去初始化INSTANCE,故而不占内存。
* 2.只有当getInstance()方法第一次被调用时,才会去初始化INSTANCE,虚拟机才会加载SingleTonHoler类
* 3.不仅能确保线程安全,也能保证单例的唯一性,同时也延迟了单例的实例化
*/
private static class SingleTonHoler {
private static SingleTon INSTANCE = new SingleTon();
}
/**
* 2.以静态方法返回实例
* @return
*/
public static SingleTon getInstance() {
return SingleTonHoler.INSTANCE;
}
}
3.泛型
泛型,即“参数化类型”,作用:通过泛型指定的类型来控制形参具体限制的类型
提到泛型,用到最多的就是在集合中, 在实际的编程过程中,
可以使用泛型去简化开发,且能很好的保证代码质量。
参考博客:java 泛型详解
1.泛型通配符,<?>
2.泛型方法与可变参数,public void printMsg( T… args)
3.泛型上下边界传入的类型实参必须是指定类型的子类型,
Generic<? extends Number> obj 或 Generic
泛型方法总结:
- 无论何时,尽量使用泛型方法,使用泛型方法将整个类泛型化,
- 静态方法无法访问类上定义的泛型,如果static方法要使用泛型,就必须声明成为泛型方法。
例子:
public final class InstanceFactory {
/**
* 日志
*/
private static Logger LOG = Logger.getLogger(InstanceFactory.class);
private InstanceFactory(){}
/**
* 采用饿汉式单例INSTANCE_MAP对象,作为实例对象的容器
* 类似于Spring的BeanFactroy思想,采用的是延迟加载形式来注入Bean,以达到单例和复用实例对象
*/
private static final HashMap<String, Object> INSTANCE_MAP = new HashMap<>();
/**
* 说明:public与返回值中间<E>非常重要,可以理解为声明此方法为泛型方法。
* 1.只有声明了<E>的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。
* 2.<E>表明该方法将使用泛型类型E,此时才可以在方法中使用泛型类型E。
* 3.与泛型类的定义一样,此处E可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型。
* Java泛型中的标记符含义:
* E - Element (在集合中使用,因为集合中存放的是元素)
* T - Type(Java 类)
* K - Key(键),V - Value(值)
* N - Number(数值类型)
* ? - 表示不确定的java类型
*/
@SuppressWarnings("unchecked")
public static <E> E getInstance(Class<E> clazz) {
if( INSTANCE_MAP .get(clazz.getName()) != null) {
return (E) INSTANCE_MAP .get(clazz.getName()) ;
}
synchronized (LOG) {
E instance = null;
try {
instance = clazz.newInstance();
INSTANCE_MAP.put(clazz.getName(), instance);
} catch ( InstantiationException | IllegalAccessException e) {
LOG.error("[InstanceFactory] 实例化初始失败!"+clazz, e);
}
return instance;
}
}
}
4.反射
对于任意一个类,都能够知道这个类的所有属性和方法;
对于任意一个对象,都能够调用它的任意一个方法和属性;
在运行时期,每个类只会初始化一次,生产一个class对象
4.1获取class对象的三种方式
1)Object.getClass()
2) 任何数据类型(包括基本数据类型)都有一个静态的class属性
3) Class类的ForName(String name) 方法,这个常用,因为不用导包,只需要传入包名+类名
4.2通过反射获取构造,成员方法,属性
4.3反射的作用及使用场景
- 需要访问隐藏属性或者调用方法改变程序原来的逻辑,这个在开发中很常见的,由于一些原因,系统并没有开放一些接口出来,这个时候利用反射是一个有效的解决方法
- 自定义注解,注解就是在运行时利用反射机制来获取的。
- 在开发中动态加载类,比如在Android中的动态加载解决65k问题等等,模块化和插件化都离不开反射。
4.4性能分析
反射机制是一种程序自我分析的能力。用于获取一个类的类变量,构造函数,方法,修饰符。
优点:运行期类型的判断,动态类加载,动态代理使用反射。
缺点:性能问题,反射相当于一系列解释操作,通知jvm要做的事情,性能比直接java的代码要慢很多。
try {
//全路径类名获取Class对象
Class<?> c = Class.forName(clazz);
//获得类中声明的公开方法,如下获取无参Method对象
//参数1是方法名,参数2是按声明顺序标识的方法形参类型
Method md = c.getMethod( taskApiBean.getMethodName() ) ;
Thread run = new Thread() {
public void run() {
//任务需要延时
if(taskApiBean.ge1tApi().waitTime()>0) {
try {
Thread.sleep(taskApiBean.ge1tApi().waitTime()*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
//如果是静态方法
if( Modifier.isStatic(md.getModifiers()) ) {
//Object obj, Object... args
//参数1实例化后的对象,参数2用于方法调用的参数
//调用静态方法Object传入null
md. invoke( null );
}
//如果非静态方法
else {
//根据实例对象调用无参非静态的方法
md. invoke( InstanceFactory.getInstance(c) );
}
} catch (Throwable e) {
SysLog.printDebug("线程反射调用错误:"+clazz+"-"+taskApiBean.getMethodName()+",错误信息:"+e.getMessage());
}
}
};
openThread(run, clazz + "-" + taskApiBean.getMethodName() , taskApiBean.ge1tApi().autoOpen() );
SysLog.printDebug(taskApiBean.getMethodName()+" 开启任务成功!");
}catch (Throwable e) {
SysLog.printDebug( taskApiBean.getMethodName()+" 开启任务失败!"+e.getMessage() );
}
5.过滤器与拦截器
5.1Filter的生命周期
init()方法:初始化参数,在创建Filter时自动调用,init方法也只会执行一次。
doFilter()方法:拦截到要执行的请求时,doFilter就会执行。对请求和响应的预处理,可以执行多次。
destroy()方法:在销毁Filter时自动调用。
@WebFilter("/*")
public class ACharacterEncodingFilter implements Filter{
@Override
public void destroy() {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
//放行
chain.doFilter(request, response);
}
@Override
public void init(FilterConfig arg0) throws ServletException {
}
}
5.2Java实现拦截器,依赖的技术就是 Java 的动态代理
Java原生与Spring框架不同,
Spring中实现HandlerInterceptor即可实现拦截器,preHandle方法在controller调用之前执行
总结:过滤器包裹住servlet,servlet包裹住拦截器
拦截器与过滤器的区别?
1、拦截器不依赖于servlet容器,过滤器的运行必须要容器的支持
2、拦截器是基于java的反射机制的,而过滤器是基于函数回调
3、拦截器只能对action请求起作用,而过滤器对所有请求起作用。
4、拦截器可以访问action上下文、值栈里的对象,而过滤器不能访问
5、在action的生命周期中,拦截器可以多次被调用,而过滤器只能在容器初始化时被调用一次
拦截器可以用来做什么?
1、验证用户登录
2、权限控制
3、日志记录。
6.枚举的使用
参考博客:优先使用注解,慎用枚举
针对类似于 “常量之间存在关联” 的情况使用枚举,毕竟官方是不推荐使用枚举
用法一:常量
/**
* BigDecimal操作枚举定义
*/
public enum BigDecimalOperations{
add, subtract, multiply, divide
}
public static void main(String[] args) {
//枚举类型使用
BigDecimalOperations operations = BigDecimalOperations.add;
switch (operations) {
case add:
//todo
break;
case subtract:
//todo
break;
case multiply:
//todo
break;
case divide:
//todo
break;
}
}
7.连接数据库,Jdbc连接
7.1SpringBoot配置
参考:阿里druid-spring-boot-starter 配置
yml文件
spring:
datasource:
username: lys
password: lys
url: jdbc:mysql://localhost:9909/springboot-mybatis
driver-class-name: com.mysql.jdbc.Driver
#配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
7.2 Jdbc获取连接
mysql.properties文件:
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:9909/dream?useUnicode=true&characterEncoding=UTF-8
username=root
password=aaa
private static String DRIVER_NAME ;
private static String URL;
private static String USER_NAME;
private static String PWD;
static {
InputStream inputStream = DBConnection.class.getClassLoader().getResourceAsStream("db.properties");
Properties properties = new Properties();
try {
properties.load(inputStream);
DRIVER_NAME = properties.getProperty("jdbc.driver");
URL = properties.getProperty("jdbc.url");
USER_NAME = properties.getProperty("jdbc.user");
PWD = properties.getProperty("jdbc.password");
} catch (IOException e) {
e.printStackTrace();
}
}
//获取连接
public static Connection getConnection() {
Connection connection = null;
try {
Class.forName(DRIVER_NAME);
connection = DriverManager.getConnection(URL, USER_NAME,PWD);
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
}
return connection;
}
7.3 Jdbc-Druid获取连接,需要druid-1.1.6.jar
db-druid.properties文件:
driverClassName=oracle.jdbc.driver.OracleDriver
url=jdbc:oracle:thin:@localhost:1521:orcl
username=scott
password=tiger
public class DBPoolConnection {
private static DBPoolConnection dbPoolConnection = null;
private static DruidDataSource druidDataSource = null;
static {
Properties properties = loadPropertiesFile("db-druid.properties");
try {
druidDataSource = (DruidDataSource)DruidDataSourceFactory.createDataSource(properties); //DruidDataSrouce工厂模式
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 数据库连接池单例
* @return
*/
public static synchronized DBPoolConnection getInstance(){
if (null == dbPoolConnection){
dbPoolConnection = new DBPoolConnection();
}
return dbPoolConnection;
}
/**
* 返回druid数据库连接
* @return
* @throws SQLException
*/
public DruidPooledConnection getConnection() throws SQLException{
return druidDataSource.getConnection();
}
/**
* @param string 配置文件名
* @return Properties对象
*/
private static Properties loadPropertiesFile(String fullFile) {
if (null == fullFile || fullFile.equals("")){
throw new IllegalArgumentException("Properties file path can not be null" + fullFile);
}
InputStream inputStream = DBPoolConnection.class.getClassLoader().getResourceAsStream(fullFile);
Properties p =null;
try {
p = new Properties();
p.load(inputStream);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (null != inputStream){
inputStream.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
return p;
}
}
8.Jdbc实现分页+模糊查询
Mysql使用limit分页:LIMIT ?,?,计算规则:
参数1 : (当前是第几页-1) * 每页显示的记录数
参数2:每页显示的记录数
public class PageBean<T> {
private List<T> data;//當前页的数据
private int currentPage = 1;//当前是第几页
private int totalPage;//总页数
private int recordOfPage = 3;//每页显示的记录数
public PageBean(int totalNum) {//totalNum为总记录数
if(totalNum % recordOfPage == 0 ) {
this.totalPage = totalNum / recordOfPage;
}else {
this.totalPage = (totalNum / recordOfPage ) + 1;
}
}
public List<T> getData() {
return data;
}
public void setData(List<T> data) {
this.data = data;
}
public int getCurrentPage() {
return currentPage;
}
public void setCurrentPage(int currentPage) {
this.currentPage = currentPage;
if(currentPage < 1) {
this.currentPage = 1;
}
if(currentPage > totalPage) {
this.currentPage = totalPage;
}
}
public int getTotalPage() {
return totalPage;
}
public int getRecordOfPage() {
return recordOfPage;
}
}
public PageBean<Student> query(PageBean<Student> pageBean,StudentDto studentDto) throws DaoException{
List<Student> students = new ArrayList<>();
try {
StringBuilder sb = new StringBuilder("SELECT * FROM student s LEFT JOIN team t ON s.tid=t.tid where 1=1");
if(studentDto.getMinTime() != null) {
sb.append(" AND entrance>=?");
}
if(studentDto.getMaxTime() != null) {
sb.append(" AND entrance<=?");
}
if(StringTool.isNotEmpty(studentDto.getTeamNum())) {
sb.append(" AND s.tid=?");
}
if(StringTool.isNotEmpty(studentDto.getKeyword())) {
sb.append(" AND name like concat('%', ? ,'%')");
}
sb.append(" LIMIT ?,?");
connection = DruidTool.getConnection();
preparedStatement = connection.prepareStatement(sb.toString());
int index = 1;
if (studentDto.getMinTime() != null) {
preparedStatement.setObject(index, studentDto.getMinTime());
index++;
}
if (studentDto.getMaxTime() != null) {
preparedStatement.setObject(index, studentDto.getMaxTime());
index++;
}
if (studentDto.getTeamNum() != null) {
preparedStatement.setObject(index, studentDto.getTeamNum());
index++;
}
if (StringTool.isNotEmpty(studentDto.getKeyword())) {
preparedStatement.setObject(index, studentDto.getKeyword());
index++;
}
preparedStatement.setInt(index, (pageBean.getCurrentPage()-1) * pageBean.getRecordOfPage());
index++;
preparedStatement.setInt(index, pageBean.getRecordOfPage());
resultSet = preparedStatement.executeQuery();
Student student;
while (resultSet.next()) {
int sid = resultSet.getInt("sid");
String name = resultSet.getString("name");
String sex = resultSet.getString("sex");
int age = resultSet.getInt("age");
String hobby = resultSet.getString("hobby");
String address = resultSet.getString("address");
Integer tid = resultSet.getInt("s.tid");
String tname = resultSet.getString("tname");
Team team = new Team(tid,tname);
String entrance = resultSet.getString("entrance");
student = new Student(sid, name, sex, age, hobby, address, team, entrance);
students.add(student);
}
pageBean.setData(students);
} catch (Exception e) {
logger.error("[StudentDaoImpl query(PageBean<Student> pageBean,StudentDto studentDto)方法]" + e.getMessage());
throw new DaoException(e);
} finally {
this.close();
}
return pageBean;
}
9.IO流
常用的节点流
- 父 类 :
InputStream
、OutputStream
、Reader
、Writer
- 文 件 :
FileInputStream
、FileOutputStrean
、FileReader
、FileWriter
文件进行处理的节点流 - 数 组 :
ByteArrayInputStream
、ByteArrayOutputStream
、CharArrayReader
、CharArrayWriter
对 数组进行处理的节点流(对应的不再是文件,而是内存中的一个数组) - 字符串 :
StringReader
、StringWriter
对字符串进行处理的节点流 - 管 道 :
PipedInputStream
、PipedOutputStream
、PipedReader
、PipedWriter 对管道进行处理的节点流
9.1序列化和反序列化
序列化:对象转换为二进制的形式保存起来或者通过网络进行传输。
反序列化:将二进制信息还原成对象
1、将要序列化的对象所在的类实现Serializable接口
2、使用ObjectOutputStream来进行序列化 ,使用ObjectInputStream来进行反序列化
3、serialVersionUID:用于序列化和反序列化的时候使用同一个类
4、transient修饰符,在序列化的时候不会保存用transient修饰的属性
5、static修饰的属性,序列化的时候也不考虑
6、继承关系,父类不实现Serializable,父类上面的属性是不会被序列化的,如果想序列化,那么父类也要实现Serializable
/**
* 读取对象(反序列化)
*/
@SuppressWarnings("unchecked")
public static HashMap<String, PersonInfo> readConfig() {
String filepath = ConfigUtil.class.getResource("/").getFile();
String webapps = new File(filepath).getParentFile().getParentFile().getParentFile().getPath();
ObjectInputStream ois = null;
try {
File file = new File(webapps + "/zhxq_source/"+A_ID+"/old.obj");
if (file.exists()) {
ois = new ObjectInputStream(new FileInputStream(file));
Object obj = ois.readObject();
return (HashMap<String, PersonInfo>)obj;
}
} catch (Exception e) {
e.printStackTrace();
}finally {
if(ois != null) {
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
/**
* 写入对象(序列化)
* @param map
*/
public static void writeConfig(Map<String, PersonInfo> map) {
String filepath = RefreshPerson.class.getResource("/").getFile();
String webapps = new File(filepath).getParentFile().getParentFile().getParentFile().getPath();
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(new FileOutputStream(webapps + "/zhxq_source/"+A_ID+"/old.obj"));
oos.writeObject(map);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (oos != null) {
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
9.2 ByteArrayOutputStream和ByteArrayInputStream
参考:Java基础-IO流对象之内存操作流(ByteArrayOutputStream与ByteArrayInputStream)
内存操作流是为了临时处理数据而设置的。内存操作流分为
ByteArrayOutputStream
和ByteArrayInputStream
。
ByteArrayOutputStream 常用于存储数据以用于一次写入。
1、ByteArrayOutputStream
类实现了一个输出流,其中的数据被写入一个 byte 数组。缓冲区会随着数据的不断写入而自动增长。可使用 toByteArray()
和 toString()
获取数据。关闭 ByteArrayOutputStream
无效。此类中的方法在关闭此流后仍可被调用,而不会产生任何 IOException。
2、ByteArrayInputStream
包含一个内部缓冲区,该缓冲区包含从流中读取的字节。内部计数器跟踪 read 方法要提供的下一个字节。关闭 ByteArrayInputStream
无效。此类中的方法在关闭此流后仍可被调用,而不会产生任何 IOException。
//ByteArrayOutputStream 常用于存储数据以用于一次写入
public static String getRest(HttpServletRequest request) throws IOException {
InputStream inStream = request.getInputStream();
ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
while ((len = inStream.read(buffer)) != -1) {
outSteam.write(buffer, 0, len);
}
String rest = new String(outSteam.toByteArray(), "utf-8");
outSteam.close();
inStream.close();
return rest;
}
//ByteArrayOutputStream 常用于存储数据以用于一次写入
private String getInputSteam(BufferedImage image) throws IOException {
ByteArrayOutputStream os = new ByteArrayOutputStream();
ImageIO.write(image, "png", os);
InputStream input = new ByteArrayInputStream(os.toByteArray());
return Base64.encode(input);
}
public static void uploadPerImg(String token, String personId, String imgpath) throws Exception {
//上传人脸图片,方式2 从文件流上传
ByteArrayOutputStream btos = new ByteArrayOutputStream();
HttpUtils.downloadFile("http://localhost:81/"+imgpath, btos );
//小程序的图片
ByteArrayInputStream oldImgIn = new ByteArrayInputStream(btos.toByteArray());
//压缩图片后的文件流
ByteArrayOutputStream newImgOut = new ByteArrayOutputStream();
//压缩,通过写入到ByteArrayOutputStream的byte 数组中,缓存了数据
ImgUtils.scaleFromStream(oldImgIn , newImgOut , 260, 300, false);
String url = HTTP+"/CardSolution/common/saveImageToByte?personId="+personId+"&token="+token;
//把输出流转成InputStream输入流,传入byte[]二进制数据创建ByteArrayInputStream,
String rs = HttpUtils.uploadFile(url, "file", null, new ByteArrayInputStream(newImgOut.toByteArray()) ,null);
SysLog.print(rs);
}
二,代码优化必须注意的细节
参考博客:Java代码优化必须注意的细节
1.乘法和除法使用位运算
//在计算机底层,对位运算是最方便、最快的
//因此如果使用算术运算符 + - * / %时,如果算术运算是2的n次方可以使用位运算
for (int val = 0; val < 100; val += 10) {
//等同于 val * 8(2的3次方)
int a = val << 3;
//等同于 val / 2(2的1次方)
int b = val >> 1;
System.out.println(a);
}
2.能确定数组大小尽量使用Array,无法确定才使用ArrayList
分析:数组是JAVA语言内置的数据类型,可以快速的访问其他的元素,缺点不可变。
ArrayList:底层数据结构基于数组实现,当数组长度超出会以原50%长度延长,优点可变
使用:
数组使用toString()打印结果是:[I@18a992f
集合使用toString()可以打印出内部内容,因为集合父类AbstractCollections重写了Object的toString()方法。
//一维数组
String[] array = { "1", "2", "3", "4" };
//二维数组
String[][] array2 = new String[2][];
//java.util.Arrays 类能方便地操作数组,它提供的所有方法都是静态的。
//给数组赋值:通过 fill 方法。
//对数组排序:通过 sort 方法,按升序。
//比较数组:通过 equals 方法比较数组中元素值是否相等。
//查找数组元素:通过 binarySearch 方法能对排序好的数组进行二分查找法操作。
Arrays.fill(array, "6");
//System中提供了一个native静态方法arraycopy(),拷贝数组效率高
//ArrayList模拟add方法
public void add(Object object) {
if(size>=element.length) {
Object[] newArr = new Object[element.length+(element.length>>1)];
//原数组,原数组下标开始,目标数组,目标数组下标开始,拷贝个数
System.arraycopy(element, 0, newArr, 0, element.length);
element = newArr;
}
element[size] = object;
size++;
}
3.Integer数据类型比较不能用==而要用equals方法
”==”比较对象的内存地址
Integer a = 50;
Integer b = 50;
System.out.println(a == b);//true
System.out.println(a.equals(b));//true
a = 500;
b = 500;
System.out.println(a == b);//false
System.out.println(a.equals(b));//true
//原因:Integer类型在比较大小的时候调用了Integer.valueOf()方法,
//值在[-128,127]范围内是直接用的int原始数据类型,而超出了这个范围则是new一个Integer新对象
//比较int则不受影响
int aaa = 500;
int bbb = 500;
System.out.println(aaa == bbb);//true
4.基本数据类型转为字符串,.toString()是最快的方式
1、String.valueOf()方法底层调用了Integer.toString()方法,但是会在调用前做空判断
2、Integer.toString()方法直接调用
3、i + “”底层使用了StringBuilder实现,先用append方法拼接,再用toString()方法获取字符串
三者对比下来,明显是toString()最快、String.valueOf()次之、i + “”最慢。
5.对资源的close()建议分开操作
public static void close(PreparedStatement prepared, Connection connection) {
//不要使用此方式释放资源,如果prepared抛出异常后面则得不到执行
try {
prepared.close();
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
//建议修改为
if (prepared != null) {
try {
prepared.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
6.ThreadLocal使用前或者使用后一定要先remove
基本上现在项目都使用了线程池技术,可以动态配置线程数、可以重用线程。
并且同时还使用ThreadLocal,线程不销毁意味着上条线程set的ThreadLocal.ThreadLocalMap中的数据依然存在
7.对于float和double数据尽量使用BigDecimal类运算
使用float、double类型数据进行计算会出现精度问题,需要使用BigDecimal类运算。
public class BigDecimalUtil {
public static void main(String[] args) throws Exception {
// BigDecimal加减乘时都没有出现问题,但是到除法运算如10/3除不尽时,会报错:没有可精确表示的十进制结果。
// 可以使用BigDecimal.setScale()方法用于格式化小数点
// setScale(1)表示保留一位小数,默认用四舍五入方式
// setScale(1,BigDecimal.ROUND_DOWN)直接删除多余的小数位,如2.35会变成2.3
// setScale(1,BigDecimal.ROUND_UP)进位处理,2.35变成2.4
// setScale(1,BigDecimal.ROUND_HALF_UP)四舍五入,2.35变成2.4
// setScale(1,BigDecimal.ROUND_HALF_DOWN)四舍五入,2.35变成2.3,如果是5则向下舍
//除法运算
System.out.println(BigDecimalUtil.operationASMD(10, 3, BigDecimalOperations.divide, 2, BigDecimal.ROUND_HALF_UP));
}
/**
* BigDecimal操作枚举定义
*/
public enum BigDecimalOperations{
add, subtract, multiply, divide
}
/**
* Bigdecimal加减乘除运算
* @param numOne [String Integer Long Double Bigdecimal] 数值1
* @param numTwo [String Integer Long Double Bigdecimal] 数值2
* @param bigDecimalOpration 操作
* @param scale 保留几位小数
* @param roundingMode 格式化小数方式
* @return
* @throws Exception
*/
public static BigDecimal operationASMD(Object numOne,Object numTwo,BigDecimalOperations bigDecimalOpration,int scale,int roundingMode) throws Exception{
BigDecimal num1 = new BigDecimal(String.valueOf(numOne)).setScale(scale,roundingMode);
BigDecimal num2 = new BigDecimal(String.valueOf(numTwo)).setScale(scale,roundingMode);
switch (bigDecimalOpration){
case add: return num1.add(num2).setScale(scale,roundingMode);
case subtract: return num1.subtract(num2).setScale(scale,roundingMode);
case multiply: return num1.multiply(num2).setScale(scale,roundingMode);
case divide: return num1.divide(num2, scale, roundingMode);
}
return null;
}
}
三,JAVA封装工具类
参考:java常用的工具类
参考:整理收集的一些常用java工具类
/**
* Date自定义格式yyyy-MM-dd HH:mm:ss
* @param date
* @param layout
* @return
*/
public static String dateFormat(Date date, String layout){
if(date == null) return null;
return new SimpleDateFormat(layout).format(date);
}
/**
* 字符串转Date
* @param date 标准格式yyyy-MM-dd HH:mm:ss
* @return
*/
public static Date parseDate(String date){
try {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(date);
} catch (ParseException e) {
return null;
}
}
四,加密算法AES和RSA
1.AES
对称加密有:DES,AES,3DES
AES:对称加解密算法中,最为安全加密算法。
参考:java使用AES加密解密 AES-128-ECB加密
示例:
import org.apache.commons.codec.binary.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
public class AESTest {
/**
* AES加密
* @param sSrc 加密数据
* @param sKey 此处使用AES-128-ECB加密模式,key需要为16位
* @return
* @throws Exception
*/
public static String Encrypt(String sSrc, String sKey) throws Exception {
if (sKey == null) {
System.out.print("Key为空null");
return null;
}
// 判断Key是否为16位
if (sKey.length() != 16) {
System.out.print("Key长度不是16位");
return null;
}
byte[] raw = sKey.getBytes("utf-8");
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");// "算法/模式/补码方式"
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
byte[] encrypted = cipher.doFinal(sSrc.getBytes("utf-8"));
return new Base64().encodeToString(encrypted);// 此处使用BASE64做转码功能,同时能起到2次加密的作用。
}
/**
* 解密
* @param sSrc 加密数据
* @param sKey 此处使用AES-128-ECB加密模式,key需要为16位
* @return
* @throws Exception
*/
public static String Decrypt(String sSrc, String sKey) throws Exception {
try {
// 判断Key是否正确
if (sKey == null) {
System.out.print("Key为空null");
return null;
}
// 判断Key是否为16位
if (sKey.length() != 16) {
System.out.print("Key长度不是16位");
return null;
}
byte[] raw = sKey.getBytes("utf-8");
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, skeySpec);
byte[] encrypted1 = new Base64().decode(sSrc);// 先用base64解密
try {
byte[] original = cipher.doFinal(encrypted1);
String originalString = new String(original, "utf-8");
return originalString;
} catch (Exception e) {
System.out.println(e.toString());
return null;
}
} catch (Exception ex) {
System.out.println(ex.toString());
return null;
}
}
public static void main(String[] args) throws Exception{
//密码:用于加密解密
// String password = "1234567890123456";
String password = "$$1234567890123.";
//加密
String resStr = AESTest.Encrypt("Test content:这是测试内容!", password);
System.out.println("加密后的字串是:"+resStr);
//解密
System.out.println("解密后的字串是:"+AESTest.Decrypt(resStr, password));
}
}
2.RSA
RSA是一种非对称加密算法,使用RSA一般需要产生公钥和私钥,当采用公钥加密时,使用私钥解密;采用私钥加密时,使用公钥解密。
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;
import javax.crypto.Cipher;
import org.apache.commons.codec.binary.Base64;
public class RSATest {
private static Map<Integer, String> keyMap = new HashMap<Integer, String>(); //用于封装随机产生的公钥与私钥
/**
* RSA公钥加密
*
* @param str 加密字符串
* @param publicKey 公钥
* @return 密文
* @throws Exception 加密过程中的异常信息
*/
public static String encrypt(String str, String publicKey) throws Exception {
//base64编码的公钥
byte[] decoded = Base64.decodeBase64(publicKey);
RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(decoded));
//RSA加密
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
String outStr = Base64.encodeBase64String(cipher.doFinal(str.getBytes("UTF-8")));
return outStr;
}
/**
* RSA私钥解密
*
* @param str
* 加密字符串
* @param privateKey
* 私钥
* @return 铭文
* @throws Exception
* 解密过程中的异常信息
*/
public static String decrypt(String str, String privateKey) throws Exception{
//64位解码加密后的字符串
byte[] inputByte = Base64.decodeBase64(str.getBytes("UTF-8"));
//base64编码的私钥
byte[] decoded = Base64.decodeBase64(privateKey);
RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(decoded));
//RSA解密
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, priKey);
String outStr = new String(cipher.doFinal(inputByte));
return outStr;
}
/**
* 随机生成密钥对
* @throws NoSuchAlgorithmException
*/
public static void genKeyPair() throws NoSuchAlgorithmException {
// KeyPairGenerator类用于生成公钥和私钥对,基于RSA算法生成对象
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
// 初始化密钥对生成器,密钥大小为96-1024位
keyPairGen.initialize(1024,new SecureRandom());
// 生成一个密钥对,保存在keyPair中
KeyPair keyPair = keyPairGen.generateKeyPair();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); // 得到私钥
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); // 得到公钥
String publicKeyString = new String(Base64.encodeBase64(publicKey.getEncoded()));
// 得到私钥字符串
String privateKeyString = new String(Base64.encodeBase64((privateKey.getEncoded())));
// 将公钥和私钥保存到Map
keyMap.put(0,publicKeyString); //0表示公钥
keyMap.put(1,privateKeyString); //1表示私钥
}
public static void main(String[] args) throws Exception{
//生成公钥和私钥
genKeyPair();
//加密字符串
String message = "Test content:这是测试内容!";
System.out.println("随机生成的公钥为:" + keyMap.get(0));
System.out.println("随机生成的私钥为:" + keyMap.get(1));
String messageEn = encrypt(message, keyMap.get(0));
System.out.println(message + "\t加密后的字符串为:" + messageEn);
String messageDe = decrypt(messageEn, keyMap.get(1));
System.out.println("还原后的字符串为:" + messageDe);
}
}
3.MD5
MD5特点:
1.md5是不可逆算法
2.会被破解,解决方式:可以多次MD5加密,也可在加密时加盐值
作用:md5可用于数字签名,及文件完整性验证以及口令加密等方面
参考:JAVA中获取文件MD5值的四种方法
md5加密的几种方法
1.原生的加密方法
import java.security.MessageDigest;
public class TestMD5 {
//盐,用于混交md5
private static final String SALT = "&%1A2Asc*&%$$#@";
/**
* 原生的加密方法
*
* @param value 需要加密的字符串
* @return 加密后的32位字符串
*/
public static String md5(String value) {
try {
value = value + SALT;
MessageDigest messageDigest = MessageDigest.getInstance("MD5");
messageDigest.update(value.getBytes("UTF8"));
byte message[] = messageDigest.digest();
String result = "";
for (int i = 0; i < message.length; i++) {
result += Integer.toHexString((0x000000FF & message[i]) | 0xFFFFFF00).substring(6);
}
return result;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static void main(String[] args) {
/**
* 需要加密的密码尽可能的要复杂,包含大小写字母和数字,
* 长度不小于8为,加密必须加盐,如果需要更高要求,可以多次加密,多次加盐
*/
System.out.println(md5("12ASD23klk935"));
//2596d9bb596d17aa85b4f2655a5e430f
}
}
2.使用到shiro权限框架
import org.apache.shiro.crypto.hash.SimpleHash;
public static void main(String[] args) {
String hashAlgorithmName = "MD5";//加密方式
Object crdentials = "520";//密码原值
Object salt = "&%1A2Asc*&%$$#@";//盐值
int hashIterations = 1024;//加密1024次
Object result = new SimpleHash(hashAlgorithmName,crdentials,salt,hashIterations);
System.out.println(result);
}
3.使用到security权限框架
参考:使用Spring Security下的BCryptPasswordEncoder进行密码加密
BCryptPasswordEncoder的加密是带salt的加密,它的salt也是嵌在加密后的密文中的,所以不用保存salt。
encode用来加密,一个matches用来解密
@Autowired
private BCryptPasswordEncoder passwordEncoder;
public void changePassword(String username, String oldPassword, String newPassword) {
SysUser u = userDao.getUser(username);
if (u == null) {
throw new IllegalArgumentException("用户不存在");
}
//shi
if (!passwordEncoder.matches(oldPassword, u.getPassword())) {
throw new IllegalArgumentException("旧密码错误");
}
userDao.changePassword(u.getId(), passwordEncoder.encode(newPassword));
log.debug("修改{}的密码", username);
}