Question & Answer
1、Hibernate问题-read-write缓存策略
read-write是严格的读取策略。在将一个对象放到缓存时,同时会加一把锁。在当前session没有关闭的情况下,其他session不可以访问缓存中的同一个对象。真到此锁定的session关闭。
@Test
publicvoid testUpdate(){
Session s2 = HibernateUtils.openSession();
Person p1 = (Person) s2.get(Person.class, "402881e4345a53ce01345a543b9a0004");
System.out.println("第一次查询"+p1); //查询获取一个新的对象
s2.clear(); //将一级缓存中的数据清除
boolean boo = //判断二级缓存中,是否存在此对象,为true
HibernateUtils.getSessionFactory()
.getCache()
.containsEntity(Person.class,"402881e4345a53ce01345a543b9a0004");
System.out.println("是否存在这个对象:"+boo);//true
Person p2 = (Person) s2.get(Person.class, "402881e4345a53ce01345a543b9a0004");//read-writeg下为什么不查二级缓存
System.out.println("同一个session第二次查询"+p2);//使用同一个session查询,结果因为是read-write,
//因为对象已经上锁,所以不会从二级缓存中返回数据
//s2.close(); //关闭或是不关闭
System.out.println("上一个Session关闭以后");
Session s3 = HibernateUtils.openSession();
boo =
HibernateUtils.getSessionFactory()
.getCache()
.containsEntity(Person.class,"402881e4345a53ce01345a543b9a0004");
System.out.println("是否存在这个对象:"+boo);//true
Person p3 = (Person) s3.get(Person.class, "402881e4345a53ce01345a543b9a0004");//read-write下,为什么这儿查二级缓存
System.out.println(p3);
s3.close();
}
2、为什么16进制数要与OxFF与运算
以下是MD5加密算法示例,其中使用了 OxFF的与运算:
@Test
publicvoidmd5() throws Exception{
String pwd = "1234";
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] bts = md.digest(pwd.getBytes());//16位
pwd = "";
for(byte bt : bts){
System.err.println("src: "+bt);
String str = Integer.toHexString(bt);
System.err.println("src:-----"+str);
str = Integer.toHexString(bt & 0xFF);//为儿在算MD5时为什么与要0xff进行与运算呢
System.err.println("dest:"+str);
if(str.length()<=1){
str+="f";
}
pwd+=str;
System.err.println("---------------------------------");
}
System.err.println("最后的密码是:"+pwd);
}
我们要讨论的主要问题是,为什么上面注解处要与OxFF进行与运算而不是其他值呢?
因为OxFF的16进制数为ffffffff (即8个f)。按与运算的法则,
先来看二进制的与运算,如:-1 & 1 = 1
-1(负1)的二进制编码为: 11111111 11111111 11111111 11111111 即一个32位的字符串,前面一位是1表示负数
1(正1)的二进制编码为: 00000000 00000000 00000000 00000001 即,只有最后一位是1,其他部分全部为0
按&(与运算)的法则结果: 00000000 00000000 00000000 00000001 即,只有当两个都为1时才为1,所以最后的结果为:1(正1)
则可以知道为什么要与0xFF进行与运算,因为OxFF的16进行为:000000FF。任意数与OxFF进行与运算都是取16进制的后两位
运算示例如下:
-127(负数127)的16进制为: FFFFFF81
0xFF的16进制为: 000000FF
则&(与)运算是结果为: 00000081
所以:Integer.toHexStriing(-127 & 0xFF) 的结果为:81。
另外,其中很多计算,都使用0x这样的16进制进行运行:
如 0xF的二进制为 1111 ,即四个1。
0xFF的二进制为 1111 1111 ,即8个1的二进制形式
每多一个F就是多一个4位的1111。
最多8个F。
3、Java随机生成中文汉字
/**
* 原理是从汉字区位码找到汉字。在汉字区位码中分高位与底位,且其中简体又有繁体。位数越前生成的汉字繁体的机率越大。
* 所以在本例中高位从171取,底位从161取,去掉大部分的繁体和生僻字。但仍然会有!!
*
*/
@Test
publicvoid create() throws Exception {
String str = null;
int hightPos, lowPos; // 定义高低位
Random random = new Random();
hightPos = (176 + Math.abs(random.nextInt(39)));//获取高位值
lowPos = (161 + Math.abs(random.nextInt(93)));//获取低位值
byte[] b = newbyte[2];
b[0] = (newInteger(hightPos).byteValue());
b[1] = (new Integer(lowPos).byteValue());
str = new String(b, "GBk");//转成中文
System.err.println(str);
}
/**
* 旋转和缩放文字
* 必须要使用Graphics2d类
*/
publicvoid trans(HttpServletRequest req, HttpServletResponse resp) throws Exception{
int width=88;
int height=22;
BufferedImage img = new BufferedImage(width, height,BufferedImage.TYPE_INT_RGB);
Graphics g = img.getGraphics();
Graphics2D g2d = (Graphics2D) g;
g2d.setFont(new Font("黑体",Font.BOLD,17));
Random r = new Random();
for(int i=0;i<4;i++){
String str = ""+r.nextInt(10);
AffineTransform aff = new AffineTransform();
aff.rotate(Math.random(),i*18,height-5);
aff.scale(0.6+Math.random(), 0.6+Math.random());
g2d.setTransform(aff);
g2d.drawString(str,i*18,height-5);
System.err.println(">:"+str);
}
g2d.dispose();
ImageIO.write(img, "JPEG",resp.getOutputStream());
}
4、MySQL区分大小写的查询
mysql查询默认是不区分大小写的如:
select * from table_name where a like 'a%'
select * from table_name where a like 'A%'
select * from table_name where a like 'a%'
select * from table_name where a like 'A%'
效果是一样的。
要让mysql查询区分大小写,可以:
select * from table_name where binary a like 'a%'
select * from table_name where binary a like 'A%'
select * from table_name where binary a like 'a%'
select * from table_name where binary a like 'A%'
也可以在建表时,加以标识
create table table_name(
a varchar(20) binary
}
5、在Hibernate的区分大小写的查询
首先,必须要创建数据库时,指字某个列为区别大小写:
CREATE TABLE person(
id VARCHAR(32) PRIMARY KEY,
NAME VARCHAR(30) BINARY //此列使用了binary,是指区别大小写的查询
);
然后在Hibernate中:
String hql = "from Person where lower(name)='jjj'"; //注意这儿使用的lower关键字
List list = sess.createQuery(hql).list()
System.err.println(list);
list = sess.createCriteria(Person.class).add(Restrictions.eq("name", "jjj").ignoreCase()).list(); //注意这儿使用了ignoreCase方法。
System.err.println(">>:"+list);
6、MySql执行存储过程时错误的处理方式
创建一个简单的存储过程:
DELIMITER $$
CREATE PROCEDURE proc_book()
BEGIN
SELECT * FROM book;
END $$
DELIMITER ;
调用这个存储过程:
CALL proc_book();
有可能会出现以下错误代码:
ERROR 1436 (HY000): Thread stack overrun
修改方式如下:
修改
My.ini中的配置
thread_stack = 256K原来是128K,将内存修改成256K即可。
7、反射判断一个方法拥有什么参数设置不同的值
package cn.itcast.loader2;
/**
* 被反射类
* @author <a href="awangsenfeng@163.com">王森丰</a>
* @version 1.0 2011-3-7
*/
publicclass Hello {
//假设以下方法有什么参数都不明确
publicvoid sayHi(String aa,Integer age,int mm){
System.err.println("Hello:"+aa+","+age+","+mm);
}
}
调用类如下:
package cn.itcast.loader2;
import java.lang.reflect.Method;
/**
* 判断有什么类型的参数,设置不同的值
* @author <a href="awangsenfeng@163.com">王森丰</a>
* @version 1.0 2011-3-7
*/
publicclass ParamDemo {
publicstaticvoid main(String[] args) throws Exception {
Class<?> cls = Hello.class;
Method[] ms = cls.getDeclaredMethods();
for(Method m:ms){
if(m.getName().equals("sayHi")){
Class<?>[] clses = m.getParameterTypes();
Object[] oo = new Object[clses.length];
for(int i=0;i<oo.length;i++){
System.err.println(i+",,"+clses[i].getSimpleName());
if(clses[i].getSimpleName().equals("String")){
oo[i] = new String("noname");
}elseif(clses[i].getSimpleName().equals("Integer")){
oo[i] = new Integer(0);
}elseif(clses[i].getSimpleName().equals("Boolean")){
oo[i] = new Boolean(false);
}elseif(clses[i].getSimpleName().equals("int")){
oo[i] = 90;
}
//TODO:,别外还应该判断Double,Float,double,float,byte,Byte,Character,char
}
System.err.println("---------------");
m.invoke(new Hello(),oo);
}
}
}
}
8、回调函数:
Java代码的回调函数经常由框架或是系统定义,由程序开发人员填充。
它的最主要特点是即定义了调用的规范同时又非常的灵活。
回调函数有些类似于观察者模式,它们的区别在于:观察者模式返回的参数为Event对象,而回调函数则一般直接返回数据本身。
回调函数包含:调用者:如dbutils中的QueryRunner。
回调规范,即一个接口,如dbutils中的ResultSetHandler<T>。其中可以定义N多个它的子类。
返回的数据:由用户自己定义,或是系统已经定义。
1、简单回调函数
package cn.itcast.test;
/**
* 回调函数的简单写法
* @author王森丰
* @version 2011-1-11
*/
publicclass TT {
publicstaticvoid main(String[] args) {
One a = new One();
a.abc("Hello",new A(){ //这是没有返回值的回调函数
publicvoid abc(String ss) {
System.err.println("SSS:"+ss);
}
});
}
}
class One{ //定义一个调用类
publicvoid abc(String sql,A a){
a.abc(sql);
}
}
interface A{ //定义规范
publicvoid abc(String ss);
}
2、拥有任意返回值的回调函数
1、以下是返回一个值的回调函数:
package cn.itcast.test;
/**
* 返回指定的泛型的回调函数
* @author王森丰
* @version 2011-1-11
*/
publicclass CallBack {
publicstaticvoid main(String[] args) {
B b = new B();
String str = b.getO(new IB<String>(){
public String getObject(){
return"ddd";
}
});
System.err.println(str);
}
}
class B{ //声明一个类,此方法返回一个普通的值
public <T> T getO(IB<T> b){//声明方法同时接收一个回调函数,注意泛型的使用
return b.getObject();
}
}
interface IB<T>{ //将泛型定义到类上,直接在类上声明泛型,这样在声明时就可以设置泛型了
public T getObject();
}
2、以下是返回List<T>类型的回调函数:
package cn.itcast.test;
import java.util.ArrayList;
import java.util.List;
/**
* 返回list<T>类型的回调函数
* @author王森丰
* @version 2011-1-11
*/
publicclass CallBack2 {
publicstaticvoid main(String[] args) {
C c = new C(); //以下是测试代码
List<String> list = c.getObjs(new IC<String>(){
public String getObject(int row) {
return"Hello"+row;
}
});
System.err.println(">:"+list);
}
}
class C{ //定义被调用的类
public <T> List<T> getObjs(IC<T> ic){ //在类上定义泛型多处使用
List<T> list = new ArrayList<T>(); //里面声明一个List并多次调用回调函数的方法
for(int i=0;i<10;i++){
T t = ic.getObject(i); //多次调用回调函数的方法
list.add(t); //将从回调函数中返回对象放到List中
}
return list;
}
}
interface IC<T>{ //定义回调规范
public T getObject(int row);
}
9、线程池ExecutorService的submit和execute
在Java5之后,并发线程这块发生了根本的变化,最重要的莫过于新的启动、调度、管理线程的一大堆API了。在Java5以后,通过 Executor来启动线程比用Thread的start()更好。在新特征中,可以很容易控制线程的启动、执行和关闭过程,还可以很容易使用线程池的特性。
一、创建任务
任务就是一个实现了Runnable接口的类。
创建的时候实run方法即可。
二、执行任务
通过java.util.concurrent.ExecutorService接口对象来执行任务,该接口对象通过工具类java.util.concurrent.Executors的静态方法来创建。
Executors此包中所定义的 Executor、ExecutorService、ScheduledExecutorService、ThreadFactory 和 Callable 类的工厂和实用方法。
ExecutorService提供了管理终止的方法,以及可为跟踪一个或多个异步任务执行状况而生成 Future 的方法。可以关闭 ExecutorService,这将导致其停止接受新任务。关闭后,执行程序将最后终止,这时没有任务在执行,也没有任务在等待执行,并且无法提交新任务。
executorService.execute(new TestRunnable());
1、创建ExecutorService
通过工具类java.util.concurrent.Executors的静态方法来创建。
Executors此包中所定义的 Executor、ExecutorService、ScheduledExecutorService、ThreadFactory 和 Callable 类的工厂和实用方法。
比如,创建一个ExecutorService的实例,ExecutorService实际上是一个线程池的管理工具:
ExecutorService executorService = Executors.newCachedThreadPool();
ExecutorService executorService = Executors.newFixedThreadPool(3);
ExecutorService executorService = Executors.newSingleThreadExecutor();
2、将任务添加到线程去执行
当将一个任务添加到线程池中的时候,线程池会为每个任务创建一个线程,该线程会在之后的某个时刻自动执行。
示例1:
@Test
publicvoid testDemo() throws Exception {
//单例线程,任意时间(同一时间)池中只能有一个线程
ExecutorService es = Executors.newSingleThreadExecutor();
es.execute(new Runnable() {
@Override
publicvoid run() {
System.err.println("线程启动并运行"+Thread.currentThread().getName());
}
});
es.execute(new Runnable() {
@Override
publicvoid run() {
System.err.println("第二个也运行了"+Thread.currentThread().getName());
}
});
//Thread.sleep(1000 * 60 * 60);
}
运行结果如下:两个都会执行,但程序只会使用一个线程来运行。
线程启动并运行pool-1-thread-1
第二个也运行了pool-1-thread-1
示例2:
@Test
publicvoid testDemo3() throws Exception {
//声明一个线程池
ExecutorService ex = Executors.newCachedThreadPool();
for (int i = 0; i < 4; i++) {
finalint a = i;
//每一次execute方法,都是向池中放入一个对象
ex.execute(new Runnable() {
publicvoid run() {
while(true){
System.err.println("测试...."+a+">"
+Thread.currentThread().getName()+","
+Thread.currentThread().isDaemon());
try{
Thread.sleep(2000);
}catch(Exception e){
e.printStackTrace();
}
}
}
});
}
Thread.sleep(1000*60*60);
}
输出的结果如下:从中可以发现,第四个一组输出,即一共创建了四个线程,每次每个线程都会执行输出,但不按顺序:位每一次输出都四个算是一组测试....0>pool-1-thread-1,false
测试....3>pool-1-thread-4,false
测试....2>pool-1-thread-3,false
测试....1>pool-1-thread-2,false
测试....0>pool-1-thread-1,false
测试....3>pool-1-thread-4,false
测试....2>pool-1-thread-3,false
测试....1>pool-1-thread-2,false
测试....1>pool-1-thread-2,false
测试....2>pool-1-thread-3,false
测试....3>pool-1-thread-4,false
测试....0>pool-1-thread-1,false
示例3:
publicvoid testCall() throws Exception{
//声明一个类,可以被调用,类似于线程,但它可以拥有返回值
class MyCall implements Callable<String>{
privateintseq;
public MyCall(int seq){
this.seq=seq;
}
//抛出异常并可以拥有返回值
public String call() throws Exception {
System.err.println("执行"+seq+","+Thread.currentThread().getName());
Thread.sleep(3000);
System.err.println("Weak up "+seq);
return"完成"+seq;//这是返回值
}
}
ExecutorService es = Executors.newCachedThreadPool();//创建线程池对象
List<Future<String>> result = new ArrayList<Future<String>>();//放结果用的集合
for(int i=0;i<3;i++){
Future<String> f=es.submit(new MyCall(i));//线程执行完成以后可以通过引用获取返回值
result.add(f);
}
for(Future<String> f:result){
System.err.println("返回值:"+f.get());//输出返回的值
}
System.err.println("完成....");
}
10、JDK的动态代理为什么必须要使用接口
JDK的代理Proxy必须要使用接口,才可以实现对方法的拦截。为什么呢?先让我们看一个JDK动态代理的示例:
接口类:
publicinterface IPerson {
publicvoid sayHi(String nm);
}
接口实现类:
publicclass Person implements IPerson{
public Person(){
}
private String name;
public Person(String name){
this.name=name;
}
publicvoid sayHi(String str){
System.err.println(name+" Hello :"+str);
}
}
使用JDK代理Person类的方法:
@Test
publicvoid testJdkProxy() throws Exception{
final Dog dog = new Dog();
//以下必须返回接口,因为Proxy内部,会根据接口创建一个IAnimal的子类,
//创建的这个子类是Dog类的兄弟关系。子类可以转换成父类,但对于平行的两个转换将失败,
//这就是为什么必须要使用接口的原因
IAnimal dog2 = (IAnimal)Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),
dog.getClass().getInterfaces(),
new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.err.println("Before...");
Object o = method.invoke(dog, args);
return o;
}
});
dog2.eat();
boolean boo = Proxy.isProxyClass(dog2.getClass());
System.err.println("是否是被代理对象:"+boo+","+dog2.getClass());
}
代码说明:Proxy返回的对象必须要强转为Ianimal的类型。如果要转成Dog类型则会抛出ClassCastException。
Proxy 代理可以使用以下简图加以说明:
Proxy代理容器
$Proxy$实现IPerson接口
1创建一个子类
IPerson接口
实现接口
InvocationHandler
invoke方法
Person实现类
2调用用户的实现类
上图中,最关键,也是最重要的部分就是Proxy在接收到Person类的实例以后,创建了IPerson类的一个子类取名为$Proxy$,此时$Proxy$类与Person类为同级兄弟关系。所以如果使用以下代码将会抛出ClassCastException:
Person person = (Person)Proxy.newInstance(…..);
但使用接口就不会出现异常,即:
IPerson peson = (IPerson)Proxy.newInstance(…..);
1、为了更加清楚为什么会抛出转换异常,我再做如下测试:
声明一个IAnimal接口类:
publicinterface IAnimal {
publicvoid eat();
}
实现IAnimal接口的Dog类:
publicclass Dog implements IAnimal {
private String food;
public Dog(String food){
this . food =food;
IAnimal接口
}
publicvoid eat() {
System.err.println("小狗吃:"+food);
}
Cat实现类
}
Dog实现类
实现IAnimal接口的Cat类:
publicclass Cat implements IAnimal {
private String food;
public Cat(String food){
this.food=food;
}
publicvoid eat() {
System.err.println("小猫吃:"+food);
}
}
测试是否可以将Dog类的实例转换成Cat,转换时,将会抛出ClassCastException:
IAnimal dog = new Dog("骨头");
IAnimal cat = new Cat("小鱼");
Dog dog2 = (Dog) cat;//cat与dog是兄弟关系不能转换成功,抛出ClassCastException
dog = cat; //此时将转换成功,因为dog是IAnimal类型,可以指向自己的子类
2、然后再测试使用反射
IAnimal dog = new Dog("骨头");
IAnimal cat = new Cat("小鱼");
boolean boo = dog.getClass()==cat.getClass();
System.err.println(boo);
Method m = dog.getClass().getMethod("eat");//从dog中获取
m.invoke(cat);//不成功,说明反射从哪儿获取方法,就应该调用谁的实例
//但如果是从最高接口中获取到的方法,则可以执行,如下:
Method m2 = IAnimal.class.getMethod("eat");
m2.invoke(dog);//执行成功
System.err.println("---");
m.invoke(cat);//执行成功
上例中:
Method m = dog.getClass.getMethod(“eat”);
m.invoke(dog);只可以执行dog的方法,如果填入cat则会执行不成功。因为Dog类拥有自己的字节码。
而如果修改成:
Method m = IAnimal.class.getMethod(“eat”);
m.invoke(dog);
m.invoke(cat);//两个调用都可以执行成功因为,Dog和Cat拥有相同的父类接口,而IAnimal字节码只有一份。
11、使用CGLIB动态代理
Cglib是一个优秀的动态代理框架,它的底层使用ASM在内存中动态的生成被代理类的子类。使用CGLIB即使被代理类没有实现任何接口也可以实现动态代理功能。CGLIB具有简单易用,它的运行速度要远远快于JDK的Proxy动态代理:
使用CGLIB需要导入以下两个jar文件:
asm.jar – CGLIB的底层实现。
cglib.jar – CGLIB的核心jar包。
CGLIB的核心类:
net.sf.cglib.proxy.Enhancer – 主要的增强类
net.sf.cglib.proxy.MethodInterceptor – 主要的方法拦截类,它是Callback接口的子接口,需要用户实现
net.sf.cglib.proxy.MethodProxy – JDK的java.lang.reflect.Method类的代理类,可以方便的实现对源对象方法的调用,如使用:
Object o = methodProxy.invokeSuper(proxy, args);//虽然第一个参数是被代理对象,也不会出现死循环的问题。
费话少说,上代码:
1、使用CGLIB的代理:
以下测试代理一个没有实现任何接口的Person类:
@Test
publicvoid testProxy1() throws Exception {
final Person p1 = new Person(); //Person类没有实现任何接口
Enhancer en = new Enhancer(); //声明增加类实例
en.setSuperclass(Person.class); //设置被代理类字节码,CGLIB根据字节码生成被代理类的子类
en.setCallback(new MethodInterceptor() {//设置回调函数,即一个方法拦截
public Object intercept(Object target, Method method,
Object[] args, MethodProxy proxy) throws Throwable {
Object o = method.invoke(p1,args); //注意参数p1,仍然为外部声明的源对象,且Method为JDK的Method反射
System.err.println("After...");
return o;
}
});
Person p = (Person) en.create(); //通过create方法返回Person类的代理
System.err.println(p.getClass());//被代理的对象
p.sayHi("Hello");
}
2、以下测试代理一个拥有接口的类:
IAnimal是接口,Dog是实现类,具体代码如下:
@Test
publicvoid testProxy2() throws Exception {
final Dog dog = new Dog(); //声明被代理对象
Enhancer en = new Enhancer(); //声明CGLIB增强类
en.setSuperclass(IAnimal.class); //设置接口类,也可以设置成dog实现类,会影响create返回的对象
en.setCallback(new MethodInterceptor() {
public Object intercept(Object target, Method method,
Object[] args, MethodProxy proxy) throws Throwable {
System.err.println("Before...");
Object o = method.invoke(dog, args);
System.err.println("After...");
return o;
}
});
//Dog dog2 = (Dog) en.create();//必须转型为接口,否则抛出ClassCastException
IAnimal dog2 = (IAnimal)en.create();
dog2.eat();
}
说明:
由于上例中,设置了en.setSuperclass(IAnimal.class),所以en.create()方法,返回的对象,必须要转换成IAnimal接口。如果转换成Dog则会抛出ClassCastException。
3、将CGLIB再做一个简单的包装:
class CglibProxy implements MethodInterceptor{
private Object srcTarget;
private CglibProxy(Object o){
this.srcTarget = o;
}
@SuppressWarnings("unchecked")
publicstatic <T>T proxyTarget(T t){
Enhancer en = new Enhancer();
en.setSuperclass(t.getClass());
en.setCallback(new CglibProxy(t));
T tt = (T) en.create();
return tt;
}
@Override
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
System.err.println("拦截前...");
Object o = method.invoke(srcTarget, args);
System.err.println("拦截后....");
return o;
}
}
包装以后的调用代码如下,主要是快速的实现获取被代理类:
Person p = CglibProxy.proxyTarget(new Person());
p.sayHi("HJello");
IAnimal dog = CglibProxy.proxyTarget(new Dog());
dog.eat();
4、使用静态方法代理一个没有接口的对象
以下代码,包含在一个测试方法或是main方法中运行:
final Person src = new Person();
//直接使用静态方法代理一个对象
Person p = (Person) Enhancer.create(Person.class,new MethodInterceptor(){
public Object intercept(Object proxyedObj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
System.err.println("Hello");
//使用原生的方法调用,注意里面的src
//Object oo = method.invoke(src, args);
//使用MethodProxy调用父类的代码,同样有效
Object oo = proxy.invokeSuper(proxyedObj, args);
return oo;
}
});
System.err.println(p.getClass());
p.abc();
12、深入ThreadLocal的内部机制
早在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。ThreadLocal并不是一个Thread,而是Thread的局部变量。当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本。
ThreadLocal的接口方法:
void set(Object value):设置当前线程的线程局部变量的值。
public Object get():该方法返回当前线程所对应的线程局部变量。
public void remove():将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。
需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,
所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。
protected Object initialValue():返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。
这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,
并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。
在JDK5.0中,ThreadLocal已经支持泛型,该类的类名已经变为ThreadLocal<T>。
ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单:在ThreadLocal类中有一个Map,用于存储每一个线程的变量副本
模拟ThreadLocal代码清单:
package cn.itcast.ref;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
publicclass MyThreadLocal {
//使用同步的map维护对象
privatestatic Map<Thread,Object> map =
Collections.synchronizedMap(new HashMap<Thread,Object>());
publicstatic Object get(){
//获取当前线程
Thread currentThread = Thread.currentThread();
Object o = map.get(currentThread);
if(o==null){
o = new Random().nextInt(100);//假设是一个随机数
map.put(currentThread, o);
}
return o;
}
publicstaticvoid remove(){
Thread currentThread = Thread.currentThread();
if(map.containsKey(currentThread)){
map.remove(currentThread);
}
}
}
虽然这个ThreadLocal实现版本显得比较幼稚,但它和JDK所提供的ThreadLocal类在实现思路上是相近的。
测试代码清单:
@Test
publicvoid testMyThreadLocal(){
Object o1 = MyThreadLocal.get();
Object o2 = MyThreadLocal.get();
//以下因为是同一个线程所有值相同
System.err.println(o1+","+o2);
new Thread(){
publicvoid run() {
//在新的线程中获取对象为不同的值
Object o3 = MyThreadLocal.get();
System.err.println("o3:"+o3);
};
}.start();
}
12.1、何时清除对象的问题
ThreadLocal可以很好的维护线程局部的对象,那么它是如何做到及时将对象从内存中回收的呢?
遍观ThreadLocal的源代码可知,ThreadLocal是通过弱引用实现对象可以在内存被及时清回收的,以下是从ThreadLocal内部类Entry的源代码,可见Entry内部是WeakReferences(弱引用)的子类,即一个弱引用而已:
staticclassEntryextendsWeakReference<ThreadLocal> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal k, Object v) {
super(k);
value = v;
}
}
12.2、对象的引用
从JDK1.2版本开始,把对象的引用分为四种级别,从而使程序能更加灵活的控制对象的生命周期。这四种级别由高到低依次为:强引用、软引用、弱引用和虚引用。
1.强引用
本章前文介绍的引用实际上都是强引用,这是使用最普遍的引用。如果一个对象具有强引用,那就类似于必不可少的生活用品,垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。
2.软引用(SoftReference)
如果一个对象只具有软引用,那就类似于可有可物的生活用品。如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。
3.弱引用(WeakReference)
如果一个对象只具有弱引用,那就类似于可有可物的生活用品。弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
4.虚引用(PhantomReference)
"虚引用"顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。虚引用主要用来跟踪对象被垃圾回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。
为弱引用使用较多:
弱引用的示例代码:
/**
* 弱引用的示例
*/
@Test
publicvoid testRef2() throws Exception{
//声明一个引用队列
ReferenceQueue<Person> pp = new ReferenceQueue<Person>();
//声明一个弱引用对象,引用一个Person对象
WeakReference<Person> rq = new WeakReference<Person>(new Person("弱引用"),pp);
//催促垃圾回收,由于Person没有被任何变量引用,所以会被回收
System.gc();
//给垃圾回收器留出足够的时间
Thread.sleep(1000);
//被回收以后,获取到的p对象将变成null值
Person p = rq.get();
System.err.println("ppp:"+p);
//被回收以后的对象,将会放到回收队列中,但已经不可以再使用
Reference<? extends Person> ref= pp.poll();
//如果有对象已经被回收,则返回一个对象,否则返回null
System.err.println(">>:"+ref);
}
模拟使用弱引用实现的ThreadLoale
package cn.itcast.ref;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Map;
publicclass MyThreadLocale{
//声明为实例成员变量
private Map<MyThreadLocale, WeakReference<Object>> mm
= new HashMap<MyThreadLocale, WeakReference<Object>>();
publicvoid set(Object t){
mm.put(this,new WeakReference<Object>(t));
}
public Object get(){
WeakReference<Object> wk = mm.get(this);
if(wk==null){
returnnull;
}else{
Object o = wk.get();
return o;
}
}
}
测试代码:
@Test
publicvoid testWeak(){
MyThreadLocale mm = new MyThreadLocale();
mm.set(new Dog("Jack"));
Object o = mm.get();//只要是被强引用就不会被回收
System.gc();
Object oo = mm.get();
System.err.println("返回的值为:"+oo);
}
以下是Dog类:
class Dog{
private String name;
public Dog(String name){
this.name=name;
}
@Override
public String toString() {
return"Dog [name=" + name + "]";
}
@Override
protectedvoid finalize() throws Throwable {
System.err.println("OKOKOKK");
}
}
13、使用JavaScript操作Cookie
相关文档请见DHTML.CHM。里面有完整的操作方法说明。以下是代码示例。
1、使用JavaScript保存一个Cookie
var date = new Date(); //声明当前时间
var time = 1000*60*60*24; //这是一天。1000毫秒(即1秒)*60=1分钟
time = date.getTime()+time; //当前时间的毫秒值相加
date.setTime(time); //设置时间,这时的时间已经是加上1天以后的时间了
var path = "<%=request.getContextPath()%>";//获取当前项目的目录
document.cookie="name=JSJack;expires="+date.toUTCString()+";path="+path;//保存Cookie
在上面的设置中,显式的设置了path。不果不设置则为当前页面所在的路径。
说明:在JS中保存Cookie时,格式有明确的说明为:
说明:GMT时间和UTC时间,都是标准时间,由于toGMTString已经被废弃,所以推荐使用toUTCString()。
2、使用JS读取Cookie
var cs = document.cookie.split(";");//根据;(分号)分隔所有Cookie
div.innerHTML=cs.length; //判断Cookie的个数写到已定义的div中
for(var i=0;i<cs.length;i++){ //遍历Cookie
var c = cs[i];
div.innerHTML+="<br/>"+c; //显示Cooke,格式为:Name=Value
}
显示结果如下:
1 //这是个数
name=JSJack //这是Cookie
3、使用JS删除一个Cookie
删除时,必须要与原来的值设置的完全一样,否则删除不成功。删除时,只要将时间设置为一个过期的时间即可以删除。
代码示例:
创建:
var date = new Date(); //声明当前时间
var time = 1000*60*60*24; //这是一天。1000毫秒(即1秒)*60=1分钟
time = date.getTime()+time; //当前时间的毫秒值相加
date.setTime(time); //设置时间,这时的时间已经是加上1天以后的时间了
document.cookie="name=JSJack;expires="+date.toUTCString()+";path=/";//保存Cookie
删除:
<scripttype="text/javascript">
//删除Cookie只要将时间设置为过期时间即可
var date = new Date(1900,1,1);//声明一个已经过去的时间
document.cookie="name=ok;expires="+date.toUTCString()+";path=/";//执行删除
</script>
4、保存中文示例
Cookie中不可以保存中文。有些浏览器如IE,虽然看上去可以处理中文,但会使整个JS解析终止,所以,不可以直接保存中文。
如果要保存中文,应该使用escape(string)进行编码,当然读取一个经过编码的cookie时,还必须要经过unescape(string)解码。关于escape的说明请查询JavaScript的文档:
保存中文的示例代码:
var date = new Date(); //声明当前时间
var time = 1000*60*60*24; //这是一天。1000毫秒(即1秒)*60=1分钟
time = date.getTime()+time; //当前时间的毫秒值相加
date.setTime(time); //设置时间,这时的时间已经是加上1天以后的时间了
var cn = "你好"; //声明需要保存的中文
cn = escape(cn); //对中文进行编码
document.cookie="name="+cn+";expires="+date.toUTCString()+";path=/";//保存Cookie
读取中文时当然也要进行unescape的解码:
var cs = document.cookie.split(";");//根据;(分号)分隔所有Cookie
div.innerHTML=cs.length; //判断Cookie的个数写到已定义的div中
for(var i=0;i<cs.length;i++){ //遍历Cookie
var c = cs[i];
div.innerHTML+="<br/>"+c; //显示Cooke,格式为:Name=Value
var cc = c.split("="); //根据=等于号进行分隔
div.innerHTML+="<br/>"+cc[0]+","+unescape(cc[1]);//对value部分进行解码
}
14、jetty快速入门示例
Jetty 是一个开源的servlet容器,它为基于Java的web内容,例如JSP和servlet提供运行环境。Jetty是使用Java语言编写的,它的 API以一组JAR包的形式发布。开发人员可以将Jetty容器实例化成一个对象,可以迅速为一些独立运行(stand-alone)的Java应用提供网络和web连接。
目前jetty已经成为eclipse的子项目。
Jetty的主页:
1、Jetty的安装与启动
登录jetty的主页:http://www.eclipse.org/jetty/。下载jetty的最新版本。目前最新版本为8.12。但本文仍然以7.x为主进行说明。
Jetty下载后,即是两个压缩文件:
目前hightide(浪潮)是最新的版本。
将下载的文件解压到安装目录下,即安装成功,如下:
接下来,我们启动一个jetty。上图中,选中的start.jar即是jetty的启动文件:
在命令行中,输入:
C:/>java –jar start.jar
Jetty默认使用8080端口。启动以后,即可以访问测试:
关闭时,在命令行窗口同时按下CTRL+C即可。
2、修改jetty的启动端口号
在Jetty_home\etc\jetty.xml文件,即是jetty的配置文件。可以在里面查找8080,然后修改成你想要端口即可。端口范围应该在1~65535之间。
<Set name="port"><Property name="jetty.port" default="8080"/></Set>
3、将jetty做为内嵌的服务器使用
Jetty的优良特点在于,它可以嵌入到任意的java程序中。并且使用jetty可以快速的开发自己的应用服务器。
1、准备开发嵌入式的jetty服务器jar包
//以下这些包,在jetty_home\lib目录下都可以找到:
jetty-continuation-7.6.2.v20110308.jar
jetty-http-7.6.2.v20110308.jar
jetty-io-7.6.2.v20110308.jar
jetty-security-7.6.2.v20110308.jar
jetty-server-7.6.2.v20110308.jar
jetty-servlet-7.6.2.v20110308.jar
jetty-util-7.6.2.v20110308.jar
jetty-webapp-7.6.2.v20110308.jar
jetty-xml-7.6.2.v20110308.jar
//以下是Servlet项目所需要的包,在tomcat_home\lib的安装目录下都可以找到
servlet-api-2.5.jar
jsp-api.jar
jasper.jar
jasper-el.jar
el-api.jar
ecj-3.7.1.jar (此包非常重要是在Eclipse环境下编译JSP文件、Servlet文件用的)
//以下在tomcat_home\bin目录下可以找到,虽然它是以tomcat命名,但它是一个公共包,主要是用于服务器的日志输出
tomcat-juli.jar
2、书写第一个jetty服务器
以下代码,创建了一个jetty服务器,并发布了一两个Servlet。且设置支持Session。
import org.eclipse.jetty.server.Server; //此类是jetty的服务器类,用于指定绑定的服务器和端口。
import org.eclipse.jetty.server.SessionManager; //此类用于管理服务器的Session
import org.eclipse.jetty.server.session.HashSessionManager; //此类是SessionManager的子类,用hash算法生成Session
import org.eclipse.jetty.server.session.SessionHandler; //此类用于将SessionManager注册到服务器
import org.eclipse.jetty.servlet.ServletContextHandler; //此类用于管理Servlet,可以管理多个Servlet.
import org.eclipse.jetty.servlet.ServletHolder; //此类用于将Servlet注册到服务器
import org.eclipse.jetty.webapp.WebAppContext; //此类,可以用于加载任意一个JavaEE项目。后面会用到
以下是访问内部的代码,类名请你自己添加:
@Test
publicvoid jettyServer2() throws Exception{
Server server = new Server(9090);//声明服务器
ServletContextHandler hand = new ServletContextHandler();//声明ServletContextHandler
hand.addServlet(new ServletHolder(newHttpServlet() { //分别添加两个Servlet
publicvoid doGet(HttpServletRequest req,
HttpServletResponse response) throws ServletException,
IOException {
response.setContentType("text/html;charset=utf-8");
response.setStatus(HttpServletResponse.SC_OK);
response.getWriter().println("<h1>Hello World第二个</h1>");
HttpSession s = req.getSession();
String name = (String) s.getAttribute("name");
response.getWriter().println("<h1>Session is:</h1>"+s+","+name);
response.getWriter().print("<br/>"+this.getServletContext().getRealPath("/"));
}
}), "/a");
hand.addServlet(new ServletHolder(newHttpServlet() {
publicvoid doGet(HttpServletRequest req,
HttpServletResponse response) throws ServletException,
IOException {
response.setContentType("text/html;charset=utf-8");
response.setStatus(HttpServletResponse.SC_OK);
response.getWriter().println("<h1>Hello World第一个</h1>");
HttpSession s = req.getSession();
s.setAttribute("name", "Jack");
response.getWriter().print("<br/><a href='a'>去第二个</a>");
}
}), "/");
//设置内嵌的jetty支持session.默认情况下不支持session
SessionManager sm = new HashSessionManager();
hand.setSessionHandler(new SessionHandler(sm));
server.setHandler(hand);//设置
server.start();//启动
server.join();
}
3、使用jetty的管理器管理一个完整的JavaEE应用
Jetty可以非常方便的管理一个JavaEE应用程序。关键点就在于使用WebAppContext来发布一个JavaEE应用:
/**
* 指定一个现有的项目发布,方式一。
* 以下情况下,默认支持容器的所有对象如:
* request,session,application,等
*/
publicstaticvoid main(String[] args) throws Exception {
String webapp = "D:\\programfiles\\MyEclipse10\\wk3\\day04\\WebRoot";//声明项目所在的目录
Server server = new Server(80); //声明端口
WebAppContext context = new WebAppContext(); //声明上下文对象
context.setDescriptor(webapp + "/WEB-INF/web.xml"); //指定web.xml文件,可选
context.setResourceBase(webapp); //设置项目路径
context.setContextPath("/"); //设置上下文根,可以任意的值
server.setHandler(context); //设置句柄
server.start(); //启动
server.join();
}
也可以使用以下方式发布:
/**
* 指定一个现有的项目发布,方式二
* @throws Exception
*/
@Test
publicvoid jettyServer() throws Exception{
String webapp = "D:\\programfiles\\MyEclipse10\\wk3\\day04\\WebRoot";
Server server = new Server(80);
WebAppContext context = new WebAppContext(webapp,"/abc");
server.setHandler(context);
server.start();
server.join();
}
15、使用注解和动态代理在Service层控制事务
数据库的事务控制可以从JavaWeb的Filter开始,到Struts2的interceptor,最后到Spring的AOP。按我个人的理解应该是粗粒度、中粒度和细粒度。在没有学习interceptor和AOP之前,我们自己完全可以使用现有技术做一个简单的AOP实现。
技术:
声明一个注解,以便于在拦截时判断是否要处理事务。
声明一个代理类。接收被代理对象。
需要说明的:如果使用JDK的动态代理,则注解必须要作用在接口上。如果使用cglib则可以没有接口类,当前注解也就作用在具体类的方法上了。
1、使用JDK的动态代理-需要接口
代码清单1-声明事务注解:
package cn.itcast.tx;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 事务注解
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(value=ElementType.METHOD)
public@interfaceTx {}
代码清单2-声明代理类:
package cn.itcast.tx;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 事务拦截模拟
*/
publicclass TxProxy implements InvocationHandler{
private Object obj;
private TxProxy(Object obj){
this.obj=obj;
}
//接收一个被代理的对象
publicstatic Object newProxy(Object o){
Object proxy = Proxy.newProxyInstance(TxProxy.class.getClassLoader(),
o.getClass().getInterfaces(),
new TxProxy(o));
return proxy;
}
//使用泛型,接收被代理类的Class对象
publicstatic <T>T newProxy(Class<T> cls){
Object o = null;
try{
o = cls.newInstance();
}catch(Exception e){
thrownew RuntimeException(e.getMessage(),e);
}
Object proxy = Proxy.newProxyInstance(TxProxy.class.getClassLoader(),
o.getClass().getInterfaces(),
new TxProxy(o));
return(T)proxy;
}
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Object res = null;
if(method.isAnnotationPresent(Tx.class)){//判断是否添加了Tx事务注解
try{
System.err.println("开始拦截..从当前线程局部对象中获取一个连接");
res = method.invoke(this.obj, args);
System.err.println("拦截完成提交");
}catch(Exception e){
System.err.println("回滚");
thrownew RuntimeException(e.getMessage(),e);
}finally{
System.err.println("结束。。。放回连接池");
}
}else{
res = method.invoke(this.obj, args);
}
return res;
}
}
测试代码清单1:
必须要先完成一个接口:
package cn.itcast.tx;
publicinterface IOne {
@Tx
void save();//添加需要处理事务的注解
void del();
}
实现接口类:
package cn.itcast.tx;
publicclass One implements IOne {
publicvoid save() {
System.err.println("保存。。。");
}
publicvoid del() {
System.err.println("删除。。。。");
}
}
测试:
@Test
publicvoid t1(){
IOne one = TxProxy.newProxy(One.class);
one.save(); //此方法将会处理事务
one.del();
}
测试结果:可见,对于保存方法成功开始了事务:
开始拦截..从当前线程局部对象中获取一个连接
保存。。。
拦截完成提交
结束。。。放回连接池
删除。。。。
2、使用cglib的动态代理
不需要接口。但需要导入cglib的两个jar文件,分为是:
cglib.jar
asm.jar
使用cglib的动态代理,由于不需要接口,当然也可以有接口。所以此时,你的事务注解就必须要注解到实体类的方法上,而不是接口的方法上。
以下是cglib代理的工具类:
仍然使用上面那个Tx注解,所以Tx注解的代码略,直接上cglib的动态代理类:
package cn.itcast.cglib;
import java.lang.reflect.Method;
import cn.itcast.tx.Tx;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
/**
* 使用Cglib的代理
*/
publicclass ProxyUtils implements MethodInterceptor {
private Object src;
private ProxyUtils(Object src){
this.src=src;
}
publicstatic <T>T newProxy(T t){
Enhancer en = new Enhancer();
en.setSuperclass(t.getClass());
en.setCallback(new ProxyUtils(t));
Object o = en.create();
return(T)o;
}
public Object intercept(Object proxy, Method method, Object[] args,
MethodProxy methodProxy) throws Throwable {
Object o = null;
if(method.isAnnotationPresent(Tx.class)){
try{
System.err.println("开始一个事务。。。");
o = method.invoke(src, args);
System.err.println("提交一个事务");
}catch(Exception e){
System.err.println("回滚一个事务");
}finally{
System.err.println("放回连接池");
}
}else{
o = method.invoke(src,args);
}
return o;
}
}
16、用反射模拟Hibernate保存JavaBean
首先要说一下思想。就是要接收一个JavaBean实例对象,然后根据字段信息、类名信息,自己组组织成sql语句最后保存到数据库中。
组件说明:
需要一个@Table注解,自己声明,以便于用户声明的表名与数据库的表名不致的情况。
此注解应该包含一个属性,以便于指定表名。
需要声明一个@Column注解,自己声明以便于用户声明的字段名与JavaBean的列名不一致的情况。
此注解也应该包含一个属性,以便于指定字段名。如果需要还可以指定数据类型。
上代码:
1:@Table注解
package cn.itcast.anno;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(value=ElementType.TYPE)
public@interfaceTable {
public String value();//声明一个属性
}
2:@Column注解
package cn.itcast.anno;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(value=ElementType.FIELD)
public@interfaceColumn {
public String value() default"";//默认值
}
3:工具类代码:接收对象,直接保存:
package cn.itcast.anno;
import java.lang.reflect.Field;
import java.sql.ParameterMetaData;
import java.sql.PreparedStatement;
import java.util.ArrayList;
import java.util.List;
/**
* 这只是保存的示例
* 应该同时提供删除,修改
* 查询的可以直接使用DbUtils
* @author <a href="awangsenfeng@163.com">王森丰</a>
*/
publicclass SaveUtils {
publicstaticvoid save(Object o) throws Exception{
String sql = "insert into";//声明insert的头
String values = " values (";//声明values的头
Class c = o.getClass();
boolean boo = c.isAnnotationPresent(Table.class);
if(boo){ //分析是否带有Table注解,此外分析的还少一句不存在时的处理
Table t = (Table) c.getAnnotation(Table.class);
String vv = t.value();
System.err.println(vv);
sql+=" "+vv;
}
sql+="(";
Field[] f = c.getDeclaredFields();//获取所有字段,来判断是否存在@Column注解
List<Object> oo = new ArrayList<Object>();//声明参数对象
for(Field ff:f){
if(ff.isAnnotationPresent(Column.class)){
Column col = ff.getAnnotation(Column.class);
String vv = col.value();
if(vv.equals("")){
if(sql.endsWith("(")){
sql+=ff.getName();
values+="?";
}else{
sql+=","+ff.getName();
values+=",?";
}
}else{
if(sql.endsWith("(")){
sql+=vv;
values+="?";
}else{
sql+=","+vv;
values+=",?";
}
}
ff.setAccessible(true);
oo.add(ff.get(o));
}
}
sql+=")";
values+=")";
System.err.println(sql+" "+values);//组成有效的sql语句
System.err.println("处理数据....");
PreparedStatement pst = //在正式的应用场合下,所有的Connection应该由用户传递过来,以保证事务
Conn.getConn().prepareStatement(sql+values);
ParameterMetaData param = pst.getParameterMetaData();
int count = param.getParameterCount();//判断有多少参数
System.err.println("count:"+count);
for(int i=0;i<count;i++){
pst.setObject(i+1,oo.get(i));//设置参数
}
pst.execute();//执行代码
Conn.getConn().close();
}
}
- 测试代码:
@Test
publicvoid testSave() throws Exception{
Stud stud = new Stud();
stud.setName("Marray");
stud.setAge(89);
stud.setAddr("中国上海");
SaveUtils.save(stud);
}
- 测试结果:
student
insert into student(name,ages,addr) values (?,?,?)
处理数据....
count:3
保存成功
17、增强request
增强request有两种方式,一种是实现包装模式、另一种是使用动态代理(JDK动态代理或是CGLIB动态代理)。
1、选择哪种方式增强request?
如果官方已经为被增强类,提供了包装类,如SUN公司提供的HttpServletRequestWrapper,则继承官方提供了包装类是一种比较好的选择。如果官方没有提供包装类,则用户可以自己实现包装类或是直接使用动态代理。毕竟个人以为,动态代理的性能要略慢一些。
2、在什么方式增强request?
一般情况下,可以在两个不同的地方增强request和response 。一个是在Filter过虑器中,这也是大多数程序增强request的地方。另一个可选的方案是改造HttpSerlvet。
以下我们将先通过改造HttpServlet的方式增强request。
3、改造HttpServlet增强Request
改造HttpServlet增强Request,同时使用动态代理调用具体的实现类。改造图示例如下,以下分别增强了getParamter、getParameterValues、getParamterMap三个方法,主要处理get请求时的中文乱码问题:
BaseServlet类是改造HttpServlet的核心类,为了增强HttpServletRequest,必须要实现包装模式,即MyRequest继承HttpSerlvetRequestWrapper类,并维护一个HttpServletRequest类型的成员变量。
具体代码如下:
package cn.itcast.pubs;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
/**
* 用一个Servlet实现对request的代理-包装
* 将此类声明成抽象类,建议这么做
* @author <a href="wangjian_me@126.com">王森丰</a>
*/
publicabstractclass BaseServlet extends HttpServlet {
privatestaticfinallongserialVersionUID = 1L;
/**
* 统一处理doGet和doPost方法
*/
@Override
publicvoid doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
doPost(req, resp);
}
@Override
publicvoid doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
req.setCharacterEncoding("UTF-8"); //设置编码格式
String cmd = req.getParameter("cmd");
if(cmd==null || cmd.trim().equals("")){
cmd = "execute"; //如果没有参数则处理execute方法。即默认值
}
try{
//使用反射统一处理
Method method = this.getClass().getMethod(cmd,HttpServletRequest.class,HttpServletResponse.class);
req = new MyRequest(req);
method.invoke(this,req,resp);
}catch(Exception e){
thrownew RuntimeException(e.getMessage(),e);
}
}
protectedabstractvoid execute(HttpServletRequest req,HttpServletResponse resp) throws Exception;
}
/**
* 第一种方案,对requst进行包装
* 速度快,实现简单,易于管理SUN推荐的做法
*/
class MyRequest extends HttpServletRequestWrapper{
public MyRequest(HttpServletRequest request) {
super(request);
}
/**
* 重写getParameter方法
*/
@Override
public String getParameter(String name){
if(getMethod().equalsIgnoreCase("GET")){
String str = super.getParameter(name);
try {
str = new String(str.getBytes("ISO-8859-1"),getCharacterEncoding()); //处理中文乱码,下同
} catch (UnsupportedEncodingException e) {
thrownew RuntimeException(e.getMessage(), e);
}
return str;
}else{
returnsuper.getParameter(name);
}
}
/**
* 重写getParameterValues方法
*/
@Override
public String[] getParameterValues(String name) {
if(getMethod().equalsIgnoreCase("GET")){
String[] params = super.getParameterValues(name);
try{
for(int i=0;i<params.length;i++){
params[i] = new String(params[i].getBytes("ISO-8859-1"),getCharacterEncoding());
}
}catch(Exception e){
thrownew RuntimeException(e.getMessage(),e);
}
return params;
}else{
returnsuper.getParameterValues(name);
}
}
/**
* 重写getParamterMap方法
*/
@Override
public Map<String, String[]> getParameterMap() {
if(getMethod().equalsIgnoreCase("GET")){
Map<String,String[]> param = super.getParameterMap();
Iterator<Map.Entry<String, String[]>> it = param.entrySet().iterator();
try{
while(it.hasNext()){
Entry<String, String[]> en = it.next();
String[] values = en.getValue();
for(int i=0;i<values.length;i++){
values[i] = new String(values[i].getBytes("ISO-8859-1"),getCharacterEncoding());
}
}
}catch(Exception e){
thrownew RuntimeException(e.getMessage(),e);
}
return param;
}else{
returnsuper.getParameterMap();
}
}
}
4、测试getParamter,getParamterValues,getParamterMap三个方法是否可以处理GET中文
package cn.itcast.servlet;
import java.util.Map;
import cn.itcast.pubs.BaseServlet;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 测试各种获取参数的方式
* @author <a href="wangjian_me@126.com">王森丰</a>
*/
@WebServlet("/OneServlet")
publicclass OneServlet extends BaseServlet {
privatestaticfinallongserialVersionUID = 1L;
/**
* 只需要重写execute方法即可
*/
@Override
publicvoid execute(HttpServletRequest req, HttpServletResponse resp)
throws Exception {
String name = req.getParameter("name");
System.err.println("name is:"+name);
System.err.println("-------测试getParamterValues------------------");
String[] ns = req.getParameterValues("name");
System.err.println(ns[0]);
System.err.println("--------测试getParamterMap--------------");
Map<String,String[]> mm = req.getParameterMap();
System.err.println(mm.get("name")[0]);
}
}
5、使用动态代理增强Request
以下类不需要配置到web.xml中,只需要继承它即可。上同。
package cn.itcast.pubs;
import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Iterator;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 使用动态代理增强request
* @author <a href="wangjian_me@126.com">王森丰</a>
*/
publicabstractclass Base2Servlet extends HttpServlet {
privatestaticfinallongserialVersionUID = 1L;
@Override
publicvoid doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
req.setCharacterEncoding("UTF-8");
String cmd = req.getParameter("cmd");
if(cmd==null || cmd.trim().equals("")){
cmd="execute";
}
try{
Method method = this.getClass().getMethod(cmd,HttpServletRequest.class,HttpServletResponse.class);
HttpServletRequest requ = DyncProxy.getProxy(req);//对Request进行动态代理
method.invoke(this, requ,resp);
}catch(Exception e){
thrownew RuntimeException(e.getMessage(),e);
}
}
@Override
publicvoid doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
doPost(req, resp);
}
protectedabstractvoid execute(HttpServletRequest req,HttpServletResponse resp) throws Exception;
}
/**
* 以下是动态代理类
*/
class DyncProxy implements InvocationHandler{
private HttpServletRequest req;
private DyncProxy(HttpServletRequest src){
this.req=src;
}
publicstatic HttpServletRequest getProxy(HttpServletRequest req){
HttpServletRequest reqProxy =
(HttpServletRequest)
Proxy.newProxyInstance(DyncProxy.class.getClassLoader(),
req.getClass().getInterfaces(),new DyncProxy(req));
return reqProxy;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
if(method.getName().equals("getParameter")){ //判断是否为getParameter方法
if(req.getMethod().equalsIgnoreCase("GET")){
String val = req.getParameter((String)args[0]);
val = new String(val.getBytes("ISO-8859-1"),req.getCharacterEncoding());
return val;
}else{
returnreq.getParameter((String)args[0]);
}
}elseif(method.getName().equalsIgnoreCase("getParameterValues")){
String[] vals = req.getParameterValues((String)args[0]);
if(req.getMethod().equalsIgnoreCase("GET")){
for(int i=0;i<vals.length;i++){
vals[i] = new String(vals[i].getBytes("ISO-8859-1"),req.getCharacterEncoding());
}
}
return vals;
}elseif(method.getName().equalsIgnoreCase("getParameterMap")){
Map<String,String[]> mm = req.getParameterMap();
if(req.getMethod().equalsIgnoreCase("GET")){
Iterator<Map.Entry<String, String[]>> it = mm.entrySet().iterator();
while(it.hasNext()){
String[] ss = it.next().getValue();
for(int i=0;i<ss.length;i++){
ss[i] = new String(ss[i].getBytes("ISO-8859-1"),req.getCharacterEncoding());
}
}
}
return mm;
}
return method.invoke(req, args);//执行原始方法
}
}
6、解决硬编码设置request字符集问题
方案1:
注意观察的同学,可能已经发现,在上面的基类中。如在反射代码之前,都设置了request的编码格式。但都是通过硬编码的方式设置的。解决不使用硬编码的问题,可以在子类中配置初始化参数,然后在父类中获取。因为父类是抽象类,不可以实例化,不可以在父类中配置初始化参数,在子类中配置的初始化参数,可以在父类中获取,示例如下:
抽象父类:
publicabstractclass Base2Servlet extends HttpServlet {
privatestaticfinallongserialVersionUID = 1L;
@Override
publicvoid doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String charset = getInitParameter("charset"); //获取子类配置的初始化参数
System.err.println("charset:"+charset);
if(charset!=null){
req.setCharacterEncoding(charset);
}
String cmd = req.getParameter("cmd");
if(cmd==null || cmd.trim().equals("")){
cmd="execute";
}
try{
Method method = this.getClass().getMethod(cmd,HttpServletRequest.class,HttpServletResponse.class);
HttpServletRequest requ = DyncProxy.getProxy(req);//对Request进行动态代理
method.invoke(this, requ,resp);
}catch(Exception e){
thrownew RuntimeException(e.getMessage(),e);
}
}
//其他代码略
}
子类中设置初始化参数的代码如下:
@WebServlet(urlPatterns="/TwoServlet",
initParams={@WebInitParam(name="charset",value="UTF-8")})//通过注解设置初始化参数
publicclass TwoServlet extends Base2Servlet {
//内部代码略
}
方案2:
也可以在web.xml中配置ServletContext的初始化参数。然后在抽象类中获取:
web.xml配置代码:
<context-param>
<param-name>charset</param-name>
<param-value>UTF-8</param-value>
</context-param>
在抽象类中获取的代码:
publicvoid doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String charset = getServletContext().getInitParameter("charset");
if(charset!=null){
req.setCharacterEncoding(charset);
}
}
//其他代码略….
18、增强response
一般增强response是指在Servlet中输出信息时,进行数据的压缩处理。但目前MVC模式不建议在Servlet的直接显示HTML信息。所以,再按照增强request的方式来改造HttpServlet就不太适合了。此时,则必须应该在Filter(过虑器)中完成。且只对*.jsp结尾的响应进行压缩。
具体代码略。
19、类与类之间的关系
泛化:表示类与类之间的继承关系、接口与接口之间的继承关系;
实现:表示类对接口的实现;
依赖:当类与类之间有使用关系时就属于依赖关系,不同于关联关系,依赖不具有“拥有关系”,而是一种“相识
关系”,只在某个特定地方(比如某个方法体内)才有关系。
关联:表示类与类或类与接口之间的依赖关系,表现为“拥有关系”;具体到代码可以用实例变量来表示;
聚合:属于是关联的特殊情况,体现部分-整体关系,是一种弱拥有关系;整体和部分可以有不一样的生命周期;是
一种弱关联;
组合:属于是关联的特殊情况,也体现了体现部分-整体关系,是一种强“拥有关系”;整体与部分有相同的生命周
期,是一种强关联;
20、Mysql的字符编码问题
使用mysql总是经常被乱码所困扰,以下就简单说一下,mysql是如何管理字符编码的。
Mysql管理字符编码,使用三个不同的等级,分别为:
客户端(character_set_client) :即客户端,将以什么编码形式准备数据。
服务器(character_set_server):即服务器,将以什么编码形式保存数据。
连接器(character_set_connection):即客户端与服务器之间进行通信时,以什么编码方式进行通信。
Mysql对于每一个数据库,每一个表,每一个字段都可以单独设置字符编码格式。我个人认为。没有必须设置的那么细。只要编码格式可以精确到数据库,就足可以满足开发的需要。一般情况下,将数据库的编码格式设置成UTF8。
1、一些查看命令如下:
show variables; //显示客户端(当前)与服务器的所有变量设置信息。
(图略)
Show variables like ‘character%’; //只显示编码信息
此处可见,客户端、连接都是utf-8编码。
而数据库则为ISO-8859-1,
服务器也是ISO-8859-1。
为了保证不出现乱码,对于中文环境一般设置客户端环境为GBK,并与结果数据result设置的编码相同。数据库为UTF-8。
在命令行,设置编码格式:
Shell/> set character_set_client=utf8;
2、Mysql的默认配置:
如果没有给mysql配置字符集,mysql将选择默认配置。默认的配置从何而来呢?
(1)编译MySQL 时,指定了一个默认的字符集,这个字符集是 latin1;
(2)安装MySQL 时,可以在配置文件 (my.ini) 中指定一个默认的的字符集,如果没指定,这个值继
承自编译时指定的;
(3)启动mysqld 时,可以在命令行参数中指定一个默认的的字符集,如果没指定,这个值继承自配置
文件中的配置,此时 character_set_server 被设定为这个默认的字符集;
(4)当创建一个新的数据库时,除非明确指定,这个数据库的字符集被缺省设定为
character_set_server;
(5)当选定了一个数据库时,character_set_database 被设定为这个数据库默认的字符集;
(6)在这个数据库里创建一张表时,表默认的字符集被设定为 character_set_database,也就是这个
数据库默认的字符集;
(7)当在表内设置一栏时,除非明确指定,否则此栏缺省的字符集就是表默认的字符集;
简单的总结一下,如果什么地方都不修改,那么所有的数据库的所有表的所有栏位的都用 latin1 存储,
不过我们如果安装 MySQL,一般都会选择多语言支持,也就是说,安装程序会自动在配置文件中把
default_character_set 设置为 UTF-8,这保证了缺省情况下,所有的数据库的所有表的所有栏位的都用
UTF-8 存储。
3、一些有用的命令
/*显示所有系统变量的设置*/
SHOW VARIABLES;
/*显示所有以character开头的环境设置*/
SHOW VARIABLES LIKE 'character%';
/*功能同上*/
SHOW VARIABLES WHERE VARIABLE_Name LIKE 'character%';
/*显示当前数据库的编码格式*/
SHOW VARIABLES WHERE VARIABLE_NAME= 'character_set_database';
/*使用全局变量查询,功能同上*/
SELECT @@character_set_database;
/*设置自增量从1开始。默认就是从1开始*/
SET AUTO_INCREMENT_increment=1;
/*设置自增量每次增加2*/
SET AUTO_INCREMENT_offset=2;
21、JDBC4.0自动加载驱动器类
从JDK1.6开始,Oracle就将修改了添加了新的加载JDBC驱动的方式。即JDBC4.0。在启动项目或是服务时,会判断当前classspath中的所的jar包,并检查META-INF目录下,是否包含services文件夹,如果包含,就会将里面的配置加载成相应的服务。
如Oracle11g的ojdbc6.jar包:
META-INF/services/jdbc.sql.Driver文件内容只有一行,即实现java.sql.Driver的类:
oracle.jdbc.OracleDriver
Oracle在随即发布的mysql-connector-java-5.1.8.jar中,也同样添加了上述的特性:
里面的内容,也是一句,即:
com.mysql.jdbc.Driver
为了验证是否会自动加载数据库驱动类,我们书写一段Java代码:
publicstaticvoid main(String[] args) throws Exception {
//从DriverManager中获取所有驱动类,遍历并输出
Enumeration<java.sql.Driver> en = DriverManager.getDrivers();
while(en.hasMoreElements()){
java.sql.Driver d = en.nextElement();
System.err.println(d.toString());
}
}
在MyEclipse或是Eclipse环境下,添加两个Jar包,分别为ojdbc6.jar的mysql-connector-5.1.8.jar:运行结果如下:
sun.jdbc.odbc.JdbcOdbcDriver@173a10f
oracle.jdbc.OracleDriver@e09713
com.mysql.jdbc.Driver@1f1fba0
可见,jdk1.6加载了三个驱动类。
为了更好的验证此问题,我们再从命令行依次加载不同的jar包进行测试:
第一次执行,没有指定任何的外部jar文件,可见只加载了系统的JdbcOdbcDriver:
第二次执行:先设置ojdbc6.jar,即oracle的驱动包,可见已经自动加载了两个驱动类:
第三次运行:再加载mysql-connector-java-5.1.18.jar(如果你用的是5.1.5你可以自己手工在jar包中的META-INF下建立一个services文件夹,然后建立一个java.sql.Driver文件,里面输入com.mysql.jdbc.Driver即可):注意以下代码中,使用%classpath%串联已经设置的classpath:
需要说明的是,JDK6是一个聪明的设计。如果发现你已经注册过某个驱动,将不会再次为你注册这个驱动。除非你显式的调用了DriverManager.registerDriver(new SomeDriver())方法:
以下是若干个测试:
publicstaticvoid main(String[] args) throws Exception {
DriverManager.registerDriver(new Driver());//自己通过严重依赖的方式注册一个,不建议这样做
Enumeration<java.sql.Driver> en = DriverManager.getDrivers();
while(en.hasMoreElements()){
java.sql.Driver d = en.nextElement();
System.err.println(d.toString());
}
}
输出结果:
sun.jdbc.odbc.JdbcOdbcDriver@66848c
oracle.jdbc.OracleDriver@47b480
com.mysql.jdbc.Driver@9931f5
com.mysql.jdbc.Driver@19ee1ac
测试代码2:
publicstaticvoid main(String[] args) throws Exception {
Class.forName("com.mysql.jdbc.Driver");//即便是删除也会只注册一个
Enumeration<java.sql.Driver> en = DriverManager.getDrivers();
while(en.hasMoreElements()){
java.sql.Driver d = en.nextElement();
System.err.println(d.toString());
}
}
结果:
sun.jdbc.odbc.JdbcOdbcDriver@66848coracle.jdbc.OracleDriver@47b480
com.mysql.jdbc.Driver@19ee1ac
22、保证永远从ServletCofing、FilterConfig的获取ServletContext对象
由于容器对HttpServletRequest对象,做了过多的修改。当用户快速多次请求,并从HttpServletRequest对象中获取ServletContext时有可能会抛出一个NullPointerException异常。为了保存,永远可以获取到ServletContext对象,且即使快速多次请求也可以获取到ServletContext对象,特别是在Filter中获取ServletContext对象时,就必须要从FilterConfig中获取ServletContext对象:
以下代码,当用户访问网站的任何一下页面时,都给ServletContext中的一个key值加1,由于这新数据并不是最主要的数据,更不能影响到用户的操作,所以,应该放到一个独立的线程中去访问。同时为了防止同时增加造成数据丢掉的问题,应该给方法添加同步代码块。
如果你看过HttpServlet、GenericServlet的源代码,你也不难发现,它们都是从Config(配置)对象中获取ServletContext对象的。
示例代码:以下功能,用于记录网站的刷新或是访问量。
package cn.itcast.filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
/**
* 开始一个线程不影响用户的操作感受
* @author <a href="wangjian_me@126.com">王森丰</a>
*/
publicclass CountFilter implements Filter{
private Add add = new Add();
publicvoid destroy() {
}
publicvoid doFilter(ServletRequest arg0, ServletResponse arg1,
FilterChain arg2) throws IOException, ServletException {
new Thread(){//开始一个新的线程不影响用户的操作感受
publicvoid run() {
//必须要从FilterConfig中获取Ctx对象,速度+稳定,尽量不要从request中获取ServletContext
add.add(conf.getServletContext());
};
}.start();
arg2.doFilter(arg0, arg1);
}
private FilterConfig conf;
publicvoid init(FilterConfig arg0) throws ServletException {
this.conf=arg0;
}
}
class Add{
//同步代码块,通过一个线程来操作,不会影响用户的感受
publicsynchronizedvoid add(ServletContext ctx){
Integer count = (Integer) ctx.getAttribute("count");
count++;
ctx.setAttribute("count",count);
}
}
23、JSTL将Long转成时间类型:
声明时间对象
<jsp:useBeanid="tm"class="java.util.Date"></jsp:useBean>
//设置一个Long类型的时间
<c:settarget="${tm}" property="time"value="${pageContext.request.session.creationTime}"></c:set>
//格式化显示
<fmt:formatDatevalue="${tm}" pattern="yyyy-MM-dd HH:mm:ss"/>
24、DIV样式float:left对不齐的解决方案
<styletype="text/css">
.div{
border:1px solid #E0E0E0;
font-size:10pt;
margin:3px;
float:left;
text-align:center;
}
.img{
width:100px;
height:120px;
border:0px;
}
</style>
如上例,定义一个div的样式表,按规定,应该显示为所有的div左对齐,但有可能不会左对齐,出现以下情况:
看了DHTML文档对float:left的说明以后,终于明白了。对于使用了float的元素,应该指定width宽度,如果没有指定宽度,则浏览器会给你指定一个宽度,英文原说明如下:
所以,上面的style样式表,应该修改成:
<styletype="text/css">
.div{
border:1px solid #E0E0E0;
font-size:10pt;
margin:3px;
float:left;
text-align:center;
width:110px;
}
.img{
width:100px;
height:120px;
border:0px;
}
</style>
即添加一个width属性。
25、增强HttpServlet实现类似于Struts的配置
页面的转发和重定向,都必须要在web.xml中进行配置。实现程序的可配置的功能。
通过反射,在接收到目标servlet的返回的字符串以后。如果不是null。则决定是转发还是重定向到目标页面。
以下是完整源代码:
package cn.itcast.pubs;
import java.io.IOException;
import java.lang.reflect.Method;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 用户继承此类,处理多方法,接收用户的返回值转发或是重定向到其他资源<br/>
* 如果是重定向可以向request中设置名称为param的属性,<br/>
* 即request.setAttribute("param","name=Jack");<br/>
* 默认为转发<br/>
* 如果不希望此工具类为你转发,则可以返回null值
* @author <a href="wangjian_me@126.com">王森丰</a>
*/
publicabstractclass Base3Servlet extends HttpServlet{
privatestaticfinallongserialVersionUID = 1L;
@Override
publicvoid doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
doPost(req, resp);
}
@Override
publicvoid doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
req.setCharacterEncoding("UTF-8");//此处应该通过过虑器(Filter)设置编码
String cmd = req.getParameter("cmd");
if(cmd==null || cmd.trim().equals("")){
cmd = "execute";
}
try{
Method m = this.getClass().getMethod(cmd,HttpServletRequest.class,HttpServletResponse.class);
String result = (String) m.invoke(this,req,resp);
if(result!=null){
if(result.startsWith("redirect:")){
result = result.substring(result.lastIndexOf(":")+1); //截取redirect后面的值
String page = getPage(result);
//获取参数,最后结果为some.jsp?name=Jack&...
String param=(String) req.getAttribute("param");
if(param!=null){
page = page+"?"+param;
}
System.err.println("page is :"+page);
if(page.startsWith("http")){
resp.sendRedirect(page);//重定向到外部网站
}else{
resp.sendRedirect(req.getContextPath()+page);//重定向到本网内的网页
}
}else{
String page = getPage(result);
req.getRequestDispatcher(page).forward(req, resp);
}
}
}catch(NoSuchMethodException e){
thrownew RuntimeException("没有此方法:"+e.getMessage(),e);
}catch(Exception e){
thrownew RuntimeException(e.getMessage(),e);
}
}
/**
* 先在此Servlet中查找,如果没有则到Context中查找配置
*/
private String getPage(String result){
String page = getInitParameter(result); //获取配置页面,先在此Servlet中找,如果没有则再找
if(page==null){
page = getServletContext().getInitParameter(result); //如果在servlet配置到找不到则找Context范围
}
return page;
}
/**
* 要求返回一个String,根据String串获取配置的初始化参数<br/>
* 转发或是重新定向到目标页面<br/>
* redirect:前缀为重定向,即return "redirect:page";<br/>
* 默认为转发
*/
publicabstract String execute(HttpServletRequest req,HttpServletResponse resp) throws Exception;
}
以下是使用示例:
配置示例如下:
<context-param>
<param-name>back</param-name>
<param-value>/jsps/result.jsp</param-value>
</context-param>
<servlet>
<servlet-name>back</servlet-name>
//以下BackStringServlet类,它继承了Base3Servlet工具类
<servlet-class>cn.itcast.servlet.BackStringServlet</servlet-class>
<init-param>
//先在这儿查找back,如果这儿没有,再查找Context的配置
<param-name>back</param-name>
<param-value>/jsps/result.jsp</param-value>
</init-param>
</servlet>
BackStringServlet.java源代码如下:
package cn.itcast.servlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import cn.itcast.pubs.Base3Servlet;
publicclassBackStringServletextends Base3Servlet{
@Override
public String execute(HttpServletRequest req, HttpServletResponse resp)
throws Exception {
req.setAttribute("name", "Jack");
return"back";
//也可以按以下方式返回,redirect决定为重定向
//return "redirect:back";
}
}
26、ajax-自己封装XMLHttpRequest对象
封装的目的:快速调用。
直接上代码,以下示例仅封装了post请求:
//生成ajax对象
function Ajax(){
this.getHttp=function(){//生成ajax对象
if(window.XMLHttpRequest){
returnnew XMLHttpRequest();
}else{
returnnew ActiveXObject("Microsoft.XMLHttp");
}
};
var http = this.getHttp();//获取XHR对象
/**
*post请求,接收三个参数为url,param,success,failure
*/
this.post = function(url,param,func,failure){
http.open("POST",url,true);
http.setRequestHeader("Content-Type","application/x-www-form-urlencoded");
http.onreadystatechange=function(){
if(http.readyState==4){
if(http.status==200){
var txt = http.responseText;
//调用用户的成功方法
func(txt);
}else{
//如果存在失败方法则,如果失败,则调用用户的失败方法
if(failure){
failure(http.status,http);
}
}
}
};
if(param){
http.send(param);
}else{
http.send();
}
};
}
调用测试:
var ajax = new Ajax();
var url = path+"buy/BuyServlet";
ajax.post(url,"cmd=addOrDel&id="+obj.bookId+"&num=1",function(data){
var amt=data;
//以下是其他计算
tr.cells[2].innerHTML=amt;
tr.cells[3].innerHTML=(amt*parseFloat(tr.cells[1].innerHTML)).toFixed(2);
calcute();
});
27、自定义类加载器
用户将需要加载的类传递过来,由本类负责加载一个.class文件的字节码自定义类加载器,不可以覆盖loadClass方法。
loadClass的过程永远是JDK干的事。
NOTE:只不过,在JDK中,它会认为哪一个类读取到了类的字节码,哪一个类就是类加载器。
package cn.itcast.demo;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
/**
* 用户将需要加载的类传递过来,由本类负责加载一个.class文件的字节码自定义类加载器,不可以覆盖loadClass方法。
* loadClass的过程永远是JDK干的事,只不过,在JDK中,它会认为哪一个类读取到了类的字节码,哪一个类就是类加载器。
* @author <a href="awangsenfeng@163.com">王森丰</a>
*/
publicclass MyLoader2 extends ClassLoader {
@Override
public Class<?> findClass(String name) throws ClassNotFoundException {
String clsName = name; // 获取类的原始名称
name = name.replaceAll("\\.", "/"); // 将类名中的.换成\,即路径.因为.是正则表达式的关键字,所以必须要转换
name = name + ".class"; // 带上.class后缀,以确定为.class文件
String path = ClassLoader.getSystemResource(name).getPath();//从当前项目的目录下找到类文件
File file = new File(path);
try {
InputStream in = new FileInputStream(file);
byte[] b = newbyte[in.available()];// 一次全部读取字节码,可选的使用ByteArrayOutputStream
int len = in.read(b);
Class<?> cls = defineClass(clsName, b, 0, len);//加载类加载字节码,注解类的名称为cn.xx.xx.ClsName.class
return cls;
} catch (Exception e) {
e.printStackTrace();
}
returnnull;
}
/**
* 接收一个类的字节码自己分析并加载类
*/
@SuppressWarnings("unchecked")
public <T> T findClass(Class<T> cls) throws Exception {
String name = cls.getName();// 获取类的名称
Class<?> cls1 = loadClass(name);// 装载类的字节码,此时会调用findClass方法
T o = (T) cls1.newInstance();// 在内部直接实例化此对象,会调用默认构造方法
return o;
}
}
28、Thread与Runnable
Thread是线程对象,Runnable主宿主对象。
测试以下代码,在以下代码中,给Thread类的构造方法传递了一个Runnable实例,同时也实现了Thread的run方法,那么Thread.start()具体会执行哪一个Run方法呢?
结果为运行Thread的run方法。
publicstaticvoid main(String[] args) throws Exception {
new Thread(new Runnable() {//给Thread的构造方法传递一个Runnable接口
publicvoid run() {
while(true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.err.println("Runnable的RUN方法:"+Thread.currentThread().getName());
}
}
}){
publicvoid run() {//同时实现Thread的run方法,这段代码会运行
while(true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.err.println("线程的RUN方法:"+Thread.currentThread().getName());
}
};
}.start();
}
为什么会运行Thread类中的run方法呢?查看Thread类源代码的run方法如下:
publicvoid run() {
if (target != null) {
target.run();
}
}
而target就是Runnable类的对象。可见,如果子类不覆盖run方法,且存在target的情况下,即会运行target的run方法。所以,更准确的说,Runnable不是一个线程,而是一个线程执行的宿主对象。
29、Spring竟然是用jaxp的dom方式读取的XML配置文件
遍读Spring的ClassPathXmlApplication后才知道,原来Spring是用jaxp的dom方式加载的xml配置文件。Spring的类:DefaultDocumentLoader专门负责加载xml配置文件。以下是它的部分代码:
/**
* Load the {@link Document} at the supplied {@link InputSource} using the standard JAXP-configured
* XML parser.
*/
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
if (logger.isDebugEnabled()) {
logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
}
DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
return builder.parse(inputSource);
}
/**
* Create the {@link DocumentBuilderFactory} instance.
* @param validationMode the type of validation: {@link XmlValidationModeDetector#VALIDATION_DTD DTD}
* or {@link XmlValidationModeDetector#VALIDATION_XSD XSD})
* @param namespaceAware whether the returned factory is to provide support for XML namespaces
* @return the JAXP DocumentBuilderFactory
* @throws ParserConfigurationException if we failed to build a proper DocumentBuilderFactory
*/
protected DocumentBuilderFactory createDocumentBuilderFactory(int validationMode, boolean namespaceAware)
throws ParserConfigurationException {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(namespaceAware);
//这儿原来有很多代码,我都删去了
return factory;
}
从上面的注释和加载方式可以明显的看出,加载XML的方式为JAXP-dom。
30、Spring的配置文件中,可以直接引用hibernate.cfg.xml文件
在Spring整合Hibernate时,一般都将hibernate的配置全部配置到Spring的配置中,大体示例如下:
<!--配置Hibernate -->
<beanid="sessionFactory"class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<propertyname="dataSource"ref="dataSource"></property>
<propertyname="hibernateProperties">
<props>
<propkey="hibernate.dialect">${hibernate.dialect}</prop>
<propkey="hibernate.show_sql">true</prop>
<propkey="hibernate.generate_statistics">${hibernate.generate_statistics}</prop>
</props>
</property>
<propertyname="eventListeners">
<map>
<entrykey="merge">
<beanclass="org.springframework.orm.hibernate3.support.IdTransferringMergeEventListener"/>
</entry>
</map>
</property>
<propertyname="mappingDirectoryLocations">
<list>
<value>classpath:com/myexam/domain</value>
</list>
</property>
</bean>
正如上面添加了底色的部分一样。这样配置当然可以,但如果信息有很多,就是让这个文件特别的庞大。且,在这儿输入不会有任何的提示。
而Spring给了另一种方式可以快速的引用一个hibernate.cfg.xml文件,这样就大大方便了编写hibernate的属性信息:
如下:
<beanid="sessionFactory"
class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<propertyname="dataSource"ref="dataSource"></property>
<!-- 可以一次导入多个包中的所有添加了注解的类,可以是多个包 -->
<propertyname="packagesToScan">
<list>
<value>cn.itcast.domain</value>
</list>
</property>
<!-- 看看是否还可以导入映射文件,可以是多个文件
<property name="mappingLocations">
<list>
<value>cn/itcast/domain/Human.hbm.xml</value>
</list>
</property>
-->
<!--或是直接设置映射文件目录 -->
<propertyname="mappingDirectoryLocations">
<list>
<value>cn/itcast/domain</value>
</list>
</property>
<!-- 引入一个hibernate的配置文件,在那儿可以更加方便的配置更多信息 -->
<propertyname="configLocation"value="hibernate.cfg.xml"/>
</bean>
请注意最后一句。
所有上面的信息,都是通过查看AnnotationSessionFactoryBean和LocalSessionFactoryBean两个类的源代码信息获知。
31、灵活使用Struts2的json插件
虽然struts2的json已经设计的很好,但还是缺少灵活,所以,建议用户自己实现,或是配置公共页面的方式实现。
Struts2.1.7以后,添加了struts2-json-plugin.jar文件。用于解析json。将json插件文件,及相前依赖的jar包放到lib目录下,然后配置如下:
<packagename="it2"extends="json-default">
<actionname="two"class="cn.itcast.action.TwoAction">
<resultname="success"type="json">
<paramname="contentType">text/plain</param>
<paramname="defaultEncoding">UTF-8</param>
<!--如果是一个map则只取map中的一个属性<param name="root">name</param> -->
<!--仅对map或是根对象有效,对list中的数据无效
<param name="excludeWildcards">url</param> -->
</result>
</action>
</package>
Json插件,默认只从OgnlValueStack的栈顶取一个对象。所以,如果实现了ModelDriven则会取ModelDriver<T>中的T对象。如果没有实现则为Action对象本身。
但我们可以通过操作OgnlValueStack向栈顶加入我们的对象:
package cn.itcast.action;
import java.util.List;
import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.criterion.Restrictions;
import cn.itcast.domain.ZTree;
import cn.itcast.utils.HibernateUtils;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionSupport;
import com.opensymphony.xwork2.ModelDriven;
publicclassTwoActionextends ActionSupport implements ModelDriven<ZTree>{
@Override
public String execute() throws Exception {
Session s = HibernateUtils.openSession();
Criteria c = s.createCriteria(ZTree.class);
if(ztree.getId()!=null && !ztree.getId().equals("")){
c.add(Restrictions.eq("parent",ztree.getId()));
}else{
c.add(Restrictions.isNull("parent"));
}
List<ZTree> list = c.list();
//向栈顶加入我们要输出的对象
ActionContext.getContext().getValueStack().push(list);
s.close();
returnSUCCESS;
}
private ZTree ztree = new ZTree();
public ZTree getModel() {
returnztree;
}
}
其他详细信息:
以下是JSONResult的源代码:
@Inject(StrutsConstants.STRUTS_I18N_ENCODING)//通过注入又修改了默认编码为UTF-8
publicvoid setDefaultEncoding(String val) {
this.defaultEncoding = val;
}
其他配置示例:
32、在Spring中使用aop模拟事务管理
通过AOP标标签将一个POJO声明成切面的方式实现。
Tx类的源代码如下:
package cn.itcast.utils;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
/**
* 使用代理自动处理事务<br/>
* 由于本对象是单例的,所以,必须只能声明一个线程的局部变量<br/>
* 其他成员变量将会产生数据共享问题。<br/>
* 另:由于在局部的线程中,应该保存被代理的Connection和原始的Connection.<br/>
* 所以使用一个ThreadLocal<Map<String,Object>>来保存数据<br/>
* 同时,在执行完成sercice方法以后,必须要保存关闭原始的数据连接。<br/>
* @author <a href="awangsenfeng@163.com">王森丰</a>
* @version 1.0 2011-4-17
*/
publicclass Tx implements ApplicationContextAware{
private ThreadLocal<Map<String,Object>> tl = new ThreadLocal<Map<String,Object>>();//用于维护当前局部线程的变量
public Object aroundConn(ProceedingJoinPoint p) throws Throwable{
Map<String,Object> pool = tl.get();
System.err.println("缓存对象为:"+pool);
if(pool!=null && pool.get("tx").equals(true)){
if(pool.get("proxed")!=null){
return pool.get("proxed");
}
final Object srcObj = p.proceed();
Enhancer en = new Enhancer();//使用CGLIB的动态代理,防止用户关闭连接,在操作完成service后,由我们自己关闭连接
en.setSuperclass(Connection.class);
en.setCallback(new MethodInterceptor() {
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
if(method.getName().equals("close")){
System.err.println("有其他程序操作关闭方法,不作为");
returnnull;
}
return method.invoke(srcObj, args);
}
});
Object proxed = en.create();
pool.put("srcObj",srcObj);//将原始的Connection放到map中
pool.put("proxed",proxed);//将被代理的Connection放到map中
return pool.get("proxed");//直接返回被代理的Connection对象
}else{
Object o = p.proceed();
return o;
}
}
public Object aroundService(ProceedingJoinPoint p) throws Throwable{
Map<String,Object> pool = tl.get();//访问Map,以设置是否打开事务
if(pool==null){
pool = new HashMap<String, Object>();
pool.put("tx", true);//设置打开事务为true
tl.set(pool);
}
Connection con = null;
Object o = null;
try{
System.err.println("开始一个事务"+Thread.currentThread().getName());
con = ctx.getBean(DataSource.class).getConnection();
con.setAutoCommit(false);
o = p.proceed();
System.err.println("提交一个事务");
con.commit();
}catch(Exception e){
System.err.println("回滚一个事务");
con.rollback();
}finally{
con.setAutoCommit(true);
if(tl.get()!=null){
System.err.println("关闭原始的连接");
((Connection)tl.get().get("srcObj")).close();//关闭原生的对象
}
tl.remove(); //删除当前线程中的对象
}
System.err.println("拦截Servciee完成");
return o;
}
private ApplicationContext ctx;
publicvoid setApplicationContext(ApplicationContext ctx)
throws BeansException {
this.ctx=ctx;
}
}
在Spring文件中的配置如下:
<!-- 声明事务的管理类 -->
<beanid="tx"class="cn.itcast.utils.Tx"></bean>
<!-- 配置事务管理 -->
<aop:config>
<aop:aspectref="tx">
<!-- 请修改以下拦截,具体应作用在具体类上,由于是我们自己写的,不可以作用在接口上 -->
<aop:aroundmethod="aroundService"pointcut="execution(* cn..*ServiceImpl.*(..))"/>
<!-- 以下代码是不变的配置,请不要修改,主要是对getConnection方法进行拦截,以代理Connection对象 -->
<aop:aroundmethod="aroundConn"pointcut="execution(* *..*.getConnection())"/>
</aop:aspect>
</aop:config>
以上程序,发布到Java项目或是web项目中,都可以使用。
需要以下jar文件的支持:
33、Xwork2容器
1、为什么在struts2中默认的Action为ActionSupport
Struts2是在xwork2的基础上建立起来的开源框架。Xwork2是一个非常不错的框架,更重要的它是一个容器。
在xwork2的核心配置文件中我们可找到,为什么struts2中默认的action为ActionSupport?
其实在struts-default.xml文件中,又重新进行了定义,但与上面保持一样:
2、Xwork2的注入框架
需要xwork-core-2.3.1.jar包。
1、Xwork2的容器类为Container它只是一个接口。它的子类ContainerImpl设置为受保护的类,外部程序不可以访问。
2、@Inject是Xwork2的注解类,用于在运行时注入受Xwrok管理的对象。
3、ContainerBuilder类为Xwork2的工具类,用于创建Container容器对象。
由于Struts2的底层实现为xwork2,所以有必要小小的研究以下xwrok2管理容器的注入值的方式。
以下是示例代码:
示例1:最简单的用容器管理一个对象的创建:
package tt;
import com.opensymphony.xwork2.inject.Container;
import com.opensymphony.xwork2.inject.ContainerBuilder;
/**
* XWork的容器框架
* @author <a href="awangsenfeng@163.com">王森丰</a>
* @version 1.0 2011-4-18
*/
publicclass XWorkContainerDemo {
publicstaticvoid main(String[] args) {
//XWork的容器框架,声明容器构建对象
ContainerBuilder cb = new ContainerBuilder();
//设置一个被管理对象
cb.factory(Person.class);
//创建容器
Container c = cb.create(true);
//从容器中获取被容器创建的对象。以下代码,每次都会创建一个新对象
Person p = c.getInstance(Person.class);
//调用对象的方法
p.say();
}
}
//声明一个被管理对象
class Person{
publicvoid say(){
System.err.println("Hello");
}
}
运行结果,即是输出Hello。
示例2:最简单的使用@Inject注入:
package tt;
import com.opensymphony.xwork2.inject.Container;
import com.opensymphony.xwork2.inject.ContainerBuilder;
import com.opensymphony.xwork2.inject.Inject;
/**
* XWork的容器框架
* @author <a href="awangsenfeng@163.com">王森丰</a>
* @version 1.0 2011-4-18
*/
publicclass XWorkContainerDemo2 {
publicstaticvoid main(String[] args) {
//声明容器构建对象
ContainerBuilder cb = new ContainerBuilder();
//设置被管理对象
cb.factory(Tom.class);
//设置一个被管理的常量,key为name值为Jack
cb.constant("name","我的名称是Jack");
//创建容器
Container c = cb.create(true);
//从容器中获取Tom的实例
Tom t = c.getInstance(Tom.class);
//调用say方法
t.say();
}
}
class Tom{
//注意此处,注入name的值
@Inject(value="name")
private String name;
publicvoid say(){
//以下将输出Hello>Jack
System.err.println("Hello>"+name);
}
}
输出的结果为:
Hello>我的姓名是Jack
示例3:声明接口及实现类,通过构造方法注入
package tt;
import com.opensymphony.xwork2.inject.Container;
import com.opensymphony.xwork2.inject.ContainerBuilder;
import com.opensymphony.xwork2.inject.Inject;
/**
* XWork的容器框架
* @author <a href="awangsenfeng@163.com">王森丰</a>
* @version 1.0 2011-4-18
*/
publicclass XWorkContainerDemo3 {
publicstaticvoid main(String[] args) {
ContainerBuilder cb = new ContainerBuilder();
cb.factory(IAnimal.class,Cat.class);//这两个类上必须要有继承关系
cb.factory(IFood.class,Fish.class);
Container c = cb.create(true);
IAnimal cat = c.getInstance(IAnimal.class);
cat.eat();
}
}
interface IAnimal{ //声明接口
publicvoid eat();
}
class Cat implements IAnimal{
private IFood food;
/**
* 如果要通过构造方法注入
* 则必须要要构造方法上,和参数上同时添
* Inject注解
*/
@Inject
public Cat(@Inject IFood food) {//构造中的参数,按类型进行匹配
this.food=food;
}
publicvoid eat() {
System.err.println("小猫在吃:"+food);
}
}
interface IFood{}
class Fish implements IFood{
public String toString() {
return"我是小鱼";
}
}
输出的结果为:小猫在吃:我的小鱼
其他几个方法
34、为struts2开发简易插件获取SpringBean的实例
1、根据Class类型从Spring中获取实例
这种方式不够灵活,只能算是入门级!
不好处有:
- 所有Action类必须要在Struts.xml中配置,且必须配置完整的类名。
- 所有Action必须是Action/ActionSupport的子类。
其实Struts中已经有非常不错的插件可以直接整合Spring。但要学习其中的原理还是要自己动手感受以下。
- Struts2中的所有Action\interceptor其实都是由名称为ObjectFactory的类创建起来的。而ObjectFactory是xwork的一个类。Struts2对此类进行了扩展。以下是它的配置:
<beanclass="com.opensymphony.xwork2.ObjectFactory"name="xwork"/>
<beantype="com.opensymphony.xwork2.ObjectFactory"name="struts"
class="org.apache.struts2.impl.StrutsObjectFactory"/>
StrutsObjectFactory类扩展了ObjectFactory类。如果我们要想知道Action类是如何创建的,则可以也扩展ObjectFactory或StrutsObjectFactory类。
在ObjectFactory中最重要的方法为:
于是,我们开始书写我们自己的ObjectFacotry:
具体分为以下几步:1 :书写对象工厂即ObjectFactory或StrutsObjectFactory(建议)的子类。
2 :配置此类,修改Struts2创建类的过程。
第一步:书写类
package cn.itcast.utils;
import java.util.Map;
import javax.servlet.ServletContext;
import org.springframework.context.ApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import com.opensymphony.xwork2.Action;
import com.opensymphony.xwork2.ObjectFactory;
import com.opensymphony.xwork2.inject.Inject;
/**
* 书写自己的ObjectFactory
* @author <a href="awangsenfeng@163.com">王森丰</a>
* @version 1.0 2011-4-18
*/
publicclass WjObjectFactory extends ObjectFactory{
privatestaticfinallongserialVersionUID = 1L;
//注入ServletContext,必须要使用xwork的注入才稳定
@Inject
private ServletContext servletContext;
/**
* 参数cls:准备创建的类
* 参数ctx:包含了ONGL等信息
*/
@Override
public Object buildBean(Class cls, Map<String, Object> ctx)
throws Exception {
if(Action.class.isAssignableFrom(cls)){//由于一般情况下,所有的action都是Action接口的子类,
//所有,此外判断是否为Action的子类
//如果是,则可以从Spring容器中获取此Action的实例
System.err.println("准备从Spring中获取的类是:"+cls);
ApplicationContext appCtx =
WebApplicationContextUtils.getWebApplicationContext(servletContext);
Object actionBean = appCtx.getBean(cls);
System.err.println("从Spring中获取到的类是:"+actionBean);
return actionBean;
}
returnsuper.buildBean(cls, ctx);
}
}
第二步:配置此类
在classpath:struts.xml中配置以下内容:
//以下声明加载类的工厂
<beanclass="cn.itcast.utils.WjObjectFactory"name="wj"type="com.opensymphony.xwork2.ObjectFactory"/>
//修改加载类的工厂为wj
<constantname="struts.objectFactory"value="wj"/>
第三步:正常配置自己的Action
<?xmlversion="1.0"encoding="UTF-8"?>
<!DOCTYPEstrutsPUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
<beanclass="cn.itcast.utils.WjObjectFactory"name="wj"type="com.opensymphony.xwork2.ObjectFactory"/>
<constantname="struts.objectFactory"value="wj"/>
<packagename="default"namespace="/"extends="struts-default">
<!-- TestAction在Spring配置中叫testAction -->
<actionname="one"class="cn.itcast.test.TestAction">
<resultname="success">/index.jsp</result>
</action>
<!-- 故意不将TwoAction不配置到Spring的配置文件中 -->
<actionname="two"class="cn.itcast.two.TwoAction">
</action>
</package>
</struts>
第四步:Spring配置文件如下:
<!-- 必须写成为原型模式 -->
<beanid="action"class="cn.itcast.test.TestAction"scope="prototype">
<propertyname="service"ref="service"></property>
</bean>
<beanid="service"class="cn.itcast.test.TestService">
<propertyname="dao">
<beanclass="cn.itcast.test.TestDao">
<propertyname="dataSource"ref="ds"></property>
</bean>
</property>
</bean>
第五步:总结和建议:
上面的代码,存在很大的问题。因为Object actionBean = appCtx.getBean(cls);是根据类型从Spring的配置文件中获取一个Action的实例,而我们只配置的TestAction并没有配置TwoAction,在访问two.action的url时,由于无法获取TwoAction在spring中的配置,则将导致程序出错。
那如何才可以将程序修改的更加稳健呢?
2、使用SpringBeanFactory
其实在xwork2中,它已经为我们定义好了一个很好的实现,但它只是一个半成品。因为没有WebApplicationContext的实例。所以它无法工作。
那么,我们下面将选择继承它,并为他设置WebApplicationContext的实例。让它工作会比我们的更加稳健。
修改成以下代码后,其他的配置都不用修改:
即可以完成以下功能:
1、Action类,可以实现Action/ActionSupport。也可以不实现。
2、从此以后,<action name=“dd” class= “可是是spring中配置的名称”>
package cn.itcast.utils;
import javax.servlet.ServletContext;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.spring.SpringObjectFactory;
/**
* 书写自己的ObjectFactory
* @author <a href="awangsenfeng@163.com">王森丰</a>
* @version 1.0 2011-4-18
*/
publicclass WjObjectFactory extends SpringObjectFactory{
privatestaticfinallongserialVersionUID = 1L;
@Inject
public WjObjectFactory(@Inject ServletContext servletContext) {
//第一种方式获取ctx对象
WebApplicationContext ctx =
WebApplicationContextUtils.getWebApplicationContext(servletContext);
//给父类设置一个上下文
setApplicationContext(ctx);
//根据名称对象获取bean即byName
setAutowireStrategy(1);
}
}
第二种方式获取ApplicationContext:
package cn.itcast.utils;
import javax.servlet.ServletContext;
import org.springframework.context.ApplicationContext;
import org.springframework.web.context.WebApplicationContext;
importcom.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.spring.SpringObjectFactory;
/**
* 书写自己的ObjectFactory
* @author <a href="awangsenfeng@163.com">王森丰</a>
* @version 1.0 2011-4-18
*/
publicclass WjObjectFactory extends SpringObjectFactory{
privatestaticfinallongserialVersionUID = 1L;
@Inject
public WjObjectFactory(@Inject ServletContext servletContext) {
//或是直接从servlet中获取ctx对象
ApplicationContext ctx =
(ApplicationContext)
servletContext.getAttribute
(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
setApplicationContext(ctx);
//根据名称对象获取bean即byName
setAutowireStrategy(1);
}
}
35、jQuery中获取tagName
本人使用jQuery1.7。在此版本中,并没有提供获取tagName的方法,即$(“#someid ”).attr(“tagName ”) ;返回的值为null。
只要能在运行时将jQuery对象,转成dom对象,即可以使用tagName如下:
$(this).get(0).tagName;
为此,不如为jQuery开发一个获取tagName的插件方法:
//为是jQuery编写一个最简单的插件,可以获取tagName
$.fn.tagName=function(){
returnthis.get(0).tagName;
};
然后在代码的任意位置即可以调用:
somejQueryObj.click(function(){
var tr = $(this).parent().parent();
alert(tr.tagName());//此处调用自己的插件
});
36、Struts2的NoParameters接口
在Struts2中,有两个接口如下:
只要是继承以上接口中任意一个接口的Action,都不会去执行params拦截器中的获取参数的方法。以下是NoParameters接口的源代码:
/**
* Marker interface to incidate no auto setting of parameters.
* <p/>
* This marker interface should be implemented by actions that do not want any
* request parameters set on them automatically (by the ParametersInterceptor).
* This may be useful if one is using the action tag and want to supply
* the parameters to the action manually using the param tag.
* It may also be useful if one for security reasons wants to make sure that
* parameters cannot be set by malicious users.
*
* @author Dick Zetterberg (dick@transitor.se)
*/
publicinterfaceNoParameters {
}
以下是params拦截器的doInterceptor方法的源代码:
37、Struts2提交数据将数据封装成List<JavaBean>
如果用户在页面上需要一次批量保存用户时,一般提交的都是字符串数组:
如:以下页面,页面上包含修改、增加(有可能为多条记录)的信息,用户一次保存,就希望将所有的信息在struts2的帮助下全部封装好:
上面的数据中。前两行是刚修改的,后面一行为新增加的。如何一次全部保存成功呢?
以下是提交的数据:
可见将数据封装成List<Bean>类型非常简单。只要提供一个List的相同下标即可封装成相同的Bean。
以下是Action类的源代码:
package cn.itcast.stud;
import java.util.ArrayList;
import java.util.List;
import cn.itcast.domain.Student;
import cn.itcast.stud.service.IStudService;
import com.alibaba.fastjson.JSON;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionSupport;
import com.opensymphony.xwork2.ModelDriven;
/**
* 两种接收参数的方式复合使用
* @author <a href="awangsenfeng@163.com">王森丰</a>
* @version 1.0 2011-4-20
*/
publicclass StudAction extends ActionSupport implementsModelDriven<Student>{
privatestaticfinallongserialVersionUID = 1L;
private IStudService service;
public IStudService getService() {
returnservice;
}
publicvoid setService(IStudService service) {
this.service = service;
}
/**
* 默认为查询所有学生信息
*/
public String execute() throws Exception{
System.err.println("学生是:"+this.stud);
studs = service.query(this.stud);
return"success";
}
public String save() throws Exception{
List<Student> studs = service.save(this.studs);
String json = JSON.toJSONString(studs);
ActionContext.getContext().put("json",json);
return"json";
}
public String del() throws Exception{
service.del(this.studs);
ActionContext.getContext().put("json","{\"success\":true}");
return"json";
}
private List<Student> studs=new ArrayList<Student>();//这儿是数组类型的参数
public List<Student> getStuds() {
returnstuds;
}
publicvoid setStuds(List<Student> studs) {
this.studs = studs;
}
private Student stud = new Student();
public Student getModel() {
returnstud;
}
}
以下是用ajax提交时,用ajax组织的数据:注意里面下标的使用:
//保存
$("#save").click(function(){
var trs = $("#table tr:has(input[name='id'])");
var param = "";
var idx=0;//用于记录下标
trs.each(function(){
var radio = $(this).find("td:first").find("input:first");
if(radio.hasClass("changed") || radio.val()==''){
if(param!=""){
param+="&";
}
param+="studs["+idx+"].id="+radio.val();
param+="&studs["+idx+"].name="+$.trim($(this).find("td:eq(1)").text());
param+="&studs["+idx+"].addr="+$.trim($(this).find("td:eq(2)").text());
param+="&studs["+idx+"].zip="+$.trim($(this).find("td:eq(3)").text());
idx++;
}
});
alert(param);
if(param==""){
alert("没有可以保存的数据");
}else{
var url = path+"stud!save.action";
$.post(url,param,function(data){
alert("保存成功");
});
}
});
38、分页再分页-对页码再分页
对分页的页码分页,是为了防止因为页码太多搞乱整个页面。如:数据库中有1002行数据,分次显示10行。则一共可以分为101页,计算公式为
101页=1002/10+(1002%10==0?0:1)。
则一般情况下,页面显示的分页为:
第1页第2页 第3页…..第100页第101页
可想而知页面将会非常难看。我们应该参考google或是baidu的做法,只在页面上显示10个页码。如baidu:
看到了吧,只显示20个页码,且选中的页码,在有条件的情况下,显示中间。这就叫对分页的页码再分页。
以下是对颁布的页码再分页的推理过程:
1:假设我们一面最多显示10个页码。
以下是推理过程:
当前页码
页码范围
推理
1
1~10
可见,只要用户请求的页码,<=5。则页码范围为1~10。而这个5正好是,每面显示10个页码/2的结果。
即if(currentPage<=pageSize/2){
start = 1;
end = 10; //要判断一下,是否够10个页码呀还要,哈。即,end = pageCount>pageSize?pageSize:pageCount;
}
2
1~10
3
1~10
4
1~10
5
1~10
6
2~11
2=6-4 = 6-(10/2-1) = currentPage-(pageSize/2-1)这是开始页码,11=2+10-1 = start+pageSize-1;
即end = start+pageSize-1;
但要注意重新计算以下end的值,以防end大于101的情况。
if(end>pageCount){
end = pageCount;
start = end-(pageSize-1);//重新计算开始的值即可。即101-9=92~101
}
7
3~12
8
4~13
9
5~14
以下是完整的servlet的源代码:
package cn.test;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
importcn.itcast.domain.Stud;
importcn.itcast.service.PageServiceImpl;
publicclassPage2Servletextends HttpServlet {
//注入Service的实现
privatePageServiceImplservice = newPageServiceImpl();
publicvoid doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doPost(request, response);
}
publicvoid doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//获取用户要看第几页
String pageNo = request.getParameter("page");
Integer _pageNo = 0;
if(pageNo==null || pageNo.trim().equals("")){
_pageNo=1;
}else{
_pageNo = Integer.valueOf(pageNo);
}
try {
//key:pageCount:一共分多少页
//Key:datas:数据是什么,List<Map>-部分数据,去后台查询
Map<String,Object> result= service.query(_pageNo);//只将页码传到dao中即可,由dao组织分页sql.
//放入当前页面
result.put("currentPage", _pageNo);
//放request中去当前的结果全部都放到request中去
request.setAttribute("result", result);
//获取所有的页号,即一共分了多少页码
int pageCount = Integer.parseInt(""+result.get("pageCount"));
//以下对分页的数据再分页
int showSize=10; //每次显示几个页号
int showStart=0; //从第几个页号开始显示
int showEnd = 0; //显示到第几个页号
//如果所有页码,还不足10页则,showStart=1,showEnd=最后那个页码
if(pageCount<=showSize){
showStart=1;
showEnd=pageCount;
}else{
//以下计算开始页,如果用户选择的是的第5个页码前的话
if(_pageNo<=(showSize/2)){
showStart=1;
}else{
showStart=_pageNo-(showSize/2-1); //否则计算开始的页码
}
//以下开始计算结束页
showEnd = showStart+(showSize-1);
//如果大于最大页码号
if(showEnd>pageCount){
showEnd=pageCount;
showStart=showEnd-(showSize-1);//需要重新计算一下开始页码
}
}
request.setAttribute("showStart",showStart);
request.setAttribute("showEnd",showEnd);
//转发
request.getRequestDispatcher("/jsps/show2.jsp").forward(request, response);
} catch (Exception e) {
e.printStackTrace();
}
}
}
39、使用过虑器过虑权限
从严格的意义上来讲,仅用一个过虑器,通过判断用户是否登录来阻止用户访问资源是远远不够的。试想一下,如果一个普通用户登录以后,他便可以非常轻松的通过第一个过虑器。如果此用户知道高级用户访问的资源url,便可以通过直接在地址栏中输入url的方式直接访问高级资源。所以,有了第一道防线以后,应该再加以道认证防线。
UML如下:
安全验证分为认证和验证,以下第一个过虑器应该为认证过虑器,第二个过虑器应该为验证过虑器。
就如同你持有一张火车票在进站时需要认证你是否具有进站乘车的权限。上了车以后还要验证一下,你是否对号入座等。
一般情况下,我们的表结构E-R图如下:
SQL语句如下:
/*以下创建用户表*/
createtable users(
uid varchar(32) primarykey,
uname varchar(30),
upassword varchar(32)
);
/*以下创建角色表*/
createtable roles(
rid varchar(32) primarykey,
rname varchar(30)
);
createtable roleuser(
ru_uid varchar(32),
ru_rid varchar(32),
constraint ru_pk primarykey(ru_uid,ru_rid),
constraint ru_fk1 foreignkey(ru_uid) references users(uid),
constraint ru_fk2 foreignkey(ru_rid) references roles(rid)
);
/*资源*/
createtable sources(
sid varchar(32) primarykey,
sname varchar(30),
surl varchar(100)
);
/*资源角色对应表*/
createtable func(
func_rid varchar(32),
func_sid varchar(32),
constraint func_pk primarykey(func_rid,func_sid),
constraint func_fk1 foreignkey(func_rid) references roles(rid),
constraint func_fk2 foreignkey(func_sid) references sources(sid)
);
/*写入两个用户*/
insertinto users values('U001','Jack','1234');
insertinto users values('U001','Jack','1234');
/*写入两个角色*/
insertinto roles values('R001','管理员');
insertinto roles values('R002','普通人员');
/*给两个用户分配角色*/
insertinto roleuser values('U001','R001');
insertinto roleuser values('U002','R002');
/*设置两个资源*/
insertinto sources values('S001','管理菜单','/jsps/a.jsp');
insertinto sources values('S002','普通菜单','/jsps/b.jsp');
/*给角色分配资源*/
insertinto func values('R001','S001');
insertinto func values('R001','S002');
insertinto func values('R002','S002');
SELECT u.uname,s.sname
FROM users u INNERJOIN roleuser ru ON u.uid=ru.ru_uid
INNERJOIN roles r ON ru.ru_rid=r.rid
INNERJOIN func f ON f.func_rid=r.rid
INNERJOIN sources s ON s.sid=f.func_sid;
以下是两个过虑器,第一个过虑器,过虑用户是否已经登录,第二个过虑器过虑用户是否拥有访问资源的权限。
第一个过虑器源代码:
package cn.itcast.filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 第一个过虑器,这个过虑器非常简单,
认证过虑器
* 仅仅是验证用户是否已经登录
* @author王森丰
* @version 2011-4-27
*/
publicclass LoginFilter implements Filter {
publicvoid destroy() {
}
publicvoid doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
req.setCharacterEncoding("UTF-8");
HttpServletRequest r = (HttpServletRequest) req;
//验证用户是否已经登录
if(r.getSession().getAttribute("user")==null){
System.err.println("你还没有登录");
HttpServletResponse rp = (HttpServletResponse) resp;
rp.sendRedirect(r.getContextPath()+"/index.jsp");
}else{
chain.doFilter(r, resp);
}
}
publicvoid init(FilterConfig arg0) throws ServletException {
}
}
第二个过虑器源代码:
package cn.itcast.filter;
import java.io.IOException;
import java.sql.Connection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.MapListHandler;
import cn.itcast.pubs.Conn;
/**
* 由于是初级测试,所以,此处直接使用QueryRunner进行的操作,
* 不建议每次都命中数据库,最好将所有请求的资源进行缓存。
验证过虑器
* @author王森丰
* @version 2011-4-27
*/
publicclass AuthenticationFilter implements Filter{
//用hash可以保存相同的url放入的是不同的角色id,使用set的好处是,不会放入相同数据
private Map<String,Set<Object>> res= new HashMap<String, Set<Object>>();
@SuppressWarnings("unchecked")
publicvoid doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest r=(HttpServletRequest) req;
String uri = r.getRequestURI(); //获取用户请求的uri:/project/a.jsp
String ctxPath = r.getContextPath(); //获取项目名称
uri = uri.replace(ctxPath,""); //从uri中去除项目名称,之后变成/a.jsp
System.err.println(uri);
if(res.containsKey(uri)){ //判断是否请求的资源为受管理的资源,如果不是,则直接放行
boolean boo = false;
Set<Object> roles1 = res.get(uri); //从缓存中获取此资源所对应的角色
Map<String,Object> user = //从session中将用户取出,以便于从用户对象中获取用户拥有的角色列表
(Map<String, Object>) r.getSession().getAttribute("user");
List<Object> roles2 = (List<Object>) user.get("roles");
for(Object r2:roles2){
for(Object r1:roles1){
if(r2.equals(r1)){ //遍历所有,进行对比
boo=true;
break;
}
}
}
if(boo){
chain.doFilter(req, resp); //如果对比成功,则放行
}else{ //如果对比不成功,则通知此用户无权访问
System.err.println("你无权访问这个资源。。。。");
HttpServletResponse rp = (HttpServletResponse) resp;
rp.sendRedirect(r.getContextPath()+"/index.jsp?error=2");
}
}else{
//如果没有此url的配置直接放行
chain.doFilter(req, resp);
}
}
/**
* 在初始化时,将所有的资源全部封装到map中
* 以url即具体资源为key值,以所对应的角色id为value。
*/
publicvoid init(FilterConfig arg0) throws ServletException {
try{
Connection con = Conn.getConn();
QueryRunner run = new QueryRunner();
//查出所有的url及对应的角色,查询的结果为[{url=/a.jsp,rid=r001},...]
String sql = "SELECT s.surl url,f.func_rid rid"+
" FROM sources s INNER JOIN func f ON s.sid=f.func_sid";
List<Map<String,Object>> list = run.query(con,sql,new MapListHandler());
for(Map<String,Object> mm:list){ //在查询结果中进行遍历
if(res.containsKey(mm.get("url"))){ //判断是否在资源中已经存在此url
Set<Object> set = (Set<Object>)res.get(mm.get("url"));
System.err.println("set is:"+set);
set.add(mm.get("rid"));
}else{
Set<Object> set = new HashSet<Object>();//声明一个新的map并放到缓存中
set.add(mm.get("rid"));
res.put(""+mm.get("url"),set);
}
}
}catch(Exception e){
e.printStackTrace();
}
}
publicvoid destroy() {}
}
以下是用户登录的servlet,其实最关键的是,不但要查询此用户所拥有的菜单,还要查询出此用户所有角色,并保存到用户(user)对象中:
package cn.itcast.servlet;
import java.io.IOException;
import java.sql.Connection;
import java.util.List;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.ColumnListHandler;
import org.apache.commons.dbutils.handlers.MapHandler;
import org.apache.commons.dbutils.handlers.MapListHandler;
import cn.itcast.pubs.Conn;
/**
* 此Servlet实现用户登录功能,同时将用户和用户角色id放到session中
* @author王森丰
* @version 2011-4-27
*/
@SuppressWarnings("serial")
publicclass LoginServlet extends HttpServlet {
publicvoid doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
try{
String name = request.getParameter("name");
String pwd = request.getParameter("pwd");
String sql = "select * from users where uname='"+name+"' and upassword='"+pwd+"'";
QueryRunner run = new QueryRunner();
Connection con=Conn.getConn();
Map<String,Object> user = run.query(con,sql,new MapHandler());
if(user==null){
System.err.println("登录不成功。。。");
response.sendRedirect(request.getContextPath()+"/index.jsp?error=1");
}else{
request.getSession().setAttribute("user",user);
//查询此用户所拥有的所有菜单,用于在页面上显示
sql = "SELECT s.sname name,s.surl url"+
" FROM users u INNER JOIN roleuser ru ON u.uid=ru.ru_uid"+
" INNER JOIN roles r ON ru.ru_rid=r.rid"+
" INNER JOIN func f ON f.func_rid=r.rid"+
" INNER JOIN sources s ON s.sid=f.func_sid"+
" where u.uid='"+user.get("uid")+"'";
List<Map<String,Object>> menus = run.query(con,sql,new MapListHandler());
user.put("menus",menus);//将用户的功能菜单放到user的map中
//以下查询出此用户的所有角色
sql = "SELECT r.rid FROM roles r INNER JOIN roleuser ru ON r.rid=ru.ru_rid " +
"WHERE ru.ru_uid='"+user.get("uid")+"'";
List<Object> roles = run.query(con, sql,new ColumnListHandler("rid"));
//将角色列表,保存到user对象内
user.put("roles",roles);
request.getRequestDispatcher("/index.jsp").forward(request, response);
}
}catch(Exception e){
e.printStackTrace();
}
}
}
40、自定义连接池的实现-通过线程休眠(包装和代理)和线程通讯(又分为包装和代码)两种方式实现
1、第一种方式-包装+线程休眠
即在获取连接时,先判断是否池中还有连接,如果没有则休眠一段时间,等待醒来时递归调用获取连接的方法,直到调用成功为止。
package cn.itcast.utils;
//导入很多包(略)
/**
* 连接数据库的工具类
* @author <a href="awangsenfeng@163.com">王森丰</a>
* @version 1.0 2011-5-5
*/
publicclass ConnUtils {
//声明一个list保存3个连接
privatestatic List<Connection> pools = new ArrayList<Connection>();
static {
System.err.println("创建一个连接");
try {
Properties p = new Properties();//读取配置文件
p.load(ConnUtils.class.getClassLoader().getResourceAsStream("jdbc.properties"));
String driver = p.getProperty("driver");
String url = p.getProperty("url");
String nm = p.getProperty("name");
String pwd = p.getProperty("pwd");
Class.forName(driver);
for(int i=0;i<3;i++){
Connection tmpConn = DriverManager.getConnection(url,nm,pwd);
MyConnection con = new MyConnection(tmpConn);//声明自己的包装类
pools.add(con);
}
System.err.println("初始化时:"+pools.size());
} catch (Exception e) {
System.err.println("创建连接不成功。。。。。。。。");
thrownew RuntimeException(e);
}
}
private ConnUtils(){}
publicsynchronizedstatic Connection getConn() {//对方法进行同步处理
if(pools.size()==0){ //如果池中已经没有连接,则等待100毫秒
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
returngetConn(); //等待完成以后再将调用这个方法直接到调用成功为止
}else{
Connection con = pools.remove(0);
System.err.println(">>获取的连接是"+con+",还有几个:"+pools.size());
return con;
}
}
staticclass MyConnection implements Connection{
private Connection conn;
public MyConnection(Connection conn){
this.conn=conn;
}
publicvoid close() throws SQLException {
System.err.println("放回来在修改的代码中。。。。。。。");
pools.add(this);//将对象加回到连接池中
}
//在此类中有很多方法,其他方法直接调用conn成员变量的方法。略去。
}
2、第二种方式-包装+线程通讯
即对pools池进行同步处理,如果一个线程过来获取连接时,发现已经没有了连接,则调用pools.wait()方法,让当前线程进入连接池的等待池。只要有线程执行完了,并调用了pools.add(Conn)即还回一个线程后,马上调用pools.notify()方法,唤醒一个等待线程:
package cn.itcast.utils;
//导入很多包略
/**
* 连接数据库的工具类
* @author <a href="awangsenfeng@163.com">王森丰</a>
* @version 1.0 2011-5-5
*/
publicclass OracleConn {
privatestatic List<Connection> pools =new ArrayList<Connection>();//声明一个list保存n个连接
static {
try {
Properties p = new Properties();
p.load(OracleConn.class.getClassLoader().getResourceAsStream("jdbc.properties"));//读取配置文件
String driver = p.getProperty("driver");
String url = p.getProperty("url");
String nm = p.getProperty("name");
String pwd = p.getProperty("pwd");
Class.forName(driver);
for(int i=0;i<3;i++){
Connection tmpConn = DriverManager.getConnection(url,nm,pwd);
MyConnection con = new MyConnection(tmpConn);//声明自己的连接对象放到池中
pools.add(con);
}
System.err.println("初始化时:"+pools.size());
} catch (Exception e) {
thrownew RuntimeException(e);
}
}
private OracleConn(){}
publicstatic Connection getConn() {//使用线程等待技术同样实现从池中获取一个连接的方法
synchronized (pools) { //对pools进行同步处理
if(pools.size()==0){ //如果池中已经没有了连接则让当前线程进入等待池
try {
pools.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
returngetConn();
}else{
Connection con = pools.remove(0);
System.err.println(">>获取的连接是"+con+",还有几个:"+pools.size());
return con;
}
}
}
staticclass MyConnection implements Connection{
private Connection conn;
public MyConnection(Connection conn){
this.conn=conn;
}
publicvoid close() throws SQLException {
synchronized (pools) {
pools.add(this);
pools.notify();//通知等待池中的线程
System.err.println("有人放回来了还有:"+pools.size());
}
}
//其他方法略
}
3、第三种方式-代理+线程通讯
使用jdk的动态代理实现对close方法的监控。
package cn.itcast.utils;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
/**
* 连接数据库的工具类
* @author <a href="awangsenfeng@163.com">王森丰</a>
* @version 1.0 2011-5-5
*/
publicclass OracleConn2 {
//声明一个list保存3个连接
privatestatic List<Connection> pools =new ArrayList<Connection>();
static {
System.err.println("创建一个连接");
try {
//读取配置文件
Properties p = new Properties();
p.load(OracleConn2.class.getClassLoader().getResourceAsStream("jdbc.properties"));
String driver = p.getProperty("driver");
String url = p.getProperty("url");
String nm = p.getProperty("name");
String pwd = p.getProperty("pwd");
Class.forName(driver);
for(int i=0;i<3;i++){
final Connection tmpConn = DriverManager.getConnection(url,nm,pwd);
Object proxedConn = Proxy.newProxyInstance(OracleConn2.class.getClassLoader(),
new Class[]{Connection.class},
new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
if(method.getName().equals("close")){
synchronized (pools) {//使用同步
pools.add((Connection) proxy);
pools.notify();//通知等待池中的线程执行
returnnull;
}
}else{
return method.invoke(tmpConn, args);
}
}
});
pools.add((Connection) proxedConn);
}
System.err.println("初始化时:"+pools.size());
} catch (Exception e) {
thrownew RuntimeException(e);
}
}
private OracleConn2(){}
//使用线程等待技术同样实现从池中获取一个连接的方法
publicstatic Connection getConn() {
synchronized (pools) { //对pools进行同步处理
if(pools.size()==0){ //如果池中已经没有了连接则让当前线程进入等待池
try {
pools.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
returngetConn();
}else{
Connection con = pools.remove(0);
System.err.println(">>获取的连接是"+con+",还有几个:"+pools.size());
return con;
}
}
}
}
4、第四种方式-代理+线程休眠
相信有了前面的知识,自己开发一个线程+休眠的连接池并不难,所以此处略了。
41、自己动手开发DataSource
虽然上面的实现已经很好,但只适合于接收Connection的程序。而目前很多应用都接收一个java.sql.DataSource。为此我们应该实现一个DataSource。实现DataSoure的关键即是实现此接口中的方法getConnection。通过查看java.sql.DataSource接口的API我们知道它的实现有以下几种:
在此,我们实现第二种,即连接池的实现:
上代码:
package cn.itcast.utils;
import java.io.PrintWriter;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.LinkedList;
import javax.sql.DataSource;
/**
* 默认情况下,池中保存着三个连接对象
* 并通过对Connection的代理实现在close时还回池
* @author <a href="awangsenfeng@163.com">王森丰</a>
* @version 1.0 2011-5-6
*/
publicclass StdDataSource implements DataSource {
privateintpoolSize=3;//默认为3个
private LinkedList<Connection> pool = new LinkedList<Connection>();
public StdDataSource(String driver,String url,String name,String pwd){
this(driver,url,name,pwd,3);
}
public StdDataSource(String driver,String url,String name,String pwd,int poolSize){
try{
Class.forName(driver);
this.poolSize=poolSize;
if(poolSize<=0){
thrownew RuntimeException("不支持的池大小"+poolSize);
}
for(int i=0;i<poolSize;i++){
Connection con = DriverManager.getConnection(url,name,pwd);
con = ConnProxy.getProxyedConnection(con,pool);//获取被代理的对象
pool.add(con);//添加被代理的对象
}
}catch(Exception e){
thrownew RuntimeException(e.getMessage(),e);
}
}
/**
* 获取池大小
*/
publicint getPoolSize() {
returnpoolSize;
}
/**
* 不支持日志操作
*/
public PrintWriter getLogWriter() throws SQLException {
thrownew RuntimeException("Unsupport Operation.");
}
publicvoid setLogWriter(PrintWriter out) throws SQLException {
thrownew RuntimeException("Unsupport operation.");
}
/**
* 不支持超时操作
*/
publicvoid setLoginTimeout(int seconds) throws SQLException {
thrownew RuntimeException("Unsupport operation.");
}
publicint getLoginTimeout() throws SQLException {
return 0;
}
@SuppressWarnings("unchecked")
public <T> T unwrap(Class<T> iface) throws SQLException {
return (T)this;
}
publicboolean isWrapperFor(Class<?> iface) throws SQLException {
return DataSource.class.equals(iface);
}
/**
* 从池中取一个连接对象<br/>
* 使用了同步和线程调度技术
*/
public Connection getConnection() throws SQLException {
synchronized (pool) {
if(pool.size()==0){
try {
pool.wait();
} catch (InterruptedException e) {
thrownew RuntimeException(e.getMessage(),e);
}
return getConnection();
}else{
Connection con = pool.removeFirst();
return con;
}
}
}
public Connection getConnection(String username, String password)
throws SQLException {
thrownew RuntimeException("不支持接收用户名和密码的操作");
}
/**
* 静态内部类,实现对Connection的动态代理
* @author <a href="awangsenfeng@163.com">王森丰</a>
* @version 1.0 2011-5-6
*/
staticclass ConnProxy implements InvocationHandler{
private Object o;
private LinkedList<Connection> pool;
private ConnProxy(Object o,LinkedList<Connection> pool){
this.o=o;
this.pool=pool;
}
publicstatic Connection getProxyedConnection(Object o,LinkedList<Connection> pool){
Object proxed = Proxy.newProxyInstance(o.getClass().getClassLoader(),
new Class[]{Connection.class},new ConnProxy(o,pool));
return (Connection) proxed;
}
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
if(method.getName().equals("close")){
synchronized (pool) {
pool.add((Connection) proxy);//将被代理的对象放回池中
pool.notify();//通知等待线程去获取一个连接吧
}
returnnull;
}else{
return method.invoke(o, args);
}
}
}
}
为了可以获取DataSource我们一般还需要提供一个工厂类,以前在工厂类中只可以获取Connection,现在应该在工厂类中获取DataSource或是Connection。如果需要处理事务,则应该同时提供一个ThreadLocal对象来维护线程局部的Connection,以下是源代码:
package cn.itcast.utils;
import java.sql.Connection;
import java.sql.SQLException;
import javax.sql.DataSource;
/**
* 通过实例化自己的DataSource获取连接
* @author <a href="awangsenfeng@163.com">王森丰</a>
* @version 1.0 2011-5-6
*/
publicclass MyDsUtils {
privatestatic DataSource dataSource;
privatestatic ThreadLocal<Connection> thread = new ThreadLocal<Connection>();
static{
dataSource =
new StdDataSource("com.mysql.jdbc.Driver",
"jdbc:mysql:///itcast?characterEncoding=UTF-8",
"root","1234",3);
}
/**
* 直接获取一个Connection
*/
publicstatic Connection getConn(){
Connection con = null;
try {
con= dataSource.getConnection();
} catch (SQLException e) {
thrownew RuntimeException(e.getMessage(),e);
}
return con;
}
/**
* 获取线程局部的Connection
*/
publicstatic Connection getThreadConn(){
Connection con = thread.get();//先从线程中取数据
if(con==null){
con = getConn();
thread.set(con);
}
return con;
}
/**
* 可选的调用删除局部线程中的对象
*/
publicstaticvoid remove(){
thread.remove();
}
/**
* 获取一个DataSource
*/
publicstatic DataSource getDataSource(){
returndataSource;
}
}
以上代码,通过dbutls测试保存通过。
qa是
最新推荐文章于 2022-03-09 10:42:30 发布