java注解和灵活的动态代理

限于个人能力,升入到源码层面分析注解的作用机制还是个无法完成的任务,只能通过一些简单的示例理解注解。

1.java中的注解。简单说,java中的注解的功能类似标签,一般是要配合java反射机制来使用的。创建一个自定注解很简单,只需要遵循java的语言规范即可,

  1)自定义一个注解

@Target({ElementType.TYPE,ElementType.FIELD,ElementType.METHOD})//指定注解的作用范围:从左到右分别是表示:作用在类上,作用在变量(字段)上,作用在方法上
@Retention(RetentionPolicy.RUNTIME)//注解生效失效时间:此处指明的运行时生效,可以指明其注解在其他阶段生效,一般情况下都是指定运行时生效的
//定义注解 public @interface MyAnnotation{      //注解中可以定义属性,也可以定义方法,在使用注解时可以改变属性值 public abstract String getValue() default "no description"; //注解中属性可以有八种基本数据类型,String,枚举(CiytEnum cityName()),Class类型:(Class Clazz()),注解(annotataion=@MyAnnotation ) //以上数据类型的一维数组names{"xxx","yyy"} int intValue(); long longValue(); }

  2)注解如果没有被使用,那就是毫无价值的,可以像下面这样来使用注解

//使用注解
//因为定义注解的时候指定了注解的作用范围,和作用时间,所以注解可以标注到类头上,方法头上,和变量头上 @MyAnnotation(getValue = "annotation on class",intValue = 2,longValue = 10) public class Demo { //注解中有多个属性是用如下方式为每一个属性指定值 @MyAnnotation(getValue = "annotation on field",intValue = 2,longValue = 10) public String name; @MyAnnotation(getValue = "annotation on method",intValue = 2,longValue = 10) public void hello(){} @MyAnnotation(intValue = 2,longValue = 10) public void methodDefault(){}

  3)读取目标类的注解信息。为了能方便理解下面的程序,需要先解释一下字节码对象:下面的程序中此处的字节码对象指的就是Demo这个类的.class文件。试想如果让你对无数的class文件进行抽象,你应该能想每个类方法都有成员变量(属性),方法,注解等信息,把这些信息封装到一个类中,这个类就是Class类(实际的Class类的抽象偶更加复杂,但是道理是一样的),如此一来你可以通过class类的对象获取其中的属性,操纵这些属性,不通过某个类的实例对象访问和操作类属性,java中的反射机制就这样工作的。

public class TestDemo {
  
    public static void main(String[] args) throws Exception {
        //通过获取类的字节码对象,获取类上的注解      
     Class<Demo> clazz = Demo.class; MyAnnotation annotationOnClass = clazz.getAnnotation(MyAnnotation.class);
     //获取注解的值,就是你在使用注解的时候设置的值 System.out.println(annotationOnClass.getValue()); System.out.println(annotationOnClass.intValue()); System.out.println(annotationOnClass.longValue()); //通过字节码对象获取成员变量上的注解 Field name = clazz.getField("name"); MyAnnotation annotationOnField = name.getAnnotation(MyAnnotation.class); System.out.println(annotationOnField.getValue()); //通过字节码对象获取方法上的注解 Method method = clazz.getMethod("hello"); MyAnnotation annotationOnMethod = clazz.getAnnotation(MyAnnotation.class); System.out.println(annotationOnMethod.getValue()); } }


--------------程序运行结果--------------------------

     annotation on class
    2
   10
  annotation on field
  annotation on class


2 .使用动态代理和注解,灵活实现类的功能增强。下面模拟给一个普通类的方法增加事务功能来说明问题

  1)定义接口

public interface UserService {

    void login(String userName,String password);

    void register();
}

  2)定义注解:

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Transactional {
}

  3)在实现类中去使用注解,很简单,只需要你想代理的方法上打上注解,

/**
 * 被代理的对象
 */
public class UserServiceImpl implements  UserService,UserLife{


    /**
     *模拟开启事务
     */
    @Override
    @Transactional
    public void login(String userName, String password) {

        System.out.println(userName + " login");
     //int i = 9 /0;你可以在这个方法中制造异常,观察程序运行结果 } @Override public void register() { System.out.println("register()....."); // login("tom","12345");是为了模拟在一个没有注解标记的方法中去调用带注解的方法,方法调用时不会走代理的 } }

  4)代理逻辑

/**
 * 代理的业务
 * 只为带有 Transactional 注解的方法开事务
 */
public class TransactionInvocationHandler implements InvocationHandler {
  //被代理类对象
    private UserService userService;

    public TransactionInvocationHandler(UserService userService) {
        this.userService = userService;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //通过真实的代理对象 找到需要代理的方法 就可以不必在接口中增加注解,此处的UserService对象取决于你在类的构造方法中传入的是哪个对象
        Method targetMethod = userService.getClass().getMethod(method.getName(), method.getParameterTypes());
     //这种方式是获取接口中的方法的注解信息,不会识别具体显现类的方法上的注解信息 // Annotation[] annotations1 = method.getAnnotations();
     //获取被代理类的当前调用方法上所有注解 Annotation[] annotations = targetMethod.getAnnotations();
    //判断方法是否需要被代理 boolean isProxy = false;
    //遍历注解, for(Annotation annotation : annotations){
        //判断注解类型是否和自定的注解时同一类型,如果是,将isProxy置为ture if(annotation.annotationType() == Transactional.class){ isProxy = true; } } //如果不需要代理,则直接调用目标方法 if(!isProxy){ Object result = method.invoke(userService, args); return result; } //以下是需要代理的业务 System.out.println(">>>>>>>开启事务...........");
     //method.invoke()方法调用时又返回值的 Object result = null; try{ result = method.invoke(userService, args); System.out.println(">>>>>>>>>>事务提交"); } catch (Exception e){ e.printStackTrace(); System.out.println(">>>>>>> 事务回滚 rollback >>>>>>>>>>>>>"); }finally { System.out.println(">>>>>>>>关闭连接 connection close"); } return result; } }

   5)测试代码: 

public class Test2 {

    public static void main(String[] args) {
        UserService service = (UserService)Proxy.newProxyInstance(Test2.class.getClassLoader(),//获取类加载器
                new Class[]{UserService.class},//传入的接口
                new TransactionInvocationHandler(new UserServiceImpl()));//承载了代理逻辑的类

      //调用方法 service.login("sx","123455"); System.out.println("---------在无事务的方法register()中调用有事务的方法login()---------");
//在一个没有事务的方法调用一个没有注解的方法中调用一个有注解的方法,注解是会被忽略的,这也是spring中事务失效的原因之一 service.register(); System.out.println("代理类的名字>>>>>" + service.getClass().getName());//com.sun.proxy.$Proxy0 } }


--------------程序运行结果--------------------

 >>>>>>>开启事务...........
sx login
>>>>>>>>>>事务提交
>>>>>>>>关闭连接 connection close
---------在无事务的方法register()中调用有事务的方法login()---------
register().....
代理类的名字>>>>>com.sun.proxy.$Proxy0

  


总结:通过java中的动态代理,配合注解,就能灵活的控制在程序执行到某些特殊点的地方动态的加上一些代码,这就是织入,一个简单单的类,就是通过这样的方式增加了功能,变得更加强大,行到此处,勉强算是摸到spring AOP的脚后跟了。  

 

posted @ 2020-05-16 21:49  指路为码  阅读(1799)  评论(0编辑  收藏  举报