现在有这么一个功能,对于用户管理有增删改查4个方法,后来由于对安全性的要求,对于这4个方法,需要添加这么一个验证安全性功能(见如下代码).如何做到不修改原来的代码,而将功能完成?
private voidcheckSecurity()
{
System.out.println("---------UserManagerImpl.checkSecurity()---------------");
}
将需要的代码列出
1.接口UserManager,定义增删改查4个方法.
public interface UserManager {
public void addUser(String username,String password);
public void delUser(int userId);
public String findUserById(int userId);
public void modifyUser(int userId,String username,String passwrod);
}
2.交由UserManagerImpl来实现.
public class UserManagerImpl implements UserManager {
public void addUser(String username, String password) {
System.out.println("---------UserManagerImpl.addUser()---------------");
}
public void delUser(int userId) {
System.out.println("---------UserManagerImpl.delUser()---------------");
}
public String findUserById(int userId) {
System.out.println("---------UserManagerImpl.findUserById()---------------");
return "张三";
}
public void modifyUser(int userId, String username, String passwrod) {
System.out.println("---------UserManagerImpl.modifyUser()---------------");
}
}
到现在前期的准备代码完成.那么如何实现加入安全性验证的功能呢?
最简单的方式
public voidaddUser(String username, String password) {
checkSecurity();
System.out.println("---------UserManagerImpl.addUser()---------------");
//checkSecurity();
}
只取其中一段代码,就是上面一样.直接修改Impl类的addUser,加入安全性验证代码,这种方式无疑是最简单的,但是却有很大的问题,首先违背了开放封闭原则,并且若方法少还行,但是方法一多,就存在大量的修改,而修改往往会存在修改错误,或者修改不全的问题,所以不可取.
那么可取的方法是,至少不可以违背开放封闭原则.如何才能不违背开放封闭原则,加个代理是个不错的选择.
静态代理
public class UserManagerImplProxy implements UserManager { private UserManager userManager;
public UserManagerImplProxy(UserManager userManager)
{
this.userManager = userManager;
}
public void addUser(String username, String password) {
checkSecurity();
userManager.addUser(username, password);
}
public void delUser(int userId) {
checkSecurity();
userManager.delUser(userId);
}
//***改查类似
private void checkSecurity()
{
System.out.println("---------UserManagerImpl.checkSecurity()---------------");
}
}
不修改原来的方法,而是加一个代理,在原来的方法上面包一层,加上安全验证的方法.这种是静态代理的方式,没有违背开放封闭,但是还是有着大量重复工作的问题,所以还是需要改进的.之所以称为静态代理,是因为你还是可以看到代理类的.
动态代理
public class SecurityHandler implements InvocationHandler {
private Object targetObject;
public Object createProxyInstance(Object targetObject)
{
this.targetObject=targetObject;
return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),
targetObject.getClass().getInterfaces(),
this);
}
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
checkSecurity();
//调用目标方法
Object ret = method.invoke(targetObject,args);
return ret;
}
private void checkSecurity()
{
System.out.println("---------UserManagerImpl.checkSecurity()---------------");
}
}
现在改成这样,改成动态代理,事先并不知道调用的会是哪个方法,但是不管你是哪个方法,都能给你转过去.
写一个SecurityHandler类,实现InvocationHandler接口.实现接口的invoke方法,参数是一个代理,一个方法,还有方法的参数数组.可以理出,代理,方法和参数数组对应的都是你需要调用的方法的类的代理,方法和参数,也就是目标对象代理和目标方法.在invoke方法中,统一添加安全性验证方法,并且将调用方法转向调用目标方法.
然后就是目标对象和目标对象的代理如何来?需要根据对应的目标对象来生成.声明一个属性targetObject目标对象,传入方式使用createProxyInstance(ObjecttargetObject)传入,并在该方法中将目标对象转为目标对象的代理对象返回.
区别
采用动态代理和静态代理完成了这个功能.那么他们之间的区别是什么?
首先静态代理的可理解性高,因为方法都是明明白白写好的,而动态代理,由于将所有的方法都汇聚到一个invoke()方法中,所以代码量大大减少,复用率高,并且容易维护和修改.不像静态代理,每个类都需要写一个代理类.
而静态代理由于在编译期间就指明了调用关系,而不用像动态代理去反射,所以效率高于动态代理.但是动态代理由于采用反射机制,在运行时创建动态代理,灵活性高,耦合性低.
然后,对于静态代理不需要引入任何其他的类,只需要和被代理的类,实现同一个接口.但是动态代理需要,java.lang.reflect 包中的Proxy类和InvocationHandler 接口提供了生成动态代理类的能力,Proxy类中的newProxyInstance()方法用来创建动态代理类的实例,而当调用动态代理类的实例的任意方法(如addUser),则该方法(addUser)会调用与它关联的InvocationHandler对象的invoke()方法.
所以,总的来说,对于不复杂追求效率的,可以用静态代理,但是对于复杂的,用动态代理就好多了.
实现
public static voidmain(String[] args) {
SecurityHandlerhandler = new SecurityHandler();
UserManager userManager=(UserManager)handler.createProxyInstance(new UserManagerImpl());
userManager.addUser("张三","123");
}
将动态代理的实现在客户端使用,实例化一个SecurityHanlder对象,用createProxyInstance,传入UserManagerImpl目标对象,返回UserManagerImpl的代理对象,强制转型为UserManager,使用该方法的addUser方法,就会调到invoke方法上,加上安全性验证的方法,并且去调用目标方法,真正的去添加用户.
总结
这样就可以做到又不用违背开发封闭原则,又可以方便的应对修改.那么对于什么样的情况,可以采用这样的方式来做?
对于那种到处都需要使用,但是又和方法本身没有什么关系的独立服务,可以将其提取出来作为一个方法,放到一个类中.这个类可以称为Aspect,这个方法可以称为advice.你可以选择将这个方法,放到目标方法执行前或者执行后调用,也可以选择目标方法出了异常再调用.那么目标方法是什么,比如只有增删改才需要安全性验证,查询就不用了,那么目标方法就是增删改的所有方法了,如何做到控制?使用一些表达式如:execution(* add*(..)),这叫做PointCut.这样就可以产生AOP了.