目前还存在问题:
- 如果业务层的方法中没有使用事务,那么数据库连接最后并没有关闭。
- 添加事务的话,需要在业务层的方法中, 嵌入一段重复编写的代码;让业务层只负责专心处理业务相关的事情,不负责处理事务。
那么以上问题如何解决?
- 注解
- 概述
注释:// /**/ /** */ <!-- --> 给人(软件开发工程师)看
注解: 给程序(编译器、类加载器、JVM)看的
注解的作用:在一些使用场景中,实现轻量级的配置。
(副作用耦合度增加了)
-
- 常见的注解
- @Override
- 常见的注解
表示被它修饰的方法,是重写父类的方法。给编译器看,在编译时验证该方法是否符合重写的规则,符合编译通过,反之编译不通过。
@Override public String toString() { return super.toString(); } |
-
-
- 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()); } |
-
-
- @Deprecated
-
表示被它修饰的方法已经过时,不建议使用。
@Deprecated public static void } |
-
- 注解开发的流程
定义注解->反射注解->使用注解
@注解名称【 (属性名1=值1[,属性名2=值2,…,属性名N=值N]) 】
使用时可以有属性赋值,也可以没有属性赋值。
-
- 自定义注解
public @interface Anno1 { } |
格式:public @interface 注解名称{}
底层帮我们自动实现了java.lang.annotation.Annotation,但是我们在定义注解时不能这样做:public class Anno3 implements Annotation{},在使用时不识别。
-
- 源生注解:用于修饰注解的注解。
@Target(ElementType.XXX):表示被修饰的注解能够在什么元素(类和接口,方法、属性、成员变量、构造方法..)上使用
@Retention(RetentionPolicy.SOURCE):表示注解的保留域。
@Documented:表示生成的javadoc中是否保留注解的信息。
-
- 注解的属性
- 和接口中的方法定义类似:public 类型 属性名称() [default …];可以default指定默认值
- 如果属性定义时没有指定默认值,那么对应注解使用的时候必须为该属性赋值;
- 如果在某些场景,使用注解时必须为其中某一个属性赋值,赋值后该注解才可以被使用,那么不要使用default指定默认值。
- 特殊属性value,单独为该属性赋值时,“value=”可以省略;除了为value赋值以外,同时还需要为其他属性赋值,这时“value=”不可以省略。
- 属性的类型通常是八种基本数据类型、Class类、枚举、String等类型;以及以上类型的一维数组。
- 为数组类型的属性赋值时,如果值只有一个,那么赋值时”{}”可以省略。
-
- 源生注解详解
- @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)修饰的注解只能被用在方法上。
-
-
- @Retention
-
@Retention(RetentionPolicy.SOURCE):表示注解的保留域。
编译器 类加载器 JVM
.java -> .class ->加载至内存->运行
CLASS 编译器将把注释记录在类文件中,但在运行时 JVM 不需要保留注释。 |
RUNTIME 编译器将把注释记录在类文件中,在运行时 JVM 将保留注释,因此可以反射性地读取。 |
SOURCE编译器要丢弃的注解。 |
SOURCE 给编译器看的,编译器看完后不再保留。生成的.class字节码文件中不再保留该类型的注解。
CLASS 给类加载器看的,在类加载时根据相关的注解做一些引导操作。编译时保留(.class字节码文件中保留),类加载时使用后丢弃。
RUNTIME 给JVM看的.编译器编译后保留,类加载器加载后保留,JVM运行时保留。
-
-
- @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; } } |
-
- 反射注解
前提条件:注解要想被反射的到,注解的保留域必须指定为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("打一顿,送到警察局...."); }
} } |
-
- 自定义事务的注解
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Tran { } |
在哪里反射事务的注解呢?
- 代理模式
- 静态代理
!!!静态代理:为某个对象(杀手对象)提供一个代理对象,以控制对这个对象(杀手)的访问。 代理类(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)的处理;而静态代理是在调用委托类对象方法执行前后做一些其他操作(事务管理、日志记录、权限的判断等)。
-
- 动态代理
使用动态代理升级杀手案例:
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("上课睡觉者"); } } |
- 使用注解和动态代理升级事务管理
@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
接下来,执行以下开发步骤:
- 创建两个标识接口Service和Dao
- 让所有业务层的接口继承Service接口
- 让所有Dao层的接口都继承Dao接口
- 修改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); } } |
- 修改OrderService接口,在addOrder()上使用事务的注解
- 修改OrderServiceImpl的addOrder(…),删除事务管理的相关代码。
- 测试,事务的处理生效了,但是流程跳转出现了错误,本来库存不足时,应该跳转到cart.jsp,结果跳转到OrderListServlet->order_list.jsp
分析:自定义异常在动态代理调用的过程中被封装成了InvocationTargetException类的target属性了,所以修改代码:
catch (InvocationTargetException e) { e.printStackTrace(); //回滚事务 TranManager.rollbackTran(); throw e.getTargetException(); } |
注意:修改了两处.