java基础知识加强(二)


注解:

注解是定义在java.lang包中的

其中要三种注解分别是:

@Override指的是是否覆盖了父类的方法,他只能放在方法上,并且是源文件时期的注解

@Deprecated标记一些不赞成的方法例如已过时的方法,他是运行在源文件时期的注解

@SuppressWarnings去掉一些不想显示的警告,他可以放在接口、类、枚举、字段、方法、构造方法、参数以及局部变量,他是运行时期的注解

通过上面的可以看出注解有表示注解放在什么地方的类和运行在什么时期的类他们分别是

标记注释放在什么地方用Target它的参数分别为有以下接种类型:

TYPE:类、接口(包括注释类型)或枚举声明

FIElD:字段声明(包括枚举常量)

METHOD:方法声明

PARAMETER:参数声明

CONSTRUCTOR:构造方法声明

LOCAL_VARIBLE:局部变量声明

ANNOTATION_TYPE:注释类型声明

标记注释运行在什么时期的是Retention,他分别有以下几种方式:

SOURCE:java源文件时期,他是编译器要丢弃的注释。

CLASS:class文件时期,编译器将把注释记录在类文件中,但在运行时 JVM 不需要保留注释。这是默认的行为。

RUNTIME:内存中的字节码时期,编译器将把注释记录在类文件中,在运行时 JVM 将保留注释,因此可以反射性地读取。

对于上面的两个注解是元注解,还有其他元注解如下:

Documented:指示某一类型的注释将通过 javadoc 和类似的默认工具进行文档化。应使用此类型来注释这些类型的声明:其注释会影响由其客户端注释的元素的使用。如果类型声明是用 Documented 来注释的,则其注释将成为注释元素的公共 API 的一部分。
Inherited :允许子类继承父类的注解

自定义注解代码:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@MyAnnotation(value="RUNTIME",my=@MyChild(id=20))//当只需要定义一个参数指且此参数值为value属性时可以将value省略
public class AnnotationTest {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        if(AnnotationTest.class.isAnnotationPresent(MyAnnotation.class))//判断是否有MyAnnotation注解
        {
            MyAnnotation a=AnnotationTest.class.getAnnotation(MyAnnotation.class);//获取注解
            System.out.println(a.value()+":"+a.id()+":"+a.name()+a.my().id());//打印注解的属性
        }
    }
}
@Retention(RetentionPolicy.RUNTIME)//将注解表为RUNTIME让其注解保存在内存中
@Target(ElementType.TYPE)//标志注解只能放在类、接口和枚举上
@interface MyAnnotation//创建标记类
{
    public int id() default 18;//定义一个标记属性,并将其初始化
    public String name() default "zjd";
    public String value();
    public int[] a() default {1,3,4};
    public MyChild my() default @MyChild(id=30);
    
}
@interface MyChild
{
    public int id() default 12;
}
通过上面我们可以看出注释的属性可以是基本数据类型,String类型,数组类型,还有注释类型,实际上还有其他两种类型就是枚举类型和Class

泛型:

泛型是提供给java编译器使用的,可以限制集合的输入类型,让编译器挡住源程序的非法输入,编译后的类型将不带泛型,是程序运行效率不受到影响

例如:

ArrayList<String> al1=new ArrayList<String>()

ArrayList<integer> al2=new ArrayList<Integer>()

通过反射可以得到他们的字节码是一致的及:

al1.getClass()==al2.getClass()这个语句的返回值是true

在创建数组实例时,数组元素不能使用参数化的类型,例如下面语句是错误的

ArrayList<Integer>[] al=new ArrayList<Integer>[];

打印map集合元素程序:

class MapTest
{
	public void run()
	{
		Map<Integer,String> map=new HashMap<Integer,String>();
		map.put(1, "zjd1");
		map.put(2, "zjd2");
		map.put(3, "zjd3");
		map.put(4, "zjd4");
		map.put(5, "zjd5");
		Set<Map.Entry<Integer,String>> set=map.entrySet();
		
		for(Iterator<Map.Entry<Integer, String>> it=set.iterator();it.hasNext();)
		{
			Map.Entry<Integer, String> me=it.next();
			System.out.println(me.getKey()+":"+me.getValue());
		}
	}
}

泛型的通配符"?":

该方法用于打印任何参数化类型的集合中的所有数据,但是他主要作引用,可以调用与参数化无关的方法,不能调用与参数化有关的方法

泛型通配符"?"的扩展限定通配符:

? extends E:取到E类型以及他的子类型,对类中的成员变量和方法都有效,成为上限

