文章目录
- 1. 类加载器学习
- 2. 注解学习
- 2.1. 创建一个自定义注解
- 2.2. 注解的使用
- 2.3. 注解的解析
- 2.4. 元注解
- 2.5. 注解使用案例一
- 3. 动态代理学习
- 3.1. 动态代理案例
- 3.2. 动态代理总结
- 4. 用动态代理解决乱码问题
1. 类加载器学习
- 类加载器主要是将字节码文件加载成字节码对象。如果要对一个java文件进行修改,可以有两种方式,第一种:在源文件处进行修改。另一种:对加载类对象文件进行修改,通过类加载机制,加载类的反射文件,在运行之前进行动态的修改。
- 类加载器种类
- 类加载器都加载哪些东西
- BootStap类加载器是由c语言写的,主要加载jvm运行时一些最基础的一些架包。例如rt.jar,这个就是java运行环境
- ExtClassLoader主要是加载一些运行时的环境jar包,这些jar包都是扩展的jar包。属于系统的基础包。
- 获取类的加载器的方法是
//先获取类的class对象,通过class对象获取类的加载器
Class clazz=Demo.class;
ClassLoader classLoader=clazz.getClassLoader();
- 类的加载器的主要作用是通过类加载机制方法获取资源的路径:即src目录下的资源。
- 类加载器是通过某个类的.classLoader()方法,将该类的.class文件从硬盘中加载到java虚拟机中,形成字节码文件。
- 反射是通过字节码文件对象,将类的字段,方法,构造器等映射成相应的类,并进行各自的操作。
2. 注解学习
- 注解和注释有很大不同
注释: 注释是给程序员看的,用来解释类的意思。
注解: 注解是给jvm看的,用于机器编译理解的。代替配置文件。提升开发效率
- 常用注解
@Override: 告知编译器,此方法是覆盖父类的方法。
Deprecated: 用于标注过时
SuppressWarnings: 压制警告
2.1. 创建一个自定义注解
- 用@interface修饰
public @interface MyAnno{
//注解的属性必须加括号。
String name();
}
- 注解的属性必须加括弧。
2.2. 注解的使用
- 在方法、类、字段上面写@注解名
- 注解有多个参数,用逗号隔开。如果注解参数有默认值,可以不用写。
- 如果注解属性是value,在使用注解时,(注解传递参数一般是键值对形式),可以不写key,直接写值。
2.3. 注解的解析
- 解析目的:获取注解中的参数,原理是通过反射的方式获取。
- 有代码:注解创建
public @interface MyAnno {
//注解的属性
String name();
int age() default 28;
}
- 注解测试类
public class MyAnnoTest {
@SuppressWarnings("all")
@MyAnno(name = "zhangsan")
public void show(String str){
System.out.println("show running...");
}
}
- 注解解析
public static void main(String[] args) throws NoSuchMethodException, SecurityException {
//解析show方法上面的@MyAnno
//直接的目的是 获得show方法上的@MyAnno中的参数
//获得show方法的字节码对象
Class clazz = MyAnnoTest.class;
Method method = clazz.getMethod("show", String.class);
//获得show方法上的@MyAnno
MyAnno annotation = method.getAnnotation(MyAnno.class);
//获得@MyAnno上的属性值
System.out.println(annotation.name());//zhangsan
System.out.println(annotation.age());//28
//根据业务需求写逻辑代码
}
}
- 注解解析: 通过类反射机制动态获取了测试类的class对象
- 通过class获取getMethod()的方式获取测试类的方法,然后封装到Method对象中,至于为什么要封装里面,因为Method有一个获取注解的方法,封装是为了获取更多的方法。
- 获取注解的方法也是通过反射的形式获取的。
2.4. 元注解
- 用途: 元注解是注解的注解,用于限定定义主键的特性。比如@Override只能用在方法上,不能修饰类,就是因为有元注解给定义了只能用在方法上。
- @Target
- @Retention
- 如果通过反射获取注解,必须设置注解的Retention为运行时范围。
2.5. 注解使用案例一
- 本案例测试注解的@Retention和@Target特性
- 自定义注解类
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest {
//不需要属性
}
- TestDemo.java
public class TestDemo {
//程序员开发中测试用的
@Test
public void test1(){
System.out.println("test1 running...");
}
@MyTest
public void test2(){
System.out.println("test2 running...");
}
@MyTest
public void test3(){
System.out.println("test3 running...");
}
}
- 注解解析类
public static void main(String[] args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, InstantiationException {
//获得TestDemo
Class clazz = TestDemo.class;
//获得所有的方法
Method[] methods = clazz.getMethods();
if(methods!=null){
//获得注解使用了@MyTest的方法
for(Method method:methods){
//判断该方法是否使用了@MyTest注解
boolean annotationPresent = method.isAnnotationPresent(MyTest.class);
if(annotationPresent){
//该方法使用MyTest注解了
method.invoke(clazz.newInstance(), null);
}
}
}
}
- 这里面有两个细节问题:一个是:判断获取的方法是否为空,另一个是判断是否是计划中的注解。
3. 动态代理学习
- 什么是代理?
代理好比一个中介,比如一个租房中介和房东,房东可以租房,中介也可以租房,这就造成了一个事情,中介和房东拥有相同的方法即:租房,但是中介还可以有其他的方法。
- 由图中可见,代理对象和目标对象拥有一个接口,两个类实现接口中的方法,就形成了两个类拥有相同的方法。
- 上图属于静态代理,也就是重新建造一个类当做代理对象,而动态代理不需要建立一个新的类,通过反射方法、类的加载机制在内存当中实现代理。
- 动态代理
- proxy类,可以创建一个新的代理对象,可以参考java文档学习。
Object objProxy=Proxy.newProxyInstance(loader,interfaces,h);//根据参数确定是谁的代理对象
- 根据参数确定是谁的代理对象。
loader:传递与目标对象相同的类加载器
interfaces:要求接口的字节码返回数组,所以可以有多个接口
InvocationHandler接口:要求一个接口的对象,相当于代理类,一般用内部类表示这个参数。该接口是通过匿名内部类的方式写的。
new InvocationHandler() {
@Override
//被执行几次?------- 看代理对象调用方法几次
//代理对象调用接口相应方法 都是调用invoke
/*
* proxy:是代理对象
* method:代表的是目标方法的字节码对象
* args:代表是调用目标方法时参数
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//反射知识点
Object invoke = method.invoke(target, args);//目标对象的相应方法
//retrun返回的值给代理对象
return invoke;
}
}
- 这个内部类很重要,特别是这个重写方法,可以在执行方法前对传来的参数进行修改,也可以对执行方法后对获得的返回值进行修改。
3.1. 动态代理案例
- 接口
public interface TargetInterface {
public void method1();
public String method2();
public int method3(int x);
}
- 目标对象
public class Target implements TargetInterface{
@Override
public void method1() {
System.out.println("method1 running...");
}
@Override
public String method2() {
System.out.println("method2 running...");
return "method2";
}
@Override
public int method3(int x) {
return x;
}
}
- 代理类
public class ProxyTest2 {
public static void main(String[] args) {
final Target target = new Target();
//动态创建代理对象
TargetInterface proxy = (TargetInterface) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
//被执行几次?------- 看代理对象调用方法几次
//代理对象调用接口相应方法 都是调用invoke
/*
* proxy:是代理对象
* method:代表的是目标方法的字节码对象
* args:代表是调用目标方法时参数
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//反射知识点
Object invoke = method.invoke(target, args);//目标对象的相应方法
//retrun返回的值给代理对象
return invoke;
}
}
);
proxy.method1();//调用invoke---Method:目标对象的method1方法 args:null 返回值null
String method2 = proxy.method2();//调用invoke---Method:目标对象的method2方法 args:null 返回值method2
int method3 = proxy.method3(100);调用invoke-----Method:目标对象的method3方法 args:Object[]{100} 返回值100
System.out.println(method2);
System.out.println(method3);
}
}
3.2. 动态代理总结
- 通过动态代理案例可以看出动态代理的好处是:在invoke()方法中,可以对目标方法的结果进行修改,可以在方法之前做一些事情,在方法之后做一下判断,还可以对方法进行增强,对方法参数修改,拦截。
- 动态代理和目标对象必须有一个相同的接口。
- 还可以用于方法权限访问,不同角色的人调用的方法有权限,可以通过动态代理的方式进行拦截不同角色的人。
4. 用动态代理解决乱码问题
public class EncodingFilter implements Filter{
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
final HttpServletRequest req = (HttpServletRequest) request;
//使用动态代理完成全局编码
HttpServletRequest enhanceRequset = (HttpServletRequest) Proxy.newProxyInstance(
req.getClass().getClassLoader(),
req.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//对getParameter方法进行增强
String name = method.getName();//获得目标对象的方法名称
if("getParameter".equals(name)){
String invoke = (String) method.invoke(req, args);//乱码
//转码
invoke = new String(invoke.getBytes("iso8859-1"),"UTF-8");
return invoke;
}
return method.invoke(req, args);
}
}
);
chain.doFilter(enhanceRequset, response);//放行,向servlet层传递。