Java 设计模式(java design patterns)
在前辈们长期开发的过程中,为解决某一类问题,总结出的一种较为优化的代码设计结构.
提高程序代码复用,扩展性,稳定性.
创建型模式、结构型模式和行为型模式 3 种
创建型模式: 主要负责创建对象 单例,工厂
单例模式
在整个程序中只允许有一个对象.
创建对象 使用构造方法. 将构造方法私有化,
只能在本类中创建,这样我们就可以控制数量.
向外界提供一个公共的访问方法.
饿汉式单例 一般又称为急切式单例
在类加载时,就会创建此单例对象,这种写法不会出现线程安全问题
/*
* 饿汉式单例
*/
public class Singleton {
//创建 Singleton 的一个对象
private static Singleton instance = new Singleton();
//让构造函数为 private
private Singleton(){}
//获取唯一可用的对象
public static Singleton getInstance(){
return instance;
}
}
//测试
public class Test {
public static void main(String[] args) {
//获取唯一可用的对象
Singleton object1 = Singleton.getInstance();
Singleton object2 = Singleton.getInstance();
Singleton object3 = Singleton.getInstance();
System.out.println(object1);
System.out.println(object2);
System.out.println(object3);
}
}
懒汉式单例
在类加载时,不会创建单例对象, 在第一次访问时,才会去创建
懒汉式单例有线程安全问题,必须要加锁处理
/*
* 懒汉式单例
*/
public class Singleton {
//定义静态的
private static Singleton instance;
//让构造函数为 private,这样该类就不会被实例化
private Singleton (){}
//向外界提供获取实例的方法 加锁 synchronized 才能保证单例
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
//测试
public class Test {
public static void main(String[] args) {
//获取唯一可用的对象
Singleton object1 = Singleton.getInstance();
Singleton object2 = Singleton.getInstance();
Singleton object3 = Singleton.getInstance();
System.out.println(object1);
System.out.println(object2);
System.out.println(object3);
}
}
工厂模式
批量创建对象, 将创建对象与使用对象分离
简单工厂模式的主要角色如下:
简单工厂(SimpleFactory):是简单工厂模式的核心,负责实现创建所有实例 的内部逻辑。工厂类的创建产品类的方法可以被外界直接调用,创建所需的产品 对象。
抽象产品(Product):是简单工厂创建的所有对象的父类,负责描述所有实例共有的公共接口。
具体产品(ConcreteProduct):是简单工厂模式的创建目标。
其结构图如下图所示。
工厂 负责生产对象的
抽象产品 接口/抽象类 定义
具体产品 实现了抽象的接口/抽象类的具体实现类
以抽象来表示具体.
//抽象产品
public interface Product {
void show();
}
//具体产品1
public class ProductA implements Product {
@Override
public void show() {
System.out.println("具体产品1显示...");
}
}
//具体产品2
public class ProductB implements Product {
@Override
public void show() {
System.out.println("具体产品2显示...");
}
}
/*
* 工厂,负责生产对象
*/
public class SimpleFactory {
public Product createProduct(String className){
if(className == null){
return null;
}else{
try {
return (Product) Class.forName(className).newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
return null;
} catch (IllegalAccessException e) {
e.printStackTrace();
return null;
} catch (ClassNotFoundException e) {
e.printStackTrace();
return null;
}
}
}
}
//测试
public class Test {
public static void main(String[] args) {
SimpleFactory simpleFactory = new SimpleFactory();
Product productA = simpleFactory.createProduct("com.ff.javadesign.simplefactory.ProductA");
Product productB = simpleFactory.createProduct("com.ff.javadesign.simplefactory.ProductB");
}
}
代理模式
代理—>代理商 4s店,中介, 商城
用户 ----> 汽车厂
用户 —> 大众4s店 (代理) 买保险,上牌照 —> 汽车厂
在有些情况下,一个客户不能或者不想直接访问另一个对象,这时需要找一 个中介帮忙完成某项任务,这个中介就是代理对象。例如,购买火车票不一定要 去火车站买,可以通过 12306 网站或者去火车票代售点买。又如找保姆、找工 作,买东西等都可以通过找中介完成。
当用户不想或不能直接去访问目标对象, 可以通过一个中间代理商在用户和目标之间起到一个中间作用.
代理模式优点:
保护目标对象
对目标的功能进行扩展
将用户和目标进行分类,降低了耦合度
模式的结构
- 抽象主题(Subject)类:通过接口或抽象类声明真实主题和代理对象实现
的业务方法。
- 真实主题(Real Subject)类:实现了抽象主题中的具体业务,是代理对象
所代表的真实对象,是最终要引用的对象。
- 代理(Proxy)类:提供了与真实主题相同的接口,其内部含有对真实主题
的引用,它可以访问、控制或扩展真实主题的功能。
其结构图如图所示。
AOP 是什么?
面向切面编程(AOP是Aspect Oriented Program的首字母缩写) ,我们知道,面向对象的特点是继承、多态和封装。而封装就要求将功能分散到不同的对象中去,这在软件设计中往往称为职责分配。实际上也就是说,让不同的类设计不同的方法。这样代码就分散到一个个的类中去了。这样做的好处是降低了代码的复杂程度,使类可重用。
AOP的作用及优势:
作用:在程序运行期间,不修改源码对已有的方法进行增强。
优势:减少重复代码,提高开发效率,维护方便。
AOP相关术语:
Joinpoint(连接点):所谓连接点是指那些被拦截到的点。在Spring中,这些点指的是方法,因为spring只支持方法类型的连接点。
Pointcut(切入点):所谓切入点是指我们要对哪些Joinpoint进行拦截的定义。
Advice(通知/增强):所谓通知是指拦截到Joinpoint之后要做的事情就是五种。通知的类型有五种:分别是前置通知,后置通知,异常通知,最终通知以及环绕通知。
Introduction(引介):引介是一种特殊的通知,是在不修改类代码的前提下,Introduction可以在运行期间为类动态地添加一些方法或Field。
Target(代理对象):代理的目标对象。
Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程,spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。
Proxy(代理):一个类被AOp植入增强后,就产生一个结果代理类。
Apect(切面):是切入点和通知(引介)的结合。
AOP使用方法 xml 注解
基于XML的AOP配置
<aop:config></aop:config>:用于声明开始aop的配置,aop的相关配置都在这个标签之内。
<aop:aspect></aop:aspect>:用于配置切面信息,属性id用于给切面提供一个唯一的表示,属性ref用于引用配置好的通知类bean的id。
<aop:pointcut></aop:pointcut>:用于配置切入点表达式,就是指定对哪些类的那些方法进行增强。属性expression用于定义切入点表达式,id用于给切入点表达式提供一个唯一标识。
<aop:xxx></aop:xxx>:用于配置对应的通知类型,这个标签在<aop:aspect></aop:aspect>标签中使用。
<aop:before></aop:before>:用于配置前置通知,在切入点方法之前执行。
method:用于指定通知类中的增强方法的名称。
pointcut-ref:用于指定切入点的表达式的引用。
pointcut:用于指定切入点表达式。
<aop:after-returning></aop:after-returning> :用于配置后置通知,在切入点方法正常执行后再执行,它和异常通知只能有一个执行,该标签具有的属性同上。
<aop:after-throwing></aop:after-throwing>:用于配置异常通知,在切入点方法发生异常后执行,它和后置通知只能执行一个,该标签具有的属性同上。
<aop:after></aop:after>:用于配置最终通知,无论切入点执行时是否发生异常,它都会在后面执行,该标签具有的属性同上。
<aop:around></aop:around>:用于配置环绕通知,它是spring框架为我们提供的一种可以手动控制增强代码什么时候执行的方式,通常情况下,环绕通知都是独立使用的,该标签具有的属性同上。
execution:对匹配到的方法进行增强,后接表达式,表达式语法:execution([修饰符] 返回值类型 包名.类名.方法名(参数))。
AOP实现原理
代理模式
思想?
分为静态代理
静态代理一般使用于关系是固定的,代理某类事务必须实现接口,
如果需要代理多个目标对象,那么就需要实现更多的接口,后期维护比较麻烦.
/*
Dao接口,定义保存功能
*/
public interface BaseDao {
void save();
}
/*
* 静态代理类
*/
public class StaticDaoProxy implements BaseDao {
//接收所有实现BaseDao接口的实现类对象
private BaseDao baseDao;
// 将被代理者的实例传进动态代理类的构造函数中
public StaticDaoProxy(BaseDao baseDao) {
this.baseDao = baseDao;
}
//代理他们实现功能,可以在调用前,调用后额外添加功能.
@Override
public void save() {
System.out.println("before");
baseDao.save();
System.out.println("after");
}
}
/*
实际功能实现类
*/
public class UserDaoImpl implements BaseDao {
@Override
public void save() {
System.out.println("UserDaoImpl:save()");
}
}
public class Test {
public static void main(String[] args) {
//把实际执行者交给代理对象管理即可
StaticDaoProxy subject = new StaticDaoProxy(new UserDaoImpl());
subject.save();
}
}
动态代理
实现方式有两种
jdk代理
jdk代理实现原理使用的是java反射机制,可以动态获取目标类中代理的方法,
不需要代理类指定的去实现某些抽象接口, 代理的扩展性好.
动态生成代理对象, 要求目标类必需有实现接口.
/*
Dao接口,定义保存功能
*/
public interface BaseDao {
void save();
}
/*
* 动态代理类
*/
public class DynamicDaoProxy implements InvocationHandler {
// 被代理类的实例
private Object object;
// 将被代理者的实例传进动态代理类的构造函数中
public DynamicDaoProxy(Object object) {
this.object = object;
}
/*
* 覆盖InvocationHandler接口中的invoke()方法
* Object proxy 表示代理对象
* Method method 代理对象中的方法
* Object[] args 表示代理方法中的参数
* 更重要的是,动态代理模式可以使得我们在不改变原来已有的代码结构
* 的情况下,对原来的“真实方法”进行扩展、增强其功能,并且可以达到
* 控制被代理对象的行为,下面的before、after就是我们可以进行特殊
* 代码切入的扩展点了。
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before");
Object result = method.invoke(object, args);
System.out.println("after");
return result;
}
}
/*
实际功能实现类
*/
public class UserDaoImpl implements BaseDao {
@Override
public void save() {
System.out.println("UserDaoImpl:save()");
}
}
public class Test {
public static void main(String[] args) {
//我们要代理的真实对象
UserDaoImpl userDaoImpl = new UserDaoImpl();
//我们要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法的
InvocationHandler dynamicProxy = new DynamicDaoProxy(userDaoImpl);
/*
* 通过Proxy的newProxyInstance方法来创建我们的代理对象,我们来看看其三个参数
* 第一个参数 handler.getClass().getClassLoader() ,我们这里使用handler这个类的ClassLoader对象来加载我们的代理对象
* 第二个参数realSubject.getClass().getInterfaces(),我们这里为代理对象提供的接口是真实对象所实行的接口,表示我要代理的是该真实对象,这样我就能调用这组接口中的方法了
* 第三个参数dynamicProxy, 我们这里将这个代理对象关联到了上方的 InvocationHandler 这个对象上
*/
BaseDao baseDao = (BaseDao) Proxy.newProxyInstance(dynamicProxy.getClass().getClassLoader(), userDaoImpl.getClass().getInterfaces(), dynamicProxy);
baseDao.save();
}
}
cglib代理(了解)
动态字节码技术
可以在运行中为目标类动态生成一个子类, 进行方法拦截,从而添加增强功能.
不能代理final所修饰的类, 目标类可以不实现任何接口.
spring中两种实现都支持 可以根据目标类是否实现接口,自动选择动态方式.
/*
* 动态代理类
*/
public class CGLibProxy implements MethodInterceptor {
private Enhancer enhancer = new Enhancer();
public Object getProxy(Class<?> clazz){
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
return enhancer.create();
}
/*
* 拦截所有目标类方法的调用
* 参数:
* obj 目标实例对象
* method 目标方法的反射对象
* args 方法的参数
* proxy 代理类的实例
*/
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
//代理类调用父类的方法
System.out.println("开始事务");
Object obj1 = proxy.invokeSuper(obj, args);
System.out.println("关闭事务");
return obj1;
}
}
//具体主题
public class UserDaoImpl {
public void save() {
System.out.println("UserDaoImpl:save()");
}
}
public class Test {
public static void main(String[] args) {
CGLibProxy proxy = new CGLibProxy();
UserDaoImpl userDaoImpl = (UserDaoImpl) proxy.getProxy(UserDaoImpl.class);
userDaoImpl.save();
}
}
java注解
Java 注解(Annotation)又称 Java 标注,为类,方法,属性,包进行标注, 与java文档注释中的标记不同.
可以被编译到字节码文件中 ,运行时可以通过反射机制获取到注解标签.
内置的注解
java中已经定义好的注解标记
@Override - 检查该方法是否是重写方法。如果发现其父类,或者是引用的接口中并没有
该方法时,会报编译错误。
@Deprecated - 标记过时方法。如果使用该方法,会报编译警告。
@SuppressWarnings - 指示编译器去忽略注解中声明的警告
元注解 (注解的注解)
@Retention - 标识这个注解怎么保存,是只在代码中,还是编入 class 文件中,或者是在
运行时可以通过反射访问。
@Target - 标记这个注解应该是哪种 Java 成员。
对象克隆
//从前端向后端发送的数据 从后端向前端返回的数据 我们通通都使用一个Model类来完成封装
实际中模型类可以分好几中, 例如专门用来接收前端数据的,可以加入验证. 向dao层传输数据的
这里就需要用到对象的克隆.
user1 前端接收的数据
user = new User()
user.setId(user1.getId())
误区
我们常见的 Student stu1 = new Student ();
Student stu2 = stu1 ;
实现方式:
1.在 Java 语言中,通过覆盖 Object 类的 clone()方法可以实现浅克隆。
2.在 spring 框架中提供 BeanUtils.copyProperties(source,target);
克隆又分为浅克隆 和 深克隆
浅克隆
如果一个对象中关联了其他的引用变量, 浅克隆时,只会将关联的对象的引用地址复制出来,并没有创建一个新的对象.
深克隆
如果一个对象中关联了其他的引用变量, 深克隆时,将此对象中所关联的对象也会进行克隆操作,也就是会创建一个新的关联对象
package objectClone.demo2;
public class Address implements Cloneable{
String address;
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "Address{" +
"address='" + address + '\'' +
'}';
}
@Override
protected Address clone() throws CloneNotSupportedException {
return (Address)super.clone();
}
}
package objectClone.demo2;
public class Person implements Cloneable{
int num;
String name;
Address address;
public Person() {
}
public Person(int num, String name) {
this.num = num;
this.name = name;
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
@Override
protected Person clone() throws CloneNotSupportedException {
Person person = (Person)super.clone();
person.address = (Address)address.clone(); //深度复制 联同person中关联的对象也一同克隆.去掉就为浅克隆
return person;
}
@Override
public String toString() {
return "Person{" +
"num=" + num +
", name='" + name + '\'' +
", address=" + address +
'}';
}
}
package objectClone.demo2;
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Address address = new Address();
address.setAddress("汉中");
Person p1 = new Person(100,"jim");
p1.setAddress(address);
Person p2 =p1.clone();
p2.setName("tom");
address.setAddress("西安");
System.out.println(p1);
System.out.println(p2);
}
}
如何实现深克隆
1.多层次克隆 在关联的类中继续克隆
2.使用序列化和反序列化的方式实现
package objectClone.demo4;
import java.io.*;
public class Person implements Serializable {
int num;
String name;
Address address;
public Person() {
}
public Person(int num, String name) {
this.num = num;
this.name = name;
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
/**
* 自定义克隆方法
* @return
*/
public Person myclone() {
Person person = null;
try { // 将该对象序列化成流,因为写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面。所以利用这个特性可以实现对象的深拷贝
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(this);
// 将流序列化成对象
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
person = (Person) ois.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return person;
}
@Override
public String toString() {
return "Person{" +
"num=" + num +
", name='" + name + '\'' +
", address=" + address +
'}';
}
}
package objectClone.demo4;
import java.io.Serializable;
public class Address implements Serializable {
String address;
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "Address{" +
"address='" + address + '\'' +
'}';
}
}
package objectClone.demo4;
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Address address = new Address();
address.setAddress("汉中");
Person p1 = new Person(100,"jim");
p1.setAddress(address);
Person p2 =p1.myclone();
p2.setName("tom");
address.setAddress("西安");
System.out.println(p1);
System.out.println(p2);
}
}