? super E:取到E类型以及他的父类,但只对类中的成员变量有效,而对方法无效,成为下限

限定通配符总是包含自己的

注意:

1、在对泛型参数化的时候,类型参数的实例必须是引用类型,不能是基本类型

2、当一个变量被声明为泛型时,只能被实例变量和方法调用(还有内嵌类型)而不能被静态变量和静态方法调用。因为静态成员是被所有参数化所共享的所以今天成员不应该有类级别的类型参数

类加载器:

类加载器也是java类,但是对于java中类加载器有三种分别是BootStrap、ExtClassLoader、AppClassLoader在这三个加载器中BootStrap有点特殊,虽然他是后两种的父类,但是他不是java类,因为它是由C++编写的二进制代码,直接存在java虚拟机中,它的直接子类是ExtClassLoader,ExtClassLoader的直接子类是AppClassLoader

java虚拟机中的所有类加载器采用具有父子关系的树形结构进行组织,在实例每个类加载器对象时,需要为其指定一个父级类加载器对象或者默认采用系统类加载器为其父级类加载

类加载器之间的父子关系和管辖范围图

BootStrap----------->jre/lib/rt.jar

       |

ExtClassLoader--->jre/lib/ext/*.jar

              |

AppClassLoader----->ClassPath指定的所有jar或目录

              |

自己的类加载器(通过类加载器可以设置保密)

通过下面的程序可以看出他们之间的继承关系

public class ClassLoaderTest {

	public static void main(String[] args) {
		Test10 t=new Test10();
		ClassLoader str=t.getClass().getClassLoader();//获得当前对象所对应的的加载器
		while(str!=null)
		{
			System.out.println(str.getClass().getName());//打印类加载器的名字
			str=str.getParent();//获得类加载器的父类加载器
		}
		System.out.println(str);
	}

}
class Test10{}
打印结果为:
sun.misc.Launcher$AppClassLoader
sun.misc.Launcher$ExtClassLoader
null

从结果也能看出BootStrap加载器不是java类

编写自己的类加载器:

知识要点:

1、自定义的类加载器必须继承ClassLoader类

2、不要覆盖LoadClass()(为了保留委托机制)应该覆盖findClass方法实现自己类的加载

3、最后用defineClass方法将字节数组变成字节码

编写一个自己的类加载器的步骤:

1、先下个程序,然后编译成.class文件然后通过文件类将其进行加密处理得到新的文件。

2、在主程序中不进行更改代码,看看运行结果,报出类格式错误,

3、编写一个自己的加载类,并将加载类中加入解密的程序对其加密.class文件进行解密,因为自定义加载类一定要继承ClassLoader类,所以在这将ClassLoader中的findClass方法进行覆盖,不要覆盖loadClass方法,为了保留他们的的委托机制

4、然后修改主程序中的代码,将其用自己的加载器进行加载

实现代码如下:

主程序:

编写一个ClassLoaderText.java:

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Date;

public class ClassLoaderTest {

	public static void main(String[] args)throws Exception {
                //对于注释的部分是一开始将要加密的.class文件进行加密
 //		FileInputStream fis=new FileInputStream("F:\\java\\jichujiaqiang\\bin\\bao\\ClassLoaderAttachment.class");
//		FileOutputStream fos=new FileOutputStream("F:\\java\\jichujiaqiang\\classLoaderLib\\ClassLoaderAttachment.class");
//		cypher(fis,fos);//加密函数
//		ClassLoaderAttachment d=new ClassLoaderAttachment();//测试在没有加密的时候是能正确显示结果
//		System.out.println(d);
		MyClassLoader my=new MyClassLoader();//创建自己的加载器
		Class<?> clazz=my.loadClass("F:\\java\\jichujiaqiang\\classLoaderLib\\ClassLoaderAttachment.class");//将要加载的.class文件加载进去
		Date d=(Date)clazz.newInstance();//通过反射获得加密的文件的类
//		ClassLoaderAttachment d=new ClassLoaderAttachment();//判断加密后是否能正常显示
		System.out.println(d);//打印结果
	}
	private static  void cypher(InputStream fis,OutputStream fos)throws Exception//对没有加密的文件进行加密
	{
		int by;
		while((by=fis.read())!=-1)
		{
			fos.write(by^0xff);
		}
	}
}
自己编写的加载器:

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.OutputStream;

public class MyClassLoader extends ClassLoader{//只要是自定义的加载器就一定要继承ClassLoader类
	private void cypher(InputStream fis,OutputStream fos)throws Exception//对加密文件进行解密
	{
		int by;
		while((by=fis.read())!=-1)
		{
			fos.write(by^0xff);
		}
	}
              //在loadClass方法没有加载都类的时候就会运行这个方法
  @Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException//覆盖ClassLoader类中的findClass方法
            {
                   try{
                          FileInputStream fis=new FileInputStream("F:\\java\\jichujiaqiang\\classLoaderLib\\ClassLoaderAttachment.class");//要加载的类文件
                          ByteArrayOutputStream bos=new ByteArrayOutputStream();cypher(fis, bos);//对加密的文件进行解密处理
                          byte[] buf=bos.toByteArray();return defineClass(buf, 0, buf.length);//获得解密后文件的字节码
                      }
                      catch(Exception e)
                      {
                          System.out.println("运行时发生异常");
                       }
                      return super.findClass(name);//当自定义加载类也找不到时调用父类的findClass方法

           }
}

用过的文件的加密了解到加密的的是.class文件而不是.java文件,还有要用到反射的方法,还有对加密的类一定要继承一个没有被加密的类,因为在编译器编译的以后被加密的类还是乱码,不能被编译成功所以通过父类先实现编译后在运行的时候才将代码解码过来所以要加一个没有被加密的类

代理类:

为已存在的多个具有相同的接口的目标类的各个方法增加一些系统功能,例如,异常、日记、计算方法的运行时间、事务管理功能等等。

代理类分为静态代理和动态代理:

静态代理:所谓静态就是有程序员编写或有某种工具自定生成源代码,在对其编译,在程序运行时就已经有代理类的.class文件了

动态代理:就是运用反射机制在程序运行时动态创建而成

动态代理技术:

jvm可以在运行时期动态的生成出类的字节码,这种动态生成的类往往被用作代理类,即动态代理类

注意:jvm生成动态代理类必须实现一个和多个接口,所以jvm生成的动态类只能用作具有相同接口的目标类代理,但是可以用CGLIB库来动态生成成一个子类,一个类的子类也可以用作代理,所以如果要为一个没有实现接口的的类生成动态代理类,那么可以使用CGLIB库

代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标外,还可以在代理方法中的如下四个位置加上系统功能代码

1、在调用目标方法前

2、在调用目标方法后

3、在调用目标方法前后

4、在处理目标方法的异常的catch块中

分析jvm动态生成类:

1、创建实现Collection接口的动态类和查看其名称,分析Proxy.getProxyClass方法的各个参数

                Class clazz=Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);//根据Proxy的静态方法getProxyClass获得代理类的字节码
		System.out.println(clazz.getName());//打印代理类的名字
		System.out.println("构造函数:");
		Constructor[] constructors=clazz.getConstructors();//获取代理类的所有构造方法
		for(Constructor constructor:constructors)//对构造方法进行遍历被输出
		{
			System.out.print(constructor.getName());
			Class[] clazzParameter=constructor.getParameterTypes();//获得构造方法中的参数类型
			System.out.print('(');
			for(int i=0;i<clazzParameter.length;i++)
			{
				if(i==0)
				System.out.print(clazzParameter[i]);
				else
					System.out.print(","+clazzParameter[i]);
			}
			System.out.println(')');
		}
		System.out.println("成员函数:");
		Method[] methods=clazz.getMethods();//获得代理类的方法
		for(Method method:methods)
		{
			System.out.print(method.getName());
			Class[] clazzParameter=method.getParameterTypes();
			System.out.print('(');
			for(int i=0;i<clazzParameter.length;i++)//打印成员函数中的参数类型
			{
				if(i==0)
				System.out.print(clazzParameter[i]);
				else
					System.out.print(","+clazzParameter[i]);
			}
			System.out.println(')');
		}
通过练习上面的程序知道在getProxyClass中的第一个参数类型是被代理类的加载器,第二个参数是被代理类的接口的字节码

还有就是通过打印结果表明代理类只有一个构造函数且,构造函数中有一个参数,参数类型是InvocationHandler类型

创建动态类的实例对象:

步骤:

1、用反射获得构造方法

2、编写一个最简单的InvocationHandler类

3、调用构造方法创建动态类的实例对象,并将编写的InvocationHandler类的实例对象传进去

4、打印创建的对象和调用对象没有返回值的方法和getClass方法,演示其他有返回值的方法

5、将创建的动态类的实例对象的代理改成匿名类的形式再做一遍。

代码如下:

	public static void main(String[] args) throws Exception {
		// TODO Auto-generated method stub
		Class clazzProxy=Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
		Constructor constructor=clazzProxy.getConstructor(InvocationHandler.class);
		class MyInvocationHandler implements InvocationHandler//实现InvocationHandler接口类
		{
				@Override
				public Object invoke(Object proxy, Method method, Object[] args)
						throws Throwable {
					// TODO Auto-generated method stub
					return null;
				}
		}
		Collection collection=(Collection)constructor.newInstance(new MyInvocationHandler());
		System.out.println(collection.getClass().getName());//打印这个正常显示
		System.out.println(collection.size());//打印带返回值的发生异常
		
	}
将其改用匿名内部类书写方式为:

	public static void main(String[] args) throws Exception {
		// TODO Auto-generated method stub
		Class clazzProxy=Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
		Constructor constructor=clazzProxy.getConstructor(InvocationHandler.class);
		Collection collection=(Collection)constructor.newInstance(new InvocationHandler(){//实现匿名内部类的方法
			@Override
			public Object invoke(Object proxy, Method method, Object[] args)
					throws Throwable {
				// TODO Auto-generated method stub
				return null;
			}
		});
		System.out.println(collection.getClass().getName());
		System.out.println(collection.size());
		
	}
对于匿名内部类打印的结果和上面的是一样的

通过上面的程序发现实现动态代理需要用到的三个信息就是对象、对象方法、方法的参数

实现AOP功能的封装与配置:

首先建立一个FactoryBean.java类:

public class FactoryBean {
	Properties props=new Properties();//创建一个装载配置信息的Properties类
	public FactoryBean(InputStream ips)//通过勾构造函数传入配置文件信息
	{
		try {
			props.load(ips);//加载到Properties对象中
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		finally
		{
			try {
				ips.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	public Object getBean(String name)throws Exception
	{
		String className=props.getProperty(name);//获取配置文件的信息
		Object bean=Class.forName(className).newInstance();//通过加载的信息生成相应的对象
		if(bean instanceof ProxyFactoryBean)//根据对象类型判断是否为代理类型
		{
			ProxyFactoryBean pfb=(ProxyFactoryBean) bean;//将对象转化成代理类型
			Advice advice=(Advice) Class.forName(props.getProperty(name+".advice")).newInstance();//创建Advice对象
			Object target=Class.forName(props.getProperty(name+".target")).newInstance();//创建需要代理的对象
			pfb.setAdvice(advice);//设置Advice
			pfb.setTarget(target);//设置代理对象
			Object proxy=pfb.getProxy();//得到代理对象	
			return proxy;//返回代理对象
		}
		return bean;//返回没有代理的对象
	}
}
创建一个ProxyFactoryBean.java类:

public class ProxyFactoryBean {
	private Advice advice;//定义一个Advice对象方便传值
	private Object target;
	public Advice getAdvice() {
		return advice;
	}
	public void setAdvice(Advice advice) {
		this.advice = advice;
	}
	public Object getTarget() {
		return target;
	}
	public void setTarget(Object target) {
		this.target = target;
	}
	public Object getProxy() {//获得代理类对象的方法
		Object proxy=Proxy.newProxyInstance(
				target.getClass().getClassLoader(), //需要代理的加载器
				target.getClass().getInterfaces(),//需要代理的接口
				new InvocationHandler(){//实现代理功能

					@Override
					public Object invoke(Object proxy, Method method,
							Object[] args) throws Throwable {
						advice.beforAdvice(method);//在目标运行之前加的处理程序
						Object retVal=method.invoke(target, args);
						advice.afterAdvice(method);//之后的处理程序
						return retVal;//返回代理对象
					}
					
				});
		return proxy;//返回代理类
	}
}
创建一个配置文档info.properties文件:
#xxx=java.util.ArrayList
xxx=ProxyTest.aopframework.ProxyFactoryBean
xxx.advice=ProxyTest.MyAdvice
xxx.target=java.util.ArrayList
创建运行程序ProxyFactoryTest.java:

public class ProxyFactoryTest {

	public static void main(String[] args) throws Exception {
		// TODO Auto-generated method stub
		InputStream ips=ProxyFactoryTest.class.getResourceAsStream("info.properties");//获取配置文件信息
		Object bean=new FactoryBean(ips).getBean("xxx");//获取名为xxx对应的对象
		System.out.println(bean.getClass().getName());//打印这个对象的名字
		Collection collection=(Collection)bean;
		collection.add("asdasdas");
	}

}
上面的代理类就是Spring的核心技术,他就是通过这种方法来实现代理的。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值