自制aop和注解

来源:互联网 发布:广州万维软件 编辑:程序博客网 时间:2024/05/01 17:48

项目使用了jdbc的多数据源连接,这我要吐槽一下原来的项目了。没接口,代码写在jsp中。我也是服了,spring扫描包和springmvc扫描一样的包,这就是带我今天入坑的原因之一。

//创建叫做DataSource的注解,可以作用在类上和方法上@Target({ElementType.TYPE, ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME) public @interface DataSource {     String value(); }

一个简单的注解,主要是为了切换线程对象中的数据源,让多数据源可以切换使用。

<bean id="dataSourceAspect" class="com.ht.Aspect.DataSourceAspect" ></bean>    <aop:config proxy-target-class="true">        <aop:aspect ref="dataSourceAspect">            <aop:pointcut id="dataSourcePointcut" expression="execution(* com.ht.dao.*.*(..))"/>            <aop:before pointcut-ref="dataSourcePointcut" method="intercept"/>            <aop:after pointcut-ref="dataSourcePointcut" method="intercept"/>        </aop:aspect>    </aop:config> 
public class DataSourceAspect {    /**     * 拦截目标方法,获取由@DataSource指定的数据源标识,设置到线程存储中以便切换数据源     *      * @param point     * @throws Exception     */    public void intercept(JoinPoint point) throws Exception {        //得到拦截类        Class<?> target = point.getTarget().getClass();        //得到实际拦截的方法        //即这个类哪个方法执行了        MethodSignature signature = (MethodSignature) point.getSignature();        // 默认使用目标类型的注解,如果没有则使用其实现接口的注解        for (Class<?> clazz : target.getInterfaces()) {            //迭代出类的接口,实例一般是实例出这个接口的实例对象            //先看看接口中有没有配置注解,配置了就执行。            resolveDataSource(clazz, signature.getMethod());        }        //最后查看实现的类,但是类中配置了注解的不同值,会覆盖接口的。        resolveDataSource(target, signature.getMethod());    }    /**     * 提取目标对象方法注解和类型注解中的数据源标识     *      * @param clazz     * @param method     */    private void resolveDataSource(Class<?> clazz, Method method) {        try {            Class<?>[] types = method.getParameterTypes();            // 默认使用类型注解            if (clazz.isAnnotationPresent(DataSource.class)) {                DataSource source = clazz.getAnnotation(DataSource.class);                DataSourceContextHolder.setDataSourceType(source.value());            }            // 方法注解可以覆盖类型注解            Method m = clazz.getMethod(method.getName(), types);            if (m != null && m.isAnnotationPresent(DataSource.class)) {                DataSource source = m.getAnnotation(DataSource.class);                DataSourceContextHolder.setDataSourceType(source.value());            }        } catch (Exception e) {            System.out.println(clazz + ":" + e.getMessage());        }    }}

这是我配置的切面。然后我发现我的切面配置启动不报错,但是没有效果,我检查完配置路径发现没有问题,然后寻找问题,在博客中得到了答案。
当你需要对collection层进行切面编程时:(部分借鉴)CGLIB代理配置在了applicationContext.xml 核心配置文件中,该配置文件会被ContextLoaderListenerclass加载,Spring会创建一个WebApplicationContext上下文,称为父上下文(父容器) ,保存在ServletContext中,key为WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE的值。
而spring-mvc.xml是DispatcherServlet,可以同时配置多个,每个 DispatcherServlet有一个自己的上下文对象(WebApplicationContext),称为子上下文(子容器),子上下文可以访问父上下文中的内容,但父上下文不能访问子上下文中的内容。 它也保存在 ServletContext中,key是”org.springframework.web.servlet.FrameworkServlet.CONTEXT”+Servlet名称
当spring加在父容器的时候就会去找切入点,但是这个时候切入的controller是在子容器中的,父容器是无法访问子容器,所以就拦截不到。如果将上述的配置文件放到spring-mvc.xml中,那么问题就解决了。我已经测试可以通过URL访问触发切点了。
解决办法就是把aop放入springmvc中。
但是问题又来了,我明明是对dao层做切面为什么不行呢?查看了配置我知道了,这原来的扫描包是spring扫描全部包,springmvc也扫描全部包。然后就冲突了,dao层被springmvc扫描了。下面的要注意。
Spring MVC 和 Spring 整合的时候,SpringMVC的spring-mvc.xml文件中配置扫描包,不要包含 service的注解,Spring的applicationContext.xml文件中配置扫描包时,不要包含controller的注解。
Spring MVC启动时的配置文件,包含组件扫描、url映射以及设置freemarker参数,让spring不扫描带有@Service注解的类。为什么要这样设置?

因为spring-mvc.xml与applicationContext.xml不是同时加载,如果不进行这样的设置,那么,spring就会将所有带@Service注解的类都扫描到容器中,等到加载applicationContext.xml的时候,会因为容器已经存在Service类,使得cglib将不对Service进行代理,直接导致的结果就是在applicationContext 中的事务配置不起作用,发生异常时,无法对数据进行回滚。以上就是原因所在.

原创粉丝点击