-
点击 File -> Project Structure(快捷键 Ctrl + Alt + Shift + s),点击Project Structure界面左侧的“Modules”显示下图界面
-
在 “Dependencies” 标签界面下,点击 “+”号,选择第一个选项“JARs or directories…”,选择相应的jar包,点“OK”,jar包添加成功。
分类: JAVA
Spring 学习
一.使用正常方式
study_spring/src/applicationContext.xml
1 2 3 4 5 6 7 8 | <bean name= "c" class = "com.how2java.pojo.Category" > <property name= "name" value= "category 1" /> </bean> <bean name= "p" class = "com.how2java.pojo.Product" > <property name= "name" value= "Product 1" /> <property name= "category" ref= "c" /> </bean> |
/study_spring/src/cn/leokim/pojo/Product.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | package cn.leokim.pojo; public class Product { private int id; private String name; private Category category; public int getId() { return id; } public void setId( int id) { this .id = id; } public String getName() { return name; } public void setName(String name) { this .name = name; } public Category getCategory(){ return category; } public void setCategory(Category category){ this .category = category; } } |
study_spring/src/cn/leokom/test/TestSpring.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | package cn.leokim.pojo; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.how2java.pojo.Category; import com.how2java.pojo.Product; public class TestSpring { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext( new String[] { "applicationContext.xml" }); // Category c = (Category) context.getBean("c"); Product p = (Product) context.getBean( "p" ); // System.out.println(c.getName()); System.out.println(p.getCategory().getName()); } } |
二.注解方式
study_spring/src/applicationContext.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | <?xml version= "1.0" encoding= "UTF-8" ?> <beans xmlns= "http://www.springframework.org/schema/beans" xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance" xmlns:aop= "http://www.springframework.org/schema/aop" xmlns:tx= "http://www.springframework.org/schema/tx" xmlns:context= "http://www.springframework.org/schema/context" xsi:schemaLocation=" http: //www.springframework.org/schema/beans http: //www.springframework.org/schema/beans/spring-beans-3.0.xsd http: //www.springframework.org/schema/aop http: //www.springframework.org/schema/aop/spring-aop-3.0.xsd http: //www.springframework.org/schema/tx http: //www.springframework.org/schema/tx/spring-tx-3.0.xsd http: //www.springframework.org/schema/context http: //www.springframework.org/schema/context/spring-context-3.0.xsd"> <context:annotation-config/> <bean name= "c" class = "com.how2java.pojo.Category" > <property name= "name" value= "category 1" /> </bean> <bean name= "p" class = "com.how2java.pojo.Product" > <property name= "name" value= "Product 1" /> <!-- <property name= "category" ref= "c" />--> </bean> </beans> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | public class Product { private int id; private String name; // @Autowired @Resource (name= "c" ) private Category category; public int getId() { return id; } public void setId( int id) { this .id = id; } public String getName() { return name; } public void setName(String name) { this .name = name; } public Category getCategory(){ return category; } public void setCategory(Category category){ this .category = category; } } |
注解方式还可以更简单
1.applicationContext.xml去掉之前的配置,只用一行
1 | <context:component-scan base- package = "cn.leokim.pojo" /> |
2.在Category上添加注解
1 2 | @Component ( "c" ) public class Category { |
3.在product上添加注解
1 2 | @Component ( "p" ) public class Product { |
运行出来结果是一样的
Java 并发
通过New Thread的方式
LiftOff.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | package concurrency; public class LiftOff implements Runnable{ protected int countDown = 10 ; //Default private static int taskCount = 0 ; private final int id = taskCount++; public LiftOff(){} // public LiftOff(int countDown){ // this.countDown = countDown; // } public String status(){ return "#" + id + " (" + (countDown > 0 ? countDown : "Liftoff!" ) + ")" ; } public void run() { while (countDown-- > 0 ){ System.out.println(status()); Thread.yield(); } } } |
使用了总是分配给main()的那个线程
MainThread.java
1 2 3 4 5 6 7 8 | package concurrency; public class MainThread { public static void main(String[] args) { LiftOff launch = new LiftOff(); launch.run(); } } |
Thread构造器只需要一个Runnable对象,调用Thread对象的start()方法为该线程执行必须的初始化操作.
然后调用 Runnable的run()方法,以便在这个新线程中启动该任务.
BasicThreads.java
1 2 3 4 5 6 7 8 9 | package concurrency; public class BasicThreads { public static void main(String[] args) { Thread t = new Thread( new LiftOff()); t.start(); System.out.println( "Waiting for LiftOff" ); } } |
Java中泛型 类型擦除
Java 泛型(Generic)的引入加强了参数类型的安全性,减少了类型的转换,但有一点需要注意:Java 的泛型在编译器有效,在运行期被删除,也就是说所有泛型参数类型在编译后都会被清除掉,看下面一个列子,代码如下:
1 2 3 4 5 6 | public class Foo { public void listMethod(List<String> stringList){ } public void listMethod(List<Integer> intList) { } } |
代码很简单,看起来没什么问题,但是编译器却报出如下错误信息:
Method listMethod(List<String>) has the same erasure listMethod(List<E>) as another method in type Foo
此错误的意思是说listMethod(List<String>) 方法在编译时擦除类型后的方法是listMethod(List<E>),它与另外一个方法重复,也就是方法签名重复。反编译之后的方法代码如下:
1 2 3 | public void listMethod(List list) { } |
从上面代码可以看出 Java 编译后的字节码中已经没有泛型的任何信息,在编译后所有的泛型类型都会做相应的转化,转化如下:
List<String>、List<T> 擦除后的类型为 List。
List<String>[]、List<T>[] 擦除后的类型为 List[]。
List<? extends E>、List<? super E> 擦除后的类型为 List<E>。
List<T extends Serialzable & Cloneable> 擦除后类型为 List<Serializable>。
Java 为什么这么处理呢?有以下两个原因:
避免 JVM 的大换血。如果 JVM 将泛型类型延续到运行期,那么到运行期时 JVM 就需要进行大量的重构工作了,提高了运行期的效率。
版本兼容。 在编译期擦除可以更好地支持原生类型(Raw Type)。
明白了 Java 泛型是类型擦除的,下面的问题就很好理解了:
(1) 泛型的 class 对象是相同的
每个类都有一个 class 属性,泛型化不会改变 class 属性的返回值,例如:
1 2 3 4 5 | public static void main(String[] args) { List<String> ls = new ArrayList<String>(); List<Integer> li = new ArrayList<Integer>(); System.out.println(ls.getClass() == li.getClass()); } |
代码返回值为 true,原因很简单,List<String> 和 List<Integer> 擦除后的类型都是 List。
(2) 泛型数组初始化时不能声明泛型类型
如下代码编译时通不过:
List<String>[] list = new List<String>[];
在这里可以声明一个带有泛型参数的数组,但是不能初始化该数组,因为执行了类型擦除操作后,List<Object>[] 与 List<String>[] 就是同一回事了,编译器拒绝如此声明。
(3) instanceof 不允许存在泛型参数
以下代码不能通过编译,原因一样,泛型类型被擦除了。
1 2 | List<String> list = new ArrayList<String>(); System.out.println(list instanceof List<String>) |
错误信息如下:
Cannot perform instanceof check against parameterized type List<String>. Use the form List<?> instead since further generic type information will be erased at runtime
以下转自:Java泛型:类型檫除、模板和泛型传递
类型擦除
正确理解泛型概念的首要前提是理解类型擦除(type erasure)。 Java中的泛型基本上都是在编译器这个层次来实现的。在生成的Java字节代码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会被编译器在编译的时候去掉。这个过程就称为类型擦除。如在代码中定义的List<Object>和List<String>等类型,在编译之后都会变成List。JVM看到的只是List,而由泛型附加的类型信息对JVM来说是不可见的。Java编译器会在编译时尽可能的发现可能出错的地方,但是仍然无法避免在运行时刻出现类型转换异常的情况。类型擦除也是Java的泛型实现方式与C++模板机制实现方式之间的重要区别。
很多泛型的奇怪特性都与这个类型擦除的存在有关,包括:
泛型类并没有自己独有的Class类对象。比如并不存在List<String>.class或是List<Integer>.class,而只有List.class。
静态变量是被泛型类的所有实例所共享的。对于声明为MyClass<T>的类,访问其中的静态变量的方法仍然是 MyClass.myStaticVar。不管是通过new MyClass<String>还是new MyClass<Integer>创建的对象,都是共享一个静态变量。
泛型的类型参数不能用在Java异常处理的catch语句中。因为异常处理是由JVM在运行时刻来进行的。由于类型信息被擦除,JVM是无法区分两个异常类型MyException<String>和MyException<Integer>的。对于JVM来说,它们都是 MyException类型的。也就无法执行与异常对应的catch语句。
类型擦除的基本过程也比较简单,首先是找到用来替换类型参数的具体类。这个具体类一般是Object。如果指定了类型参数的上界的话,则使用这个上界。把代码中的类型参数都替换成具体的类。同时去掉出现的类型声明,即去掉<>的内容。比如T get()方法声明就变成了Object get();List<String>就变成了List。接下来就可能需要生成一些桥接方法(bridge method)。这是由于擦除了类型之后的类可能缺少某些必须的方法。比如考虑下面的代码:
1 2 3 4 5 | class MyString implements Comparable<String> { public int compareTo(String str) { return 0 ; } } |
当类型信息被擦除之后,上述类的声明变成了class MyString implements Comparable。但是这样的话,类MyString就会有编译错误,因为没有实现接口Comparable声明的int compareTo(Object)方法。这个时候就由编译器来动态生成这个方法。
实例分析
了解了类型擦除机制之后,就会明白编译器承担了全部的类型检查工作。编译器禁止某些泛型的使用方式,正是为了确保类型的安全性。以上面提到的List<Object>和List<String>为例来具体分析:
1 2 3 4 5 6 7 8 9 10 | public void inspect(List<Object> list) { for (Object obj : list) { System.out.println(obj); } list.add( 1 ); //这个操作在当前方法的上下文是合法的。 } public void test() { List<String> strs = new ArrayList<String>(); inspect(strs); //编译错误 } |
这段代码中,inspect方法接受List<Object>作为参数,当在test方法中试图传入List<String>的时候,会出现编译错误。假设这样的做法是允许的,那么在inspect方法就可以通过list.add(1)来向集合中添加一个数字。这样在test方法看来,其声明为List<String>的集合中却被添加了一个Integer类型的对象。这显然是违反类型安全的原则的,在某个时候肯定会抛出ClassCastException。因此,编译器禁止这样的行为。编译器会尽可能的检查可能存在的类型安全问题。对于确定是违反相关原则的地方,会给出编译错误。当编译器无法判断类型的使用是否正确的时候,会给出警告信息。
通配符与上下界
在使用泛型类的时候,既可以指定一个具体的类型,如List<String>就声明了具体的类型是String;也可以用通配符?来表示未知类型,如List<?>就声明了List中包含的元素类型是未知的。 通配符所代表的其实是一组类型,但具体的类型是未知的。List<?>所声明的就是所有类型都是可以的。但是List<?>并不等同于List<Object>。List<Object>实际上确定了List中包含的是Object及其子类,在使用的时候都可以通过Object来进行引用。而List<?>则其中所包含的元素类型是不确定。其中可能包含的是String,也可能是 Integer。如果它包含了String的话,往里面添加Integer类型的元素就是错误的。正因为类型未知,就不能通过new ArrayList<?>()的方法来创建一个新的ArrayList对象。因为编译器无法知道具体的类型是什么。但是对于 List<?>中的元素确总是可以用Object来引用的,因为虽然类型未知,但肯定是Object及其子类。考虑下面的代码:
1 2 3 | public void wildcard(List<?> list) { list.add( 1 ); //编译错误 } |
如上所示,试图对一个带通配符的泛型类进行操作的时候,总是会出现编译错误。其原因在于通配符所表示的类型是未知的。
因为对于List<?>中的元素只能用Object来引用,在有些情况下不是很方便。在这些情况下,可以使用上下界来限制未知类型的范围。 如List<? extends Number>说明List中可能包含的元素类型是Number及其子类。而List<? super Number>则说明List中包含的是Number及其父类。当引入了上界之后,在使用类型的时候就可以使用上界类中定义的方法。比如访问 List<? extends Number>的时候,就可以使用Number类的intValue等方法。
类型系统
在Java中,大家比较熟悉的是通过继承机制而产生的类型体系结构。比如String继承自Object。根据Liskov替换原则,子类是可以替换父类的。当需要Object类的引用的时候,如果传入一个String对象是没有任何问题的。但是反过来的话,即用父类的引用替换子类引用的时候,就需要进行强制类型转换。编译器并不能保证运行时刻这种转换一定是合法的。这种自动的子类替换父类的类型转换机制,对于数组也是适用的。 String[]可以替换Object[]。但是泛型的引入,对于这个类型系统产生了一定的影响。正如前面提到的List<String>是不能替换掉List<Object>的。
引入泛型之后的类型系统增加了两个维度:一个是类型参数自身的继承体系结构,另外一个是泛型类或接口自身的继承体系结构。第一个指的是对于 List<String>和List<Object>这样的情况,类型参数String是继承自Object的。而第二种指的是 List接口继承自Collection接口。对于这个类型系统,有如下的一些规则:
相同类型参数的泛型类的关系取决于泛型类自身的继承体系结构。即List<String>是Collection<String> 的子类型,List<String>可以替换Collection<String>。这种情况也适用于带有上下界的类型声明。
当泛型类的类型声明中使用了通配符的时候, 其子类型可以在两个维度上分别展开。如对Collection<? extends Number>来说,其子类型可以在Collection这个维度上展开,即List<? extends Number>和Set<? extends Number>等;也可以在Number这个层次上展开,即Collection<Double>和 Collection<Integer>等。如此循环下去,ArrayList<Long>和 HashSet<Double>等也都算是Collection<? extends Number>的子类型。
如果泛型类中包含多个类型参数,则对于每个类型参数分别应用上面的规则。
理解了上面的规则之后,就可以很容易的修正实例分析中给出的代码了。只需要把List<Object>改成List<?>即可。List<String>是List<?>的子类型,因此传递参数时不会发生错误。
开发自己的泛型类
泛型类与一般的Java类基本相同,只是在类和接口定义上多出来了用<>声明的类型参数。一个类可以有多个类型参数,如 MyClass<X, Y, Z>。 每个类型参数在声明的时候可以指定上界。所声明的类型参数在Java类中可以像一般的类型一样作为方法的参数和返回值,或是作为域和局部变量的类型。但是由于类型擦除机制,类型参数并不能用来创建对象或是作为静态变量的类型。考虑下面的泛型类中的正确和错误的用法。
1 2 3 4 5 6 7 8 9 10 11 | class ClassTest<X extends Number, Y, Z> { private X x; private static Y y; //编译错误,不能用在静态变量中 public X getFirst() { //正确用法 return x; } public void wrong() { Z z = new Z(); //编译错误,不能创建对象 } } |
泛型传递
即泛型可以当作参数在不同的实例化的类中传递,理论上来说可以无限制层次的传递下去。最终会约束每一层的方法或者类型的泛型确定,在《泛型传递》这篇文章中对具体的用法进行详尽的描述。
最佳实践
在使用泛型的时候可以遵循一些基本的原则,从而避免一些常见的问题。
在代码中避免泛型类和原始类型的混用。比如List<String>和List不应该共同使用。这样会产生一些编译器警告和潜在的运行时异常。当需要利用JDK 5之前开发的遗留代码,而不得不这么做时,也尽可能的隔离相关的代码。
在使用带通配符的泛型类的时候,需要明确通配符所代表的一组类型的概念。由于具体的类型是未知的,很多操作是不允许的。
泛型类最好不要同数组一块使用。你只能创建new List<?>[10]这样的数组,无法创建new List<String>[10]这样的。这限制了数组的使用能力,而且会带来很多费解的问题。因此,当需要类似数组的功能时候,使用集合类即可。
不要忽视编译器给出的警告信息。
JDK动态代理-Proxy.newProxyInstance
java.lang.reflect.Proxy:该类用于动态生成代理类,只需传入目标接口、目标接口的类加载器以及InvocationHandler便可为目标接口生成代理类及代理对象。
1 2 3 4 5 6 7 8 | // 方法 1: 该方法用于获取指定代理对象所关联的InvocationHandler static InvocationHandler getInvocationHandler(Object proxy) // 方法 2:该方法用于获取关联于指定类装载器和一组接口的动态代理类的类对象 static Class getProxyClass(ClassLoader loader, Class[] interfaces) // 方法 3:该方法用于判断指定类是否是一个动态代理类 static boolean isProxyClass(Class cl) // 方法 4:该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例 static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h) |
JDK中的动态代理是通过反射类Proxy以及InvocationHandler回调接口实现的;但是,JDK中所要进行动态代理的类必须要实现一个接口,也就是说只能对该类所实现接口中定义的方法进行代理,这在实际编程中具有一定的局限性,而且使用反射的效率也并不是很高。
要生成某一个对象的代理对象,这个代理对象通常也要编写一个类来生成,所以首先要编写用于生成代理对象的类。在java中如何用程序去生成一个对象的代理对象呢,java在JDK1.5之后提供了一个"java.lang.reflect.Proxy"类,通过"Proxy"类提供的一个newProxyInstance方法用来创建一个对象的代理对象,如下所示:
示例业务逻辑:
1-娱乐明星都会唱歌、演习(interface Star)
2-有一个明星叫胡歌(class HuGe implements Star)
3-他有两个助理(分别对应两个代理类)(class HuGeProxy1、class HuGeProxy2)
4-如果要找胡歌唱歌、演戏,需要先找两个助理中的一个,然后助理去找胡歌唱歌、演戏(class ProxyTest)
1 2 3 4 5 6 7 | package com.huishe.testOfSpring.proxy; //定义一个明细接口 public interface Star { void sing(String song); //唱歌 String act(String teleplay); //表演 } |
1 2 3 4 5 6 7 8 9 10 11 | package com.huishe.testOfSpring.proxy; //创建胡歌类-实现明细接口 public class HuGe implements Star{ public void sing(String song) { System.out.println( "胡歌演唱: " + song); } public String act(String teleplay) { System.out.println( "胡歌决定出演电视剧: " + teleplay); return "胡歌答应出演电视剧: " + teleplay; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | package com.huishe.testOfSpring.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; //胡歌代理类1 //使用一个匿名内部类来实现该接口实现InvocationHandler接口,实现invoke方法 public class HuGeProxy1 { private Star hg = new HuGe(); //实例化一个对象 public Star getProcxy(){ //使用Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)返回某个对象的代理对象 /** * ClassLoader loader:Java类加载器; 可以通过这个类型的加载器,在程序运行时,将生成的代理类加载到JVM即Java虚拟机中,以便运行时需要! * Class<?>[] interfaces:被代理类的所有接口信息; 便于生成的代理类可以具有代理类接口中的所有方法 * InvocationHandler h:调用处理器; 调用实现了InvocationHandler 类的一个回调方法 * */ return (Star)Proxy.newProxyInstance( getClass().getClassLoader(), hg.getClass().getInterfaces(), new InvocationHandler() { /** * InvocationHandler接口只定义了一个invoke方法,因此对于这样的接口,我们不用单独去定义一个类来实现该接口, * 而是直接使用一个匿名内部类来实现该接口,new InvocationHandler() {}就是针对InvocationHandler接口的匿名实现类 */ /** * 在invoke方法编码指定返回的代理对象干的工作 * proxy : 把代理对象自己传递进来 * method:把代理对象当前调用的方法传递进来 * args:把方法参数传递进来 * * 当调用代理对象的star.sing("逍遥叹");或者 star.act("琅琊榜")方法时, * 实际上执行的都是invoke方法里面的代码, * 因此我们可以在invoke方法中使用method.getName()就可以知道当前调用的是代理对象的哪个方法 */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (method.getName().equals( "sing" )){ System.out.println( "我是胡歌代理1,找胡歌唱歌找我" ); return method.invoke(hg, args); } if (method.getName().equals( "act" )){ System.out.println( "我是胡歌代理1,找胡歌演电视剧找我" ); return method.invoke(hg, args); } return null ; } }); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | package com.huishe.testOfSpring.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; //胡歌代理类2 //实现InvocationHandler接口,实现invoke方法 public class HuGeProxy2 implements InvocationHandler{ private Star hg = new HuGe(); public Star getProcxy(){ return (Star)Proxy.newProxyInstance( getClass().getClassLoader(), hg.getClass().getInterfaces(), this ); } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (method.getName().equals( "sing" )){ System.out.println( "我是胡歌代理2,找胡歌唱歌找我" ); return method.invoke(hg, args); } if (method.getName().equals( "act" )){ System.out.println( "我是胡歌代理2,找胡歌演电视剧找我" ); return method.invoke(hg, args); } return null ; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | package com.huishe.testOfSpring.proxy; import org.junit.Test; public class ProxyTest { //测试代理类1 @Test public void testHuGeProxy1(){ HuGeProxy1 proxy = new HuGeProxy1(); //找到胡歌的助理 Star hg = proxy.getProcxy(); //助理和胡歌洽谈 hg.sing( "《逍遥叹》" ); //(胡歌答应后)唱歌 String actResult = hg.act( "《琅琊榜》" ); //(胡歌答应后)演习 System.out.println( "演出结果:" + actResult); } //测试代理类1 @Test public void testHuGeProxy2(){ HuGeProxy2 proxy = new HuGeProxy2(); Star hg = proxy.getProcxy(); hg.sing( "《逍遥叹》" ); String actResult = hg.act( "《琅琊榜》" ); System.out.println( "演出结果:" + actResult); } } |
代理1日志输出:
我是胡歌代理1,找胡歌唱歌找我
胡歌演唱: 《逍遥叹》
我是胡歌代理1,找胡歌演电视剧找我
胡歌决定出演电视剧: 《琅琊榜》
演出结果:胡歌答应出演电视剧: 《琅琊榜》
代理2日志输出:
我是胡歌代理2,找胡歌唱歌找我
胡歌演唱: 《逍遥叹》
我是胡歌代理2,找胡歌演电视剧找我
胡歌决定出演电视剧: 《琅琊榜》
演出结果:胡歌答应出演电视剧: 《琅琊榜》
Java代理
简单代理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | package typeinfo; interface Interface { void doSomething(); void doSomethingElse(String arg); } class RealObject implements Interface { @Override public void doSomething() { System.out.println( "doSomething" ); } @Override public void doSomethingElse(String arg) { System.out.println( "SomethingElse " + arg); } } class SimpleProxy implements Interface { private Interface proxied; public SimpleProxy(Interface proxied) { this .proxied = proxied; } @Override public void doSomething() { System.out.println( "SimpleProxy doSomething" ); proxied.doSomething(); } @Override public void doSomethingElse(String arg) { System.out.println( "SimpleProxy doSomethingElse" + arg); proxied.doSomethingElse(arg); } } public class SimpleProxyDemo { public static void consumer(Interface iface){ iface.doSomething(); iface.doSomethingElse( "bonobo" ); } public static void main(String[] args) { consumer( new RealObject()); consumer( new SimpleProxy( new RealObject())); } } |
动态代理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | package typeinfo; import java.lang.reflect.*; class DynamicProxyHandle implements InvocationHandler { private Object proxied; public DynamicProxyHandle(Object proxied) { this .proxied = proxied; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println( "****** proxy: " + proxy.getClass() + ", method: " + method + ", args: " + args); if (args != null ) { for (Object arg : args) System.out.println( " " + arg); } return method.invoke(proxied, args); } } public class SimpleDynamicProxy { public static void consumer(Interface iface) { iface.doSomething(); iface.doSomethingElse( "bonobo" ); } public static void main(String[] args) { RealObject real = new RealObject(); consumer(real); //Insert a proxy and call again: Interface proxy = (Interface) Proxy.newProxyInstance(Interface. class .getClassLoader(), new Class[]{Interface. class }, new DynamicProxyHandle(real)); consumer(proxy); } } |
可以查看方法名
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | package typeinfo; import java.lang.reflect.*; class MethodSelector implements InvocationHandler { private Object proxied; public MethodSelector(Object proxied) { this .proxied = proxied; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (method.getName().equals( "interesting" )) System.out.println( "Proxy detected the interesting method" ); return method.invoke(proxied, args); } } interface SomeMethods { void boring1(); void boring2(); void interesting(String arg); void boring3(); } class Implementaction implements SomeMethods { @Override public void boring1() { System.out.println( "boring1" ); } @Override public void boring2() { System.out.println( "boring2" ); } @Override public void interesting(String arg) { System.out.println( "interesting " + arg); } @Override public void boring3() { System.out.println( "boring3" ); } } class SelectingMethods { public static void main(String[] args) { SomeMethods proxy = (SomeMethods)Proxy.newProxyInstance( SomeMethods. class .getClassLoader(), new Class[]{SomeMethods. class }, new MethodSelector( new Implementaction()) ); proxy.boring1(); proxy.boring2(); proxy.interesting( "bonobo" ); proxy.boring3(); } } |
Java向上转型
在tune()中,程序代码可以对Instrument和它所有的导出类起作用,这种将wind引用转换为instrument引用的动作,我们称之为向上转型.
其实就是在父类里有一个方法规定了可以使用父类类型的对象
在子类里使用父类的这个方法,并传递子类自身作为参数
然后使用这个子类自身对象调用父类里存在的方法
由导出类转型成基类,在继承图上是向上移动的,因此一般称为向上转型。由于向上转型是从一个较专用类型向较通用类型转换,所以总是很安全的。也就是说,导出类是基类的一个超集。它可能比基类含有更多的方法,但它必须至少具备基类中所含有的方法。在向上转型的过程中,类接口中唯一可能发生的事情是丢失方法,而不是获取它们。这就是为什么编译器在“未曾明确表示转型”或“未曾指定特殊标记”的情况下,仍然允许向上转型的原因。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | class Instrument { protected String name; public Instrument(String name) { this .name = name; } public void play() { System.out.println( "使用 " + this .getClass().getName() + " 演奏了: " + name); } static void tune(Instrument i) { i.play(); } } public class Wind extends Instrument { public Wind(String name) { super (name); } public static void main(String[] args) { Wind flute = new Wind( "《一千年以后》" ); Instrument.tune(flute); } } |
Spring的DI
bean.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 | <?xml version= "1.0" encoding= "UTF-8" ?> <beans xmlns= "http://www.springframework.org/schema/beans" xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http: //www.springframework.org/schema/beans http: //www.springframework.org/schema/beans/spring-beans.xsd"> <!-- spring中的依赖注入 依赖注入: Dependency Injection IOC的作用: 降低程序间的耦合(依赖关系) 依赖关系的管理: 以后都交给spring来维护 在当前类需要用到其他类的对象,由spring为我们提供,我们只需要在配置文件中说明 依赖关系的维护: 就称之为依赖注入。 依赖注入: 能注入的数据:有三类 基本类型和String 其他bean类型(在配置文件中或者注解配置过的bean) 复杂类型/集合类型 注入的方式:有三种 第一种:使用构造函数提供 第二种:使用set方法提供 第三种:使用注解提供(明天的内容) --> <!--构造函数注入: 使用的标签:constructor-arg 标签出现的位置:bean标签的内部 标签中的属性 type:用于指定要注入的数据的数据类型,该数据类型也是构造函数中某个或某些参数的类型 index:用于指定要注入的数据给构造函数中指定索引位置的参数赋值。索引的位置是从 0 开始 name:用于指定给构造函数中指定名称的参数赋值 常用的 =============以上三个用于指定给构造函数中哪个参数赋值=============================== value:用于提供基本类型和String类型的数据 ref:用于指定其他的bean类型数据。它指的就是在spring的Ioc核心容器中出现过的bean对象 优势: 在获取bean对象时,注入数据是必须的操作,否则对象无法创建成功。 弊端: 改变了bean对象的实例化方式,使我们在创建对象时,如果用不到这些数据,也必须提供。 --> <bean id= "accountService" class = "com.itheima.service.impl.AccountServiceImpl" > <constructor-arg name= "name" value= "泰斯特" ></constructor-arg> <constructor-arg name= "age" value= "18" ></constructor-arg> <constructor-arg name= "birthday" ref= "now" ></constructor-arg> </bean> <!-- 配置一个日期对象 --> <bean id= "now" class = "java.util.Date" ></bean> <!-- set方法注入 更常用的方式 涉及的标签:property 出现的位置:bean标签的内部 标签的属性 name:用于指定注入时所调用的set方法名称 value:用于提供基本类型和String类型的数据 ref:用于指定其他的bean类型数据。它指的就是在spring的Ioc核心容器中出现过的bean对象 优势: 创建对象时没有明确的限制,可以直接使用默认构造函数 弊端: 如果有某个成员必须有值,则获取对象是有可能set方法没有执行。 --> <bean id= "accountService2" class = "com.itheima.service.impl.AccountServiceImpl2" > <property name= "name" value= "TEST" ></property> <property name= "age" value= "21" ></property> <property name= "birthday" ref= "now" ></property> </bean> <!-- 复杂类型的注入/集合类型的注入 用于给List结构集合注入的标签: list array set 用于个Map结构集合注入的标签: map props 结构相同,标签可以互换 --> <bean id= "accountService3" class = "com.itheima.service.impl.AccountServiceImpl3" > <property name= "myStrs" > <set> <value>AAA</value> <value>BBB</value> <value>CCC</value> </set> </property> <property name= "myList" > <array> <value>AAA</value> <value>BBB</value> <value>CCC</value> </array> </property> <property name= "mySet" > <list> <value>AAA</value> <value>BBB</value> <value>CCC</value> </list> </property> <property name= "myMap" > <props> <prop key= "testC" >ccc</prop> <prop key= "testD" >ddd</prop> </props> </property> <property name= "myProps" > <map> <entry key= "testA" value= "aaa" ></entry> <entry key= "testB" > <value>BBB</value> </entry> </map> </property> </bean> </beans> |
AccountServiceImpl.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | package com.itheima.service.impl; import com.itheima.service.IAccountService; import java.util.Date; /** * 账户的业务层实现类 */ public class AccountServiceImpl implements IAccountService { //如果是经常变化的数据,并不适用于注入的方式 private String name; private Integer age; private Date birthday; public AccountServiceImpl(String name,Integer age,Date birthday){ this .name = name; this .age = age; this .birthday = birthday; } public void saveAccount(){ System.out.println( "service中的saveAccount方法执行了。。。" +name+ "," +age+ "," +birthday); } } |
AccountServiceImpl2.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | package com.itheima.service.impl; import com.itheima.service.IAccountService; import java.util.Date; /** * 账户的业务层实现类 */ public class AccountServiceImpl2 implements IAccountService { //如果是经常变化的数据,并不适用于注入的方式 private String name; private Integer age; private Date birthday; public void setName(String name) { this .name = name; } public void setAge(Integer age) { this .age = age; } public void setBirthday(Date birthday) { this .birthday = birthday; } public void saveAccount(){ System.out.println( "service中的saveAccount方法执行了。。。" +name+ "," +age+ "," +birthday); } } |
AccountServiceImpl3.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | package com.itheima.service.impl; import com.itheima.service.IAccountService; import java.util.Arrays; import java.util.List; import java.util.Properties; import java.util.Set; import java.util.Map; /** * 账户的业务层实现类 */ public class AccountServiceImpl3 implements IAccountService { private String[] myStrs; private List<String> myList; private Set<String> mySet; private Map<String,String> myMap; private Properties myProps; public void setMyStrs(String[] myStrs) { this .myStrs = myStrs; } public void setMyList(List<String> myList) { this .myList = myList; } public void setMySet(Set<String> mySet) { this .mySet = mySet; } public void setMyMap(Map<String, String> myMap) { this .myMap = myMap; } public void setMyProps(Properties myProps) { this .myProps = myProps; } public void saveAccount(){ System.out.println(Arrays.toString(myStrs)); System.out.println(myList); System.out.println(mySet); System.out.println(myMap); System.out.println(myProps); } } |
Spring创建Bean的三种方式的使用和区别
在学习Spring的时候,发现Spring的IOC(控制反转)为我们提供的三种创建Bean的方式。
1.Spring创建Bean的三种方式
这里采用XML配置,分别演示三种创建Bean的方式和代码。
先创建一个Bean User类 三种方式都是为了得到这个User的对象
1 2 3 4 5 6 | /** * User对象 */ public class User { // 这里只是一个空对象 } |
1.1 采用默认的无参构造创建实例
XML配置:
<!– 默认的无参构建 –>
1 | <bean id= "user" class = "ioc.pojo.User" ></bean> |
测试:
1 2 3 4 5 6 7 | @Test public void testUser(){ ApplicationContext context = new ClassPathXmlApplicationContext( "classpath:applicationContext.xml" ); // 默认的无参构造创建 User user = (User) context.getBean( "user" ); System.out.println( "默认的无参构造创建:" + user); } |
控制台输出: 默认的无参构造创建:ioc.pojo.User@3b088d51
1.2 采用静态工厂创建实例
配置工厂类:
1 2 3 4 5 6 7 8 9 10 | /** * User对象的工厂类 */ public class UserFactory { // 静态方法 public static User getUser1() { return new User(); } } |
XML配置:
<!– 使用静态工厂创建user –>
1 | <bean id= "user1" class = "ioc.service.UserFactory" factory-method= "getUser1" ></bean> |
class 指的是该工厂类的包路径,factory-method 指的是该工厂类创建Bean的静态方法。注意:这里一定要静态方法
测试:
1 2 3 4 5 6 7 | @Test public void testUser1(){ ApplicationContext context = new ClassPathXmlApplicationContext( "classpath:applicationContext.xml" ); // 静态工厂创建 User user1 = (User) context.getBean( "user1" ); System.out.println( "静态工厂创建:" + user1); } |
控制台输出结果:静态工厂创建:ioc.pojo.User@3b088d51
1.3 采用实例工厂创建实例
配置工厂类:
1 2 3 4 5 6 7 8 9 | /** * User对象的工厂类 */ public class UserFactory { //普通方法 public User getUser2() { return new User(); } } |
XML配置:
<!– 使用实例工厂创建 user –>
1 2 | <bean id= "userFactory" class = "ioc.service.UserFactory" ></bean> <bean id= "user2" factory-bean= "userFactory" factory-method= "getUser2" ></bean> |
测试:
1 2 3 4 5 6 7 | @Test public void testUser2(){ ApplicationContext context = new ClassPathXmlApplicationContext( "classpath:applicationContext.xml" ); // 实例工厂创建 User user2 = (User) context.getBean( "user2" ); System.out.println( "实例工厂创建:" + user2); } |
控制台输出结果:实例工厂创建:ioc.pojo.User@3b088d51
好了,实现了Spring三种Bean,感觉很顺利。
—————————————————————————————————————————————————————–
那么问题来了,为什么Spring要提供三种创建Bean的方式呢?
这三种创建Bean的方式又有什么区别呢?接下来开始做实验。
实验一: 三种方式创建的Bean是否有联系?
测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 | @Test public void test1(){ ApplicationContext context = new ClassPathXmlApplicationContext( "classpath:applicationContext.xml" ); // 默认的无参构造创建 User user = (User) context.getBean( "user" ); System.out.println( "默认的无参构造创建:" + user); // 静态工厂创建 User user1 = (User) context.getBean( "user1" ); System.out.println( "静态工厂创建:" + user1); // 实例工厂创建 User user2 = (User) context.getBean( "user2" ); System.out.println( "实例工厂创建:" + user2); } |
控制台输出结果:
默认的无参构造创建:ioc.pojo.User@3b088d51
静态工厂创建:ioc.pojo.User@1786dec2
实例工厂创建:ioc.pojo.User@74650e52
结论:三种方式都是创建一个新的实例对象。实例对象都是独立的,没有联系!
实验二: 三种方式创建的Bean的时机是否不同?
这里采用 bean的配置 init-method 初始化方法来查看Bean的实例是什么时候被加载的
是在加载配置文件的时候?还是在调用getBean()方法的时候?
修改User类,添加init()方法
1 2 3 4 5 6 7 8 9 | /** * User对象 */ public class User { public void init(){ System.out.println( "user被初始化啦" ); } } |
XML配置:
1 2 3 4 5 6 7 8 9 | <!-- 默认的无参构建 --> <bean id= "user" class = "ioc.pojo.User" init-method= "init" ></bean> <!-- 使用静态工厂创建user --> <bean id= "user1" class = "ioc.service.UserFactory" factory-method= "getUser1" init-method= "init" ></bean> <!-- 使用实例工厂创建 user --> <bean id= "userFactory" class = "ioc.service.UserFactory" ></bean> <bean id= "user2" factory-bean= "userFactory" factory-method= "getUser2" init-method= "init" ></bean> |
测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | @Test public void test2(){ ApplicationContext context = new ClassPathXmlApplicationContext( "classpath:applicationContext.xml" ); System.out.println( "=====================================" ); // 默认的无参构造创建 User user = (User) context.getBean( "user" ); System.out.println( "默认的无参构造创建:" + user); // 静态工厂创建 User user1 = (User) context.getBean( "user1" ); System.out.println( "静态工厂创建:" + user1); // 实例工厂创建 User user2 = (User) context.getBean( "user2" ); System.out.println( "实例工厂创建:" + user2); } |
控制台输出结果:
user被初始化啦
user被初始化啦
user被初始化啦
=====================================
默认的无参构造创建:ioc.pojo.User@3b088d51
静态工厂创建:ioc.pojo.User@1786dec2
实例工厂创建:ioc.pojo.User@74650e52
结论:从初始化方法可以看出,Spring这三种创建实例的方式都是一样的,在加载配置文件的时候就创建了实例,证明这三种方式实例加载的时机是一样的。
========================================================================================
很明显,这三种方式最根本的区别还是创建方式的不同。
第一种,通过默认的无参构造方式创建,其本质就是把类交给Spring自带的工厂(BeanFactory)管理、由Spring自带的工厂模式帮我们维护和创建这个类。如果是有参的构造方法,也可以通过XML配置传入相应的初始化参数,这种也是开发中用的最多的。
第二种,通过静态工厂创建,其本质就是把类交给我们自己的静态工厂管理,Spring只是帮我们调用了静态工厂创建实例的方法,而创建实例的这个过程是由我们自己的静态工厂实现的,在实际开发的过程中,很多时候我们需要使用到第三方jar包提供给我们的类,而这个类没有构造方法,而是通过第三方包提供的静态工厂创建的,这是时候,如果我们想把第三方jar里面的这个类交由spring来管理的话,就可以使用Spring提供的静态工厂创建实例的配置。
第三种,通过实例工厂创建,其本质就是把创建实例的工厂类交由Spring管理,同时把调用工厂类的方法创建实例的这个过程也交由Spring管理,看创建实例的这个过程也是有我们自己配置的实例工厂内部实现的。在实际开发的过程中,如Spring整合Hibernate就是通过这种方式实现的。但对于没有与Spring整合过的工厂类,我们一般都是自己用代码来管理的。
<!–把对象的创建交给spring来管理–>
<!–spring对bean的管理细节
1.创建bean的三种方式
2.bean对象的作用范围
3.bean对象的生命周期
–>
<!–创建Bean的三种方式 –>
<!– 第一种方式:使用默认构造函数创建。
在spring的配置文件中使用bean标签,配以id和class属性之后,且没有其他属性和标签时。
采用的就是默认构造函数创建bean对象,此时如果类中没有默认构造函数,则对象无法创建。
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
–>
<!– 第二种方式: 使用普通工厂中的方法创建对象(使用某个类中的方法创建对象,并存入spring容器)
<bean id="instanceFactory" class="com.itheima.factory.InstanceFactory"></bean>
<bean id="accountService" factory-bean="instanceFactory" factory-method="getAccountService"></bean>
–>
<!– 第三种方式:使用工厂中的静态方法创建对象(使用某个类中的静态方法创建对象,并存入spring容器)
<bean id="accountService" class="com.itheima.factory.StaticFactory" factory-method="getAccountService"></bean>
–>
<!– bean的作用范围调整
bean标签的scope属性:
作用:用于指定bean的作用范围
取值: 常用的就是单例的和多例的
singleton:单例的(默认值)
prototype:多例的
request:作用于web应用的请求范围
session:作用于web应用的会话范围
global-session:作用于集群环境的会话范围(全局会话范围),当不是集群环境时,它就是session
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl" scope="prototype"></bean>
–>
<!– bean对象的生命周期
单例对象
出生:当容器创建时对象出生
活着:只要容器还在,对象一直活着
死亡:容器销毁,对象消亡
总结:单例对象的生命周期和容器相同
多例对象
出生:当我们使用对象时spring框架为我们创建
活着:对象只要是在使用过程中就一直活着。
死亡:当对象长时间不用,且没有别的对象引用时,由Java的垃圾回收器回收
–>
类的解耦
通常情况下类的调用和创建都是引入 然后直接通过new的方式来操作
但是这种操作的方式会让类之间的耦合度很高 不利于代码的维护
一般简单解耦操作可以通过以下方式:
-
创建配置文件,内部包含需要实例化的类名
-
通过工厂类 使用反射的方式创建类的实例
bean.properties
1 2 | accountService=cn.leokim.service.impl.AccountServiceImpl accountDao=cn.leokim.dao.impl.AccountDaoImpl |
BeanFactory
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | public class beanFactory{ //定义一个properties对象 provate static Properties props; //使用静态代码块为Properties对象赋值 static { try { props = new Properties(); //获取properties文件的流对象 inputStream in = BeanFactory. class .getClassLoder().getResourceAsStream( "bean.properties" ); porps.load(in); } catch (Exception e){ throw new ExceptionInInitializerError( "初始化props失败." ); } } //根据Bean的名称获取bean对象 public static Object getBean(String beanName){ Object bean = null ; try { String beanPath = props.getProperty(beanName); // System.out.println(beanPath); bean = Class.forName(beanPath).newInstance(); //每次都会调用默认构造函数创建对象 } catch (Exception e){ e.printStackTrace(); } return bean; } } |
如果需要单例模式可以通过以下方法在static静态代码块里将类直接实例化好,在getBean的时候直接返回实例化好的类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | import java.io.InputStream; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import java.util.Properties; public class BeanFactory { //定义一个Properties对象 private static Properties props; //定义一个Map,用于存放我们要创建的对象。我们把它称之为容器 private static Map<String,Object> beans; //使用静态代码块为Properties对象赋值 static { try { //实例化对象 props = new Properties(); //获取properties文件的流对象 InputStream in = BeanFactory. class .getClassLoader().getResourceAsStream( "bean.properties" ); props.load(in); //实例化容器 beans = new HashMap<String,Object>(); //取出配置文件中所有的Key Enumeration keys = props.keys(); //遍历枚举 while (keys.hasMoreElements()){ //取出每个Key String key = keys.nextElement().toString(); //根据key获取value String beanPath = props.getProperty(key); //反射创建对象 Object value = Class.forName(beanPath).newInstance(); //把key和value存入容器中 beans.put(key,value); } } catch (Exception e){ throw new ExceptionInInitializerError( "初始化properties失败!" ); } } /** * 根据bean的名称获取对象 * @param beanName * @return */ public static Object getBean(String beanName){ return beans.get(beanName); } |