16注解_动态代理、AOP升级事务

目前还存在问题:

  1. 如果业务层的方法中没有使用事务,那么数据库连接最后并没有关闭。
  2. 添加事务的话,需要在业务层的方法中, 嵌入一段重复编写的代码;让业务层只负责专心处理业务相关的事情,不负责处理事务。

那么以上问题如何解决?

 

  1. 注解
    1. 概述

注释://   /**/  /** */  <!--  --> 给人(软件开发工程师)看

注解: 给程序(编译器、类加载器、JVM)看的

注解的作用:在一些使用场景中,实现轻量级的配置。

   (副作用耦合度增加了)

    1. 常见的注解
      1. @Override

表示被它修饰的方法,是重写父类的方法。给编译器看,在编译时验证该方法是否符合重写的规则,符合编译通过,反之编译不通过。

@Override

public String toString() {

return super.toString();

}

      1. SuppressWarnings抑制编译器出现警告

@SuppressWarnings("unused")

public static void main(String[] args) {

int x = 5;

}

@SuppressWarnings({ "unused", "deprecation" })

public static void main(String[] args) {

int x = 5;

Date date = new Date();

System.out.println(date.toLocaleString());

}

 

      1. @Deprecated

表示被它修饰的方法已经过时,不建议使用。

@Deprecated

public static void sleep(){

}

 

    1. 注解开发的流程

定义注解->反射注解->使用注解

@注解名称【 (属性名1=值1[,属性名2=值2,…,属性名N=值N]) 】

使用时可以有属性赋值,也可以没有属性赋值。

    1. 自定义注解

public @interface Anno1 {

}

格式:public @interface  注解名称{}

底层帮我们自动实现了java.lang.annotation.Annotation,但是我们在定义注解时不能这样做:public class Anno3 implements Annotation{},在使用时不识别。

    1. 源生注解:用于修饰注解的注解。

@Target(ElementType.XXX):表示被修饰的注解能够在什么元素(类和接口,方法、属性、成员变量、构造方法..)上使用

@Retention(RetentionPolicy.SOURCE):表示注解的保留域。

@Documented:表示生成的javadoc中是否保留注解的信息。

    1. 注解的属性
  1. 和接口中的方法定义类似:public 类型  属性名称() [default  …];可以default指定默认值
  2. 如果属性定义时没有指定默认值,那么对应注解使用的时候必须为该属性赋值;
  3. 如果在某些场景,使用注解时必须为其中某一个属性赋值,赋值后该注解才可以被使用,那么不要使用default指定默认值。
  4. 特殊属性value,单独为该属性赋值时,“value=”可以省略;除了为value赋值以外,同时还需要为其他属性赋值,这时“value=”不可以省略。
  5. 属性的类型通常是八种基本数据类型、Class类、枚举、String等类型;以及以上类型的一维数组。
  6. 为数组类型的属性赋值时,如果值只有一个,那么赋值时”{}”可以省略。

 

    1. 源生注解详解
      1. @Target(ElementType.XXX):

表示被修饰的注解能够在什么元素(类和接口,方法、属性、成员变量、构造方法..)上使用

ANNOTATION_TYPE 注解类型声明

CONSTRUCTOR构造方法声明

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

LOCAL_VARIABLE局部变量声明

METHOD方法声明

PACKAGE包声明

PARAMETER参数声明

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

 

源代码:

@Documented

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.ANNOTATION_TYPE)

public @interface Target {

    ElementType[] value();

}

在定义注解时,如果没有使用Target指定,默认都可以使用。如果使用了Target指定使用的位置,那么该注解只能在所指定的位置使用。

比如:@Target(ElementType.METHOD)修饰的注解只能被用在方法上。

 

      1. @Retention

@Retention(RetentionPolicy.SOURCE):表示注解的保留域。

       编译器      类加载器      JVM

.java   -> .class   ->加载至内存->运行

CLASS  编译器将把注释记录在类文件中,但在运行时 JVM 不需要保留注释。

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

SOURCE编译器要丢弃的注解。

SOURCE 给编译器看的,编译器看完后不再保留。生成的.class字节码文件中不再保留该类型的注解。

CLASS 给类加载器看的在类加载时根据相关的注解做一些引导操作编译时保留(.class字节码文件中保留),类加载时使用后丢弃

RUNTIME 给JVM看的.编译器编译后保留,类加载器加载后保留,JVM运行时保留。

 

      1. @Documented

表示生成的javadoc中是否保留注解的信息。

@Documented

public @interface Anno1 {}

 

@Anno1

