使用框架时有句话叫无代理无框架,无反射无框架,今天就来分析一下代理模式。 使用代理模式,是为了在不修改目标对象的基础上,增强主业务逻辑,客户端想要访问的是目标对象,但是客户类可以真正访问的对象是代理对象,代理类与目标类要实现同一接口。
注意一下几点:
静态代理指, 代理类在程序运行前就已经定义好,其与目标类的关系在程序运行前就已经确立。 类比企业与企业的法律顾问之间的代理关系,这种代理关系不是在“官司”发生后才建立的,而是之前就确立号的一种关系。
静态代理实现转账:
public interface ISomeService {
String doFirst();
}
// 目标类,代理类要增强的类
public class ISomeServiceImp implements ISomeService{
@Override
public String doFirst() {
return "hello";
}
}
// 静态代理类
public class SomeServiceProxy implements ISomeService {
// 声明业务接口对象
private ISomeService target;
public SomeServiceProxy() {
}
// 业务接口对象作为构造器参数,用于接收目标对象
public SomeServiceProxy(ISomeService target) {
super();
this.target = target;
}
// 增强目标方法
@Override
public String doFirst() {
return target.doFirst().toUpperCase();
}
}
public class MyTest {
public static void main(String[] args) {
// 创建目标对象
ISomeService target = new ISomeServiceImp();
// 创建代理对象,并用目标对象初始化
ISomeService service = new SomeServiceProxy(target);
String result = service.doFirst();
System.out.println(result);
}
}
测试结果为静态代理类将doFirst()方法返回结果增强为大写。
注意,静态代理模式与装饰者模式较为相似,但两者有区别,可以参考装饰者设计模式
动态代理指,程序在整个运行过程中根本不存在目标类的代理类,目标对象是由代理工具(如代理工厂类)在程序运行时由JVM根据反射等机制动态生成的。代理对象与目标对象的关系在策划稿内需运行时才确立。 类比于当事人于聘请的律师之年的关系,实在官司发生之后才确立的。
使用JDK的java.lang.reflect.Proxy类进行动态代理,使用newProxyInstance()方法生成动态代理对象。
代码实现:
// 业务接口
public interface ISomeService {
String doSome();
}
// 目标类
public class ISomeServiceImpl implements ISomeService{
@Override
public String doSome() {
return "abc";
}
}
// 动态代理测试类
public class MyTest {
public static void main(String[] args) {
// 定义目标对象
ISomeService target = new ISomeServiceImpl();
// 创建动态代理对象
ISomeService service = (ISomeService) Proxy.newProxyInstance(
target.getClass().getClassLoader(), // 目标类的类加载器
target.getClass().getInterfaces(), // 目标类实现的所有接口
new InvocationHandler() { // 匿名内部类,用于加强主页午逻辑
/**
* proxy 代表生成的代理对象
* method 代表目标方法 doSome()
* args 代表目标方法参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = method.invoke(target, args);
if (result != null) {
// 调用目标方法
result = ((String) method.invoke(target, args)).toUpperCase();
}
return result;
}
});
// 测试代理对象方法
System.out.println("动态代理对象方法: " + service.doSome());
}
}
测试结果输出为:ABC
测试成功
使用Proxy实现动态代理时,要求目标类于代理类实现相同的接口,但是当目标类不存在接口时,无法用该方法实现。 对于无接口的类,通过CGLIB(Code Generation Library, Spring 用他来实现AOP的编程, hibernate用它实现持久对象的字节码的动态生成.)实现,原理是生成目标类的子类,其子类是增强过的,子类对象就是代理对象,所以使用CGLIB要求目标类能够被继承,即不能是final的类。
// 目标类 注意,这次没有接口
public class SomeService {
public String doSome() {
return "abc";
}
}
// 定义CglibFactory用于生成增强子类
public class CglibFactory implements MethodInterceptor{
private SomeService target;
public CglibFactory() {
super();
}
public CglibFactory(SomeService target) {
super();
this.target = target;
}
public SomeService myCglibCreator() {
Enhancer enhancer = new Enhancer();
// 指定父类,即目标类
enhancer.setSuperclass(SomeService.class);
// 设置回调接口对象
enhancer.setCallback(this);
// create()方法用于创建Cgliv动态代理对象
return (SomeService) enhancer.create();
}
// 回调接口的对象, 代理对象执行目标方法时会触发
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
Object result = method.invoke(target, args);
if(result != null) {
result = ((String) result).toUpperCase();
}
return result;
}
}
// 定义测试类
public class MyTest {
public static void main(String[] args) {
SomeService target = new SomeService();
SomeService service = new CglibFactory(target).myCglibCreator();
System.out.println("增强之后: " + service.doSome());
}
}
测试输出ABC,测试成功
在测试时一直报错Exception in thread "main" java.lang.NoClassDefFoundError:org/objectweb/asm/Type
,原因java字节码操作和分析的第三方类库都引用了asm.jar文件,在官网下载asm.jar添加到项目路径就可解决
// TODO
在CglibFactory代码中还涉及到方法回调设计模式,这个以后补充。
之前在过滤器Filter一文中写到过装饰者设计模式,现在重新总结一下这中常用的设计模式。
装饰者设计模式时为了能够在不修改目标类也不适用继承的情况下,动态地扩展一个类的功能。它是通过创建一个包装对象,也就是装饰者来达到增强目标类的目的的。
装饰者设计模式的实现有两个要求:
在装饰者设计模式中,装饰者类一般是不对目标类进行增强的。装饰者类作为一个基类,具体的装饰者继承自这个基类,对目标类进行具体的、单功能的增强。这样做的好处是,在很方便的情况下可以实现多重地、组合式地增强。
代码实现:
// 业务接口
public interface ISomeService {
String doSome();
}
// 目标类
public class ISomeServiceImp implements ISomeService {
@Override
public String doSome() {
return " abc ";
}
}
// 定义装饰者基类
// 要求1. 要有无参构造器
// 要求2. 不对目标类的目标方法进行增强
public class SomeServiceWrapper implements ISomeService{
// 声明装饰类要装饰的目标对象
private ISomeService target;
public SomeServiceWrapper() {
super();
}
// 目标对象通过带参构造器传入
public SomeServiceWrapper(ISomeService target) {
super();
this.target = target;
}
// 调用目标类的目标方法,但不对其进行增强
@Override
public String doSome() {
return target.doSome();
}
}
public class TrimDecorator extends SomeServiceWrapper{
public TrimDecorator() {
super();
}
public TrimDecorator(ISomeService target) {
super(target);
}
@Override
public String doSome() {
// 对目标方法进行增强
return super.doSome().trim();
}
}
public class ToUpperCaseDecorator extends SomeServiceWrapper{
public ToUpperCaseDecorator() {
super();
}
public ToUpperCaseDecorator(ISomeService target) {
super(target);
}
@Override
public String doSome() {
// 增强目标方法
return super.doSome().toUpperCase();
}
}
public class MyTest {
public static void main(String[] args) {
// 创建原始的目标对象
ISomeService target = new ISomeServiceImp();
System.out.println("装饰前的输出: " + target.doSome());
// 进行去空格增强
ISomeService trimService = new TrimDecorator(target);
System.out.println("第一次增强结果: " + trimService.doSome());
// 进行去空格增强
ISomeService toUpperCaseService = new ToUpperCaseDecorator(trimService);
System.out.println("第二次增强结果: " + toUpperCaseService.doSome());
}
}
本文记录一些比较常用的git命令并给出响应的使用场景。 主要参考博客: 廖雪峰git教程 非常感谢廖老师的博客,学习了很多。
git config –global user.name “Your Name” git config –global user.email “email@example.com”
在Git中,用HEAD
表示当前版本,上一个版本就是HEAD^,上上一个版本就是HEAD^^
,当然往上100个版本写100个^比较容易数不过来,所以写成HEAD~100
。
// 查看要回退到哪一个版本 git log // 回退到HEAD的上一个版本 git reset –hard HEAD^ // 查看操作记录 git reflog // 返回到回退前的版本 git reset –hard (commit_id)
git checkout – readme.txt
命令git checkout -- readme.txt
意思就是,把readme.txt
文件在工作区的修改全部撤销,这里有两种情况:
一种是readme.txt自修改后还没有被放到暂存区,现在,撤销修改就回到和版本库一模一样的状态;
一种是readme.txt.已经添加到暂存区后,又作了修改,现在,撤销修改就回到添加到暂存区后的状态。
总之,就是让这个文件回到最近一次git commit
或git add
时的状态。
Git同样告诉我们,用命令git reset HEAD <file>
可以把暂存区的修改撤销掉(unstage),重新放回工作区
git reset HEAD readme.txt
git reset
命令既可以回退版本
,也可以把暂存区的修改回退到工作区
。当我们用HEAD时,表示最新的版本。
以上命令使用场景:
场景1:当你改乱了工作区某个文件的内容,想直接丢弃工作区的修改时,用命令git checkout -- file
。
场景2:当你不但改乱了工作区某个文件的内容,还添加到了暂存区时,想丢弃修改,分两步,第一步用命令git reset HEAD <file>
,就回到了场景1,第二步按场景1操作。
场景3:已经提交了不合适的修改到版本库时,想要撤销本次提交,使用git reset --hard commit_id
回退到相应版本,不过前提是没有推送到远程库。
这两个命令都可以达到合并分支的作用,merge只是合并另外一个分支的内容,rebase也合并另外一个分支的内容,但是会把本分支的commits顶到最顶端。
也就是说rebase主要用来跟上游同步,同时把自己的修改顶到最上面。 另外,尽量及时rebase上游分支,有冲突提前就fix掉,即使我们自己的分支开发了很久(哪怕是几个月),也不会积累太多的conflict,最后合并进主分支的时候特别轻松。
详细内容可参考git merge和git rebase的区别
1.git add -A 提交所有变化 2.git add -u 提交被修改(modified)和被删除(deleted)文件,不包括新文件(new) 3.git add . 提交新文件(new)和被修改(modified)文件,不包括被删除(deleted)文件
这里以Github远程仓库为例,由于你的本地Git仓库和GitHub仓库之间的传输是通过SSH加密的,所以,需要一点设置:
SSH Key
。在用户主目录下(Windows系统在C:\Users\'youraccount'\.ssh
目录下),看看有没有.ssh
目录,如果有,再看看这个目录下有没有id_rsa
和id_rsa.pub
这两个文件,如果已经有了,可直接跳到下一步。如果没有,打开Shell(Windows下打开Git Bash),创建SSH Key:
ssh-keygen -t rsa -C “youremail@example.com”
你需要把邮件地址换成你自己的邮件地址,然后一路回车,使用默认值即可,由于这个Key也不是用于军事目的,所以也无需设置密码。
如果一切顺利的话,可以在用户主目录里找到.ssh目录,里面有id_rsa
和id_rsa.pub
两个文件,这两个就是SSH Key
的秘钥对,id_rsa是私钥,不能泄露出去,id_rsa.pub是公钥,可以放心地告诉任何人。
Add SSH Key
,填上任意Title,在Key文本框里粘贴id_rsa.pub
文件的内容为什么GitHub需要SSH Key呢?因为GitHub需要识别出你推送的提交确实是你推送的,而不是别人冒充的,而Git支持SSH协议,所以,GitHub只要知道了你的公钥,就可以确认只有你自己才能推送。
当然,GitHub允许你添加多个Key。假定你有若干电脑,你一会儿在公司提交,一会儿在家里提交,只要把每台电脑的Key都添加到GitHub,就可以在每台电脑上往GitHub推送了。
// 创建远程仓库 git remote add origin git@github.com:’youaccount’/learngit.git // 推送到远程仓库 git push -u origin master
由于远程库是空的,我们第一次推送master分支时,加上了-u
参数,Git不但会把本地的master分支内容推送的远程新的master分支,还会把本地的master分支和远程的master分支关联起来,在以后的推送或者拉取时就可以简化命令。
1.创建dev分支,然后切换到dev分支:
git checkout -b dev
git checkout命令加上-b
参数表示创建并切换,相当于以下两条命令:
// 新建branch dev git branch dev // 转到dev git checkout dev
用git branch
命令查看当前分支.
2.用git merge 'branch'
命令用于合并指定分支到当前分支。合并之后可以选择用git branch -d dev
删除分支。
3.如果要merge的分支的连个分支同时修改了同一个文件,那么就会出现冲突
git merge feature1 Auto-merging readme.txt CONFLICT (content): Merge conflict in readme.txt Automatic merge failed; fix conflicts and then commit the result.
此时修改产生冲突的文件就可以解决。
git add readme.txt git commit -m “conflict fixed”
3.通常,合并分支时,如果可能,Git会用Fast forward
模式,但这种模式下,删除分支后,会丢掉分支信息。
如果要强制禁用Fast forward
模式,Git就会在merge时生成一个新的commit
,这样,从分支历史上就可以看出分支信息。
以合并dev分支为例,–no-ff参数,表示禁用Fast forward
git merge –no-ff -m “merge with no-ff” dev
在实际开发中,我们应该按照几个基本原则进行分支管理:
首先,master分支应该是非常稳定的,也就是仅用来发布新版本,平时不能在上面干活;
那在哪干活呢?干活都在dev分支上,也就是说,dev分支是不稳定的,到某个时候,比如1.0版本发布时,再把dev分支合并到master上,在master分支发布1.0版本;
你和你的小伙伴们每个人都在dev分支上干活,每个人都有自己的分支,时不时地往dev分支上合并就可以了。
所以,团队合作的分支看起来就像这样:
当你接到一个修复一个代号101的bug的任务时,很自然地,你想创建一个分支issue-101来修复它,但是,等等,当前正在dev上进行的工作还没有提交: Git还提供了一个stash功能,可以把当前工作现场“储藏”起来,等以后恢复现场后继续工作。
$ git stash Saved working directory and index state WIP on master: 747daa8 merge with noff
现在,用git status
查看工作区,就是干净的(除非有没有被Git管理的文件),因此可以放心地创建分支来修复bug。
现在可以开始修复bug,首先确定要在哪个分支上修复bug,假定需要在master分支上修复,就从master创建临时分支
// 切换到master分支 git checkout master // 切到bug分支 git checkout -b issue-101 git add . git commit -m “fix bug 101” // 切到master开始合并分支 git checkout master git merge –no-ff -m “merged bug fix 101” issue-101
bug修好后可以返回到dev分支继续干活.
1.先用git stash list
命令查看之前保存的记录。
2.恢复保存的工作记录,有两种方法.
一是用git stash apply
恢复,但是恢复后,stash内容并不删除,你需要用git stash drop
来删除;另一种方式是用git stash pop
,恢复的同时把stash内容也删了
当然,你可以多次stash,恢复的时候,先用git stash list
查看,然后恢复指定的stash,用命令
git stash apply stash@{0}
小结: 修复bug时,我们会通过创建新的bug分支进行修复,然后合并,最后删除;
当手头工作没有完成时,先把工作现场git stash
一下,然后去修复bug,修复后,再git stash pop
,回到工作现场。
把该分支上的所有本地提交推送到远程库。推送时,要指定本地分支,这样,Git就会把该分支推送到远程库对应的远程分支上。
git push origin master git push origin dev
多人协作时,大家都会往master
和dev
分支上推送各自的修改。
当有xiao’hui’b从GitHub上clone下代码准备开发时,默认情况下只能看到本地的master分支。现在,你的小伙伴要在dev分支上开发,就必须创建远程origin的dev分支到本地,于是他用这个命令创建本地dev分支:
git checkout -b dev origin/dev
git remote -v
;git push origin branch-name
,如果推送失败,先用git pull
抓取远程的新提交git checkout -b branch-name origin/branch-name
,本地和远程分支的名称最好一致;git branch --set-upstream branch-name origin/branch-name
;git pull
,如果有冲突,要先处理冲突。在Git中打标签非常简单.
git checkout <branch>
git tag <name>
就可以打一个新标签.git tag
查看所有标签,也可以使用git tag v0.9 <commit_id>
给对应的commit打标签。注意,标签不是按时间顺序列出,而是按字母排序的。可以用git show <tagname>
查看标签信息.
git tag -a v0.1 -m “version 0.1 released” 1094adb
git tag -d <tag_name>
git push origin <tagname>
,或者,一次性推送全部尚未推送到远程的本地标签git push origin --tags
git tag -d v0.9
, 然后,从远程删除。删除命令也是push,但是格式是git push origin :refs/tags/v0.9
git提供了强大的submodule功能,以便进行多模块的单独管理。 常用功能有:
git clone <repository> --recursive 递归的方式克隆整个项目
git submodule add <repository> <path> 添加子模块
git submodule init 初始化子模块
git submodule update 更新子模块
git submodule foreach git pull 拉取所有子模块
本文在这里进行具体的展开,可以参考博客Git Submodule管理项目子模块
git init
初始化的版本库用户也可以在该目录下执行所有git方面的操作。但别的用户在将更新push上来的时候容易出现冲突。
git init –bare
方法创建一个所谓的裸仓库,之所以叫裸仓库是因为这个仓库只保存git历史提交的版本信息,而不允许用户在上面进行各种git操作,如果你硬要操作的话,只会得到下面的错误This operation must be run in a work tree
简单地总结: 1.git init 创建本地仓库(在工程目录下创建) 2.git init –bare 创建远端仓库(在服务器或者工程目录以外路径都可以创建的备份仓库) 3.工程commit到1中,push到2中 具体可参考git init和git init –bare 的区别
迭代器模式又叫游标(Cursor)模式,是对象的行为模式。迭起器模式可以顺序地访问一个集合中的元素而不必暴露集合的内部表象。
集合对象必须提供适当的方法,使得程序员可以按照一个线性顺序遍历所有的元素对象,把元素对象提取出来或者删除掉等,一个使用集合的系统肯定会使用这些方法操纵集合对象,因此有可能出现两种问题:
出现上述情况的原因是没有符合”开-闭原则”,即没有把不变的结构从系统中抽象出来。
迭代器模式就是将迭代逻辑封装到一个独立的迭代子对象中,从而与集合本身分隔开。 不同的集合对象可以提供相同的迭代器对象,从而客户端无需知道集合的底层结构。 一个集合也可以提供多个迭代器对象,从而是的白能力逻辑的变化不会影响集合对象本身。
代码举例:
public interface Iterator<Item> {
Item next();
boolean hasNext();
}
public interface Aggregate<Item> {
Iterator<Item> CreateIterator();
}
public class ConcreteAggregate<Item> implements Aggregate<Item>{
private Integer[] items;
public ConcreteAggregate() {
items = new Integer[10];
for(int i=0; i<10; i++) {
items[i] = i;
}
}
@Override
public Iterator CreateIterator() {
return new ConcreteIterator<Integer>(items);
}
}
public class ConcreteIterator<Item> implements Iterator<Item>{
private Item[] items;
private int position = 0;
public ConcreteIterator(Item[] items) {
super();
this.items = items;
}
@Override
public Item next() {
return items[position++];
}
@Override
public boolean hasNext() {
return position < items.length;
}
}
public class Test {
public static void main(String[] args) {
ConcreteAggregate<Integer> aggregate = new ConcreteAggregate<>();
Iterator<Integer> iterator = aggregate.CreateIterator();
while (iterator.hasNext()) {
Object next = iterator.next();
System.out.println(next);
}
}
}
为了符合开闭原则,Java容器中采用了迭代器的设计模式,具体可参考迭代器模式
对以下代码分析:
Collection<String> c = new ArrayList<>();
Iterator<String> iterator = c.iterator();
在编译期c.iterator()会检查Collection中有没有iterator()方法。在运行时会调用ArrayList中的iterator()方法。
public Iterator<E> iterator() {
return new Itr();
}
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
// ...
可以看出返回的iterator是由ArrayList实现的。
注意: 1.List底层是数组实现,所以可以支持get(index)方法获取集合对象。 2.List支持ListIterator,可以用hasPrevious()和hasPrevious()实现逆向遍历,但是必须先正向遍历才可,所以一般不使用。 3.并发修改异常:CurrentModificationException
public static <T> List<T> asList(T... a)
可以使用以上方法将数组类型转化为List,但是有几个注意点。 1.asList()的参数为泛型的可变参数,本质是一个数组,要求里面的参数必须是引用类型,即基本类型只能使用包装类型。 2.由于参数本质是数组,所以转为list之后还是数组,不能对数组的长度进行改变,即不能使用add,remove方法,可以使用set方法。 代码举例:
List<String> list = Arrays.asList("hello", "minmin");
list.add("love"); // 报错
list.remove("love"); // 报错
list.set(1, "java"); // 可以执行
异或运算的结构等于无进位相加: 10010, 01101异或结果为 11111
a = a^b
b = a^b
a = a^b
注意,进行上面算法的时候a和b必须是两个不同的空间,但是和值没关系,如 ```java // 满足要求 int a = 10; int b = 10;
// 若i != j,则不满足要求 // 这样运行回导致arr[i] = arr[j] = 0 arr[i] = arr[i] ^ arr[j] arr[j] = arr[i] ^ arr[j] arr[i] = arr[i] ^ arr[j] ```