谈谈 对 Spring 的理解??
这道题是java 面试 最常见的题,上网随便搜都能得到很多答案,其中都提到spring 两个核心特性:IOC 和 AOP。这里谈谈我对AOP的理解。
AOP(Aspect-Oriented Programming),即面向切面编程,平时用到的拦截器,过滤器,都是AOP 思想。AOP思想把系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。利用这种”横切“ 的技术,横切关注点会发生在多处核心关注点,并且各处都基本相似,比如,权限、日志、异常、事务处理等。
AOP 实现原理
动态代理。讲动态代理之前,先看看静态代理的代码:
/**
* 静态代理演示,接口,目标类,代理类,目标类和代理类实现同一个接口,获得同样技能
* @author hyl
*
*/
public class StaticProxy {
public static void main(String[] args) {
Person star = new Star();
Person starProxy = new StaticStarProxy(star);
starProxy.sing();
}
}
/**
* 定义一个接口
* @author hyl
*
*/
interface Person{
void sing();
}
/**
* 目标类
* @author hyl
*
*/
class Star implements Person{
@Override
public void sing() {
System.out.println("我是歌星,我会唱好听的歌");
}
}
/**
* 代理类实现同样的接口,不同的是,他不是自己唱歌,而是让歌星唱
* 也是因为每次要实现同样的接口,如果方法多,每个方法都要维护,所以产生了动态代理
* @author hyl
*
*/
class StaticStarProxy implements Person{
/** 接受要代理的对象*/
Person person;
public StaticStarProxy(Person person) {
super();
this.person = person;
}
@Override
public void sing() {
System.out.println("我是经纪人,给我钱就让歌星唱歌");
person.sing();
}
}
上面代码注释也写了,目标类要和代理类实现同样的接口,那么如果接口添加方法,目标类和代理类都要维护。这个时候,动态代理登场,接口和目标类不变,上代码:
/**
* JDK动态代理,和静态代理一样的角色,一个接口,一个是目标对象,一个代理对象
* 创建代理对象主要需要两个类 InvocationHandler,Proxy
* 代理类必须实现InvocationHandler 方法,用Proxy.newProxyInstance创建动态代理对象
* @author hyl
*
*/
public class DynamicProxy {
public static void main(String[] args) {
Person star = new Star();
InvocationHandler handler = new DynamicStarProxy(star);
/**创建代理类
* 第一个参数 handler.getClass().getClassLoader() ,使用handler这个类的ClassLoader对象来加载我们的代理对象
* 第二个参数star.getClass().getInterfaces(),我们这里为代理对象提供的接口是真实对象所实行的接口,表示我要代理的是该真实对象,这样我就能调用这组接口中的方法了
* 第三个参数handler, 我们这里将这个代理对象关联到了上方的 InvocationHandler 这个对象上
*/
Person dynamicProxy = (Person) Proxy.newProxyInstance(handler.getClass().getClassLoader(), star.getClass().getInterfaces(), handler);
/**
* 通过 Proxy.newProxyInstance 创建的代理对象是在jvm运行时动态生成的一个对象,
* 它并不是我们的InvocationHandler类型,也不是我们定义的那组接口的类型,
* 而是在运行是动态生成的一个对象,并且命名方式都是这样的形式,以$开头,proxy为中,最后一个数字表示对象的标号。
*/
System.out.println("dynamicProxy: "+dynamicProxy.getClass().getName());
dynamicProxy.sing();
}
}
/**
* 代理类必须实现InvocationHandler 接口
* 同时创建一个供传入真是对象的构造方法
* @author hyl
*
*/
class DynamicStarProxy implements InvocationHandler{
// 这个就是我们要代理的真实对象
private Object subject;
// 构要代理的真实对象赋值
public DynamicStarProxy(Object subject)
{
this.subject = subject;
}
/**
* @param proxy 指代我们所代理的那个真实对象
* @param method 调用真实对象的某个方法的Method对象
* @param args 方法的参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println(subject.getClass().getName());
System.out.println("我是经纪人,给我钱才让歌星唱歌。。动态代理");
Object object = method.invoke(subject, args);
System.out.println("歌星唱完歌了,大家鼓掌");
return object;
}
}
通过代码可以看到,在创建代理对象的时候只需要传入目标对象的所有接口,就能让代理对象可代理所有的接口的所有方法,但是有多个接口的话,创建的时候要强转,比如上面Star 实现了Person,创建的时候转成了Person,如果Star 不仅实现了Person,还实现Monkey,要用到Monkey 类下方法的时候 要强转为Monkey。
Object object = method.invoke(subject, args); 这句代码是用反射机制,所以动态代理事先不知道要代理的是什么,只有在运行的时候才能确定。上面例子在运行 dynamicProxy.sing(); 时才确认要执行的方法。
这里只写了JDK自带的动态代理,这种代理方式是目标类必须实现接口,如果没有接口,继承,如何解决:CGlib 方式。这里不做笔记。
AOP 的实现
第一步,spring配置文件开启识别,添加 <aop:aspectj-autoproxy/>
第二部,定义切面,代码:
/**
* @Aspect 定义一个切面,@Component注入到spring
* @author hyl
*
*/
@Component
@Aspect
public class AopTest {
/**
* 声明切点,两部分:
* 一个方法签名, 包括方法名和相关参数
* 一个 pointcut 表达式, 用来指定哪些方法执行是要可以织入 advice的
* 满足 point cut 规则的 叫 join point 连接点
*/
@Pointcut("execution(* hyl.com.front.controller..*(..))")
public void log(){}
/**
* 定义 advice (增强)
* 定义满足表达式的 “拦截器”,增强join point(符合规则的方法)
* @param joinPoint
* @return
* @throws Throwable
*/
@Around("log()")
public Object writeLog(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("AOP之前");
System.out.println(joinPoint.getSignature().getName());
Object object = joinPoint.proceed();
System.out.println("AOP之后");
return object;
}
}
上面的声明切点和增强可以合在一起写
@Around("execution(* hyl.com.front.controller..*(..))")
public Object writeLog(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("AOP之前");
System.out.println(joinPoint.getSignature().getName());
Object object = joinPoint.proceed();
System.out.println("AOP之后");
return object;
}
增强常用方法注解:
@around 可以在一个方法的之前之前和之后添加不同的操作。
@Before 方法执行之前执行
@After 方法执行之后执行
@AfterThrowing 方法抛出异常之后执行
@AfterRunning 方法返回结果之后执行
这些注解的用法都和上面代码的@around 一样。
常见切点表达式:
"execution(* aaa(..))" 表示匹配所有目标类中的 aaa() 方法。
"within(com.hyl.*)"
表示 com.hyl 包中的所有连接点,,即包中的所有类的所有方法。
"@annotation(com.hyl.common.annotations.NeedLog)" 匹配@NeedLog注解所标注的方法。
学习遇到的问题:
struts2 会出现所有@Autowired全部失效,解决方案,在 struts.xml 中加上
<constant name="struts.objectFactory.spring.autoWire.alwaysRespect" value="true" />