public class Student {

@Anno1(name="zs",value="val",like={"fb","bb"})

private String name;

@Anno1

public void setName(@Anno1 String name){

this.name =name;

}

}

 

 

 

    1. 反射注解

前提条件:注解要想被反射的到,注解的保留域必须指定为RUNTIME,否则反射不到。

jdk中提供的注解反射接口:AnnotatedElement,提供了以下方法:

<T extends Annotation> T

 getAnnotation(Class<T> annotationClass)

     如果存在该元素的指定类型的注解,则返回这些注解,否则返回 null。

 Annotation[] getAnnotations()  返回此元素上存在的所有注释。

 Annotation[] getDeclaredAnnotations()

          返回直接存在于此元素(类或接口、方法、…)上的所有注解。

 boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) 如果指定类型的注释存在于此元素上,则返回 true,否则返回 false。

java.lang.reflect

接口 AnnotatedElement

所有已知实现类: 

AccessibleObject, Class, Constructor, Field, Method, Package

 

案例演示:

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.TYPE)

public @interface PoliceLevel {

public String level() default "";

}

class Plice{

public void getMoney(){

System.out.println("敬个礼,您违章了,罚款200..");

}

}

@PoliceLevel(level="大队长")

public class Police {

public void getMoney(){

System.out.println("敬个礼,您违章了,罚款200..");

}

public static void main(String[] args) {

//Police p = new Police();

Plice p = new Plice();

p.getMoney();

//你该如何操作

//判断p对象是真的交警还是假的

if(p.getClass().isAnnotationPresent(PoliceLevel.class)){

//真的,有证件(有指定的注解)

//获取注解对象

PoliceLevel pl = p.getClass().getAnnotation(PoliceLevel.class);

//获取注解中的属性

String level = pl.level();

if("协警".equals(level)){

System.out.println("偷么给50,不要发票..");

}else if("交警".equals(level)){

System.out.println("给200,给个发票...");

}else if("大队长".equals(level)){

System.out.println("给2000,给开一个年票..");

}

}else{//假的

System.out.println("打一顿,送到警察局....");

}

 

}

}

 

    1. 自定义事务的注解

@Target(ElementType.METHOD)

@Retention(RetentionPolicy.RUNTIME)

public @interface Tran {

}

 

在哪里反射事务的注解呢?

 

  1. 代理模式
    1. 静态代理

 

!!!静态代理:为某个对象(杀手对象)提供一个代理对象,以控制对这个对象(杀手)的访问。 代理类(ProxyKiller)和委托类(Killer)有共同的父类或接口(Kill),这样在任何使用委托类对象的地方都可以用代理对象替代。代理类负责请求的预处理、过滤、将请求分派给委托类处理、以及委托类执行完请求后的后续处理。 由软件工程师创建或工具生成代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。

 

public interface Kill {

public void kill(String name);

}

public class Killer implements Kill {

public void kill(String name){

System.out.println(name+"被杀死了...");

}

}

public class ProxyKill implements Kill{

private Kill kill;

public ProxyKill(Kill kill) {

this.kill = kill;

}

public void kill(String name) {

System.out.println("收50%定金");

kill.kill(name);

System.out.println("收50%尾款");

}

}

public class BasicFactory {

public static Kill getInstance(){

return new ProxyKill(new Killer());

}

public static void main(String[] args) {

//代理类的对象

Kill kill = getInstance();

kill.kill("上课睡觉者");

}

}

 

    静态代理类优缺点

优点:委托类(业务类)只需要关注业务逻辑本身,保证了业务类的重用性。这是代理的共有优点。

缺点:每一个委托类需要编写一个代理类,委托类和接口每添加或删除一些方法的时候,代理类需要同步调整跟着修改。如果项目比较庞大的话,需要编写大量的代理类。

委托类 1---1 代理类:静态代理

委托类 n---1类:动态代理。

和装饰者设计模式的核心区别是:装饰者设计模式是对被装饰者对象执行结果(map)的处理;而静态代理是在调用委托类对象方法执行前后做一些其他操作(事务管理、日志记录、权限的判断等)。

 

    1. 动态代理

使用动态代理升级杀手案例:

public class BasicFactory {

public static Kill getInstance(){

//声明代理对象

Kill proxy = null;

//proxy = new ProxyKill(new Killer());

//创建的委托类对象

final Killer k = new Killer();

//创建动态代理对象

proxy = (Kill)Proxy.newProxyInstance(

//获取类加载器

k.getClass().getClassLoader(),

//获取委托类实现所有接口(通常是一个接口)

k.getClass().getInterfaces(),

new InvocationHandler(){

/**proxy:代理对象

 * method:执行的方法对应Method类的对象

 * args:方法的所有参数

 */

public Object invoke(Object proxy, Method method,

Object[] args) throws Throwable {

Object result = null;

try{

System.out.println("开启事务");

//执行委托对象的方法

result = method.invoke(k, args);

System.out.println("提交事务");

}catch (Exception e) {

System.out.println("回滚事务");

}finally{

System.out.println("释放数据库连接");

}

return result;

}

});

return proxy;

}

public static void main(String[] args) {

//代理类的对象

Kill kill = getInstance();

kill.kill("上课睡觉者");

}

}

 

 

  1. 使用注解和动态代理升级事务管理

@Target(ElementType.METHOD)

@Retention(RetentionPolicy.RUNTIME)

public @interface Tran {

}

 

由于只有业务层对象才需要创建代理对象,所以我们需要区分t是service层对象还是dao层对象。

Class类中提供一个方法:

 boolean

isAssignableFrom(Class<?> cls) 

Class clzA(A.class)    Class clzB(B.class)

clzA.isAssignableFrom(clzB);

返回true的几种情况,A类是B类超类或超接口或同一个类对象Class对象,如下:

情况一:

clzA = Student.class;

Student stu = new Student();

clzB = stu.getClass();

情况二:

public class Student extends Person{}

clzA = Person.class;

clzB = Student.class

情况三:

public class Student implements Play{}

clzA = Play.class;

clzB = Student.class;

情况四:

public interface Live{}

public interface Play extends Live{}

public Student implements Play{}

clzA = Live.class;

clzB = Student.class;

类推:

public interface Service{}

public interface **Service extends Service{}

public class **ServiceImpl implments **Service{}

clzA = Service.class

clzB = OrderServiceImpl.class

接下来,执行以下开发步骤:

  1. 创建两个标识接口Service和Dao
  2. 让所有业务层的接口继承Service接口
  3. 让所有Dao层的接口都继承Dao接口
  4. 修改BasicFactory类的getInstance()方法

public <T> T getInstance(Class<T> clazz) {

try {

// 1.读取配置文件中配置的信息(UserDao的实现类)

// cn.tedu.dao.UserDaoImpl

String className = prop.getProperty(clazz.getSimpleName());

// 2.根据类的全限定名称获得该类的Class对象

Class clz = Class.forName(className);

// 3.利用反射技术根据该类Class对象创建该类的实例

//T t = (T)clz.newInstance();

//区分t是Service层对象还是Dao层对象

if(Service.class.isAssignableFrom(clz)){

final T t = (T)clz.newInstance();

//service层

//创建代理对象

@SuppressWarnings("unchecked")

T proxy = (T)Proxy.newProxyInstance(clz.getClassLoader(),

clz.getInterfaces(),

new InvocationHandler() {

public Object invoke(Object proxy, Method method, Object[] args)

throws Throwable {

Object result = null;

//判断method上是否使用事务的注解@Tran

if(method.isAnnotationPresent(Tran.class)){

//使用了事务

try{//开启事务

TranManager.startTran();

//执行委托类对象(真实对象)的方法

result = method.invoke(t, args);

//提交事务

TranManager.commitTran();

}catch (Exception e) {

e.printStackTrace();

//回滚事务

TranManager.rollbackTran();

}finally{//释放事务中使用数据库连接

TranManager.releseTran();

}

}else{//未使用事务

try{

result = method.invoke(t, args);

}catch (Exception e) {

e.printStackTrace();

}finally{

TranManager.releseTran();

}

}

return result;

}

});

return proxy;

}else if(Dao.class.isAssignableFrom(clz)){

//dao层

return (T)clz.newInstance();

}else{//既不是service也不是dao层

System.out.println("别捣乱....");

return null;

}

} catch (Exception e) {

e.printStackTrace();

throw new RuntimeException(e);

}

}

 

  1. 修改OrderService接口,在addOrder()上使用事务的注解
  2. 修改OrderServiceImpl的addOrder(…),删除事务管理的相关代码。
  3. 测试,事务的处理生效了,但是流程跳转出现了错误,本来库存不足时,应该跳转到cart.jsp,结果跳转到OrderListServlet->order_list.jsp

分析:自定义异常在动态代理调用的过程中被封装成了InvocationTargetException类的target属性了,所以修改代码:

catch (InvocationTargetException e) {

e.printStackTrace();

//回滚事务

TranManager.rollbackTran();

throw e.getTargetException();

}

注意:修改了两处.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

岁月玲珑

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值