spring 純注解方式 與AOP


spring注解方式

以前我也使用過純注解方式.現在在這里做個記錄

 我們先認識幾個我們都耳熟能詳的注解

  •  @configuration :從spring3.0這個注解就可以用於定義配置類,可以替換xml配置文件,相當於beans的根標簽,配置類中可以包含一個或者多個@bean注解這些方法都會被一個

    AnnotationConfigApplicationContext或AnnotationConfigWebApplicationContext類進行掃描,並用於構建bean定義,初始化Spring容器。

  • @bean: 相當於 bean
  • @componentScan
  • @PropertySource 相當於context:property-placeholder標簽 如果是在類路徑下,需要加上classpath 和@value 配合使用
/**
 * 相當於一個beans
 */
@Configuration
@ComponentScan(basePackages = "xiaodao.spring")
public class SpringConfiguration {

    public SpringConfiguration() {
        System.out.println("spring 容器啟動");
    }

    /**
     * bean id 默認是bean的方法名
     * @return
     */

 /*   @Bean
    public UserService userService(){
        return new UserServiceImpl();
    }*/
}
public class SpringConfigurationTest {

    @Test
    public void test1(){
        //創建純注解方式的spring 容器
        ApplicationContext applicationContext  = new AnnotationConfigApplicationContext(SpringConfiguration.class);
        UserService userService = (UserService) applicationContext.getBean("userService");
       userService.save();
    }

}

spring 分模塊開發

@import 注解的使用方法  

我們在spring中有各種各樣的配置文件.寫在一個類中,肯定是 不合適的.我們需要分門分類

 

@Configuration
@ComponentScan(basePackages = "com.kkb.spring")
@Import({ JdbcConfig.class})
public class SpringConfiguration {
}


@Configuration
@PropertySource("classpath:jdbc.properties")
public class JdbcConfig{
    }

 什么是AOP呢?

  AOP為Aspect Oriented Programming的縮寫,意為:面向切面編程

  AOP最早是AOP聯盟的組織提出的,指定的一套規范,spring將AOP的思想引入框架之中,通過預編譯方式運行期間動態代理實現程序的統一維護的一種技術,

  預編譯方式在spring中沒有使用,因為要結合其他編譯方式,和spring造成強耦合,預編譯方式就是在程序沒有運行前編譯.

  AOP是OOP的延續,,AOP解決的是從橫向解決代碼重復的問題 ,OOP是從縱向解決代碼的重復問題

  AOP采取橫向抽取機制,取代了傳統縱向繼承體系重復性代碼(性能監視、事務管理、安全檢查、緩存)講業務邏輯和系統處理的代碼進行解耦 如如:關閉連接 事物管理,操作日志的記錄

AOP 的相關術語?

1. Joinpoint(連接點)   -- 所謂連接點是指那些被攔截到的點。在spring中,這些點指的是方法,因為spring只支持方法類型的連接點

2. Pointcut(切入點)        -- 所謂切入點是指我們要對哪些Joinpoint進行攔截的定義

3. Advice(通知/增強)    -- 所謂通知是指攔截到Joinpoint之后所要做的事情就是通知.通知分為前置通知,后置通知,異常通知,最終通知,環繞通知(切面要完成的功能)

4. Introduction(引介) -- 引介是一種特殊的通知在不修改類代碼的前提下, Introduction可以在運行期為類動態地添加一些方法或Field

5. Target(目標對象)     -- 代理的目標對象

6. Weaving(織入)      -- 是指把增強應用到目標對象來創建新的代理對象的過程

7.Proxy(代理)        -- 一個類被AOP織入增強后,就產生一個結果代理類

8. Aspect(切面)           -- 是切入點和通知的結合,以后咱們自己來編寫和配置的

  

AOP實現之AspectJ(了解)

***AspectJ是一個java實現的AOP框架,它能夠對java代碼進行AOP編譯(一般在編譯期進行),讓java代碼具有AspectJ的AOP功能(當然需要特殊的編譯器)

***可以這樣說AspectJ是目前實現AOP框架中最成熟,功能最豐富的語言,更幸運的是,AspectJ與java程序完全兼容,幾乎是無縫關聯,因此對於有java編程基礎的工程師,上手和使用都非常容易。

***了解AspectJ應用到java代碼的過程(這個過程稱為織入),對於織入這個概念,可以簡單理解為aspect(切面)應用到目標函數(類)的過程。

***對於這個過程,一般分為動態織入和靜態織入動態織入的方式是在運行時動態將要增強的代碼織入到目標類中,這樣往往是通過動態代理技術完成的,如Java JDK的動態代理(Proxy,底層通過反射實現)或者CGLIB的動態代理(底層通過繼承實現),Spring AOP采用的就是基於運行時增強的代理技術

***ApectJ采用的就是靜態織入的方式。ApectJ主要采用的是編譯期織入,在這個期間使用AspectJ的acj編譯器(類似javac)把aspect類編譯成class字節碼后,在java目標類編譯時織入,即先編譯aspect類再編譯目標類。

 

 

  Spring AOP實現原理分析

  spring AOP 是通過動態代理技術實現的

  而動態代理的技術是通過反射來實現的

  動態代理技術的實現方式有兩種:基於接口的JDK動態代理和基於繼承的CGLib動態代理。

  

我們來寫一下 jdk的代理和cglib的代理類

  我們要代理的對象

public interface UserService {

    public void save();
}
@Service("userService")
public class UserServiceImpl implements UserService {
    public void save() {
        System.out.println("userserviceimpl  save方法");
    }
}

代理的實現

public class MyProxyUtils {

    /**
     * 使用JDK動態代理類
     * @param userServiceInterface
     * @return
     */
    public static UserService getProxy(final UserService userServiceInterface){
        /**
         * proxy是jdk中的代理類
         * 1.目標對象的類加載器
         * 2.目標對接的接口
         * 3.代理對象的執行處理器
         */
     UserService userService = (UserService) Proxy.newProxyInstance(userServiceInterface.getClass().getClassLoader(), userServiceInterface.getClass().getInterfaces(), new InvocationHandler() {

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

                System.out.println("記錄日志-開始");
                //下面的代碼是,反射中的api用法
                //方法
                //2.參數
                //這行代碼實際還是調用目標對象的方法
                Object invoke = method.invoke(userServiceInterface, args);
                System.out.println("記錄日志結束");
                return invoke;
            }
        });
        return userService;
    }

    /**
     * 使用cglib的方式動態代理技術實現
     * 它是基於繼承的方式實現的
     * @param userService
     * @return
     */
    public static UserService getProxyByCglib(UserService userService){

        //創建增強器
        Enhancer enhancer =new Enhancer();
        //這里設置增強類的類對象-實現類的類對象
        enhancer.setSuperclass(UserServiceImpl.class);
        //設置回調函數
        enhancer.setCallback(new MethodInterceptor() {
            /**
             *
             * @param o
             * @param method
             * @param args 方法參數
             * @param methodProxy 代理之后的對象的方法
             * @return
             * @throws Throwable
             */
            public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {


                long start = System.currentTimeMillis();
                System.out.println("cglib 記錄開始時間:  "+start);
                //代理對象是目標的對象的子類
                //這行代碼實際還是調用目標對象的方法
                //o 是代理對象
                Object object = methodProxy.invokeSuper(o, args);
                long end = System.currentTimeMillis();
                System.out.println("cglib 記錄結束時間:  "+end);
                return object;
            }
        });
        // 獲取增強之后的代理對象.
        return (UserService) enhancer.create();
    }

}

 

我們的測試類

 

  

public class MyProxyUtilsTest {


    @Test
    public void testjdkProxy(){
        UserService userService = new UserServiceImpl();
        UserService proxy = MyProxyUtils.getProxy(userService);

        userService.save();
        System.out.println("===========");
        proxy.save();

    }

    @Test
    public void testCglibProcy(){
        UserService userService = new UserServiceImpl();
        UserService proxy = MyProxyUtils.getProxyByCglib(userService);

        userService.save();
        System.out.println("===========");
        proxy.save();
    }

}
userserviceimpl save方法
===========
cglib 記錄開始時間: 1556369092690
userserviceimpl save方法
cglib 記錄結束時間: 1556369092700
View Code

 切入點表達式

  execution:必須要([修飾符] 返回值類型 包名.類名.方法名(參數))

  •   修飾符可以省略掉
  • 返回值類型必須要 可以用*來代替
  • 包名:多級包之間使用.分割 包名可以用*號代替,省略中間的包名可以用..
  • 類名可以用*代替,也可以寫成*DaoImpl
  • 方法名可以用*代替 *add
  • 參數可以用*代替 如果有多個參數可以..代替

xml 實現aop

   aop總共有5中通知

前置通知

后置通知

異常通知

后置通知

環繞通知

xml 配置文件

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <beans xmlns="http://www.springframework.org/schema/beans"
 3        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4        xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop"
 5        xsi:schemaLocation="http://www.springframework.org/schema/beans
 6        http://www.springframework.org/schema/beans/spring-beans.xsd
 7        http://www.springframework.org/schema/aop
 8        http://www.springframework.org/schema/aop/spring-aop.xsd">
 9 
10 
11 
12    <bean  class="xiaodao.spring.service.UserServiceImpl"></bean>
13     <!--增強類-->
14    <bean id="myAdvice" class="xiaodao.spring.MyAdvice"></bean>
15 
16     <!--aop 配置-->
17     <aop:config>
18         <!--配置aop切面,切面是由通知和切入點組成的-->
19       <aop:aspect ref="myAdvice">
20           <!--指定切入點.需要通過表達式來指定 method增強類的方法-->
21           <!--<aop:before method="log" pointcut="execution(void xiaodao.spring.service.UserServiceImpl.save())"/>-->
22           <!--<aop:after method="log2" pointcut="execution(void xiaodao.spring.service.UserServiceImpl.save())"/>-->
23           <!--<aop:after-returning method="log3" pointcut="execution(* xiaodao.spring.*.UserServiceImpl.save())"/>-->
24 
25           <!--&lt;!&ndash;異常通知 &ndash;&gt;-->
26           <!--<aop:after-throwing method="log4" pointcut="execution(* xiaodao.spring.*.UserServiceImpl.save())"/>-->
27 
28           <aop:around method="log5" pointcut="execution(* xiaodao.spring.*.UserServiceImpl.save())"/>
29 
30         </aop:aspect>
31     </aop:config>
32 </beans>
View Code
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:application.xml")
public class TestAOP {

    @Autowired
    private UserService userService;

    @Test
    public void test(){
        userService.save();
    }
}

增強類:

public class MyAdvice {
    public void log(){
        System.out.println("記錄日志.....");
    }

    public void log2(){
        System.out.println("記錄日志  后置通知.....");
    }

    public void log3(){
        System.out.println("記錄日志..最終通知..");
    }

    public void log4(){
        System.out.println("記錄日志..異常...");
    }

    public void log5(ProceedingJoinPoint joinPoint) throws Throwable {

        System.out.println("環繞通知前");
        //調用目標對象
        try {
            joinPoint.proceed();
            System.out.println("后置通知");
        } catch (Throwable throwable) {
            System.out.println("環境通知..異常");
            throwable.printStackTrace();
        }finally {
            System.out.println("最終通知");
        }


    }

}
View Code
userservice 還是剛剛那個userservice
@Service("userService") public class UserServiceImpl implements UserService { public void save() { System.out.println("userserviceimpl save方法"); System.out.println(1/0); } }

 注解實現AOP

@Aspect
@Component("myaspect")
//相當於<aop:aspectj-proxy>
@EnableAspectJAutoProxy
public class MyAspect {

    //定義該方法是一個前置通知
    @Before(value = "execution(* xiaodao.spring.service.*.*())")
    public void before(){
        System.out.println("注解前置通知");

    }
    @After(value = "execution(* xiaodao.spring.service.*.*())")
    public void after(){
        System.out.println("注解后置通知");

    }

    @AfterReturning(value = "func()")
    public void end(){
        System.out.println("最終通知");
    }


    @Pointcut(value = "execution(* xiaodao.spring.service.*.*())")
    public void func(){

    }
}

 本來准備全注解實現,目前還不可以junit還是需要加載application.xml 配置文件,暫時不知道junit如何使用我自定義的注解配置類

Spring AOP 采用哪種代理?

JDK 動態代理和 CGLIB 動態代理均是實現 Spring AOP 的基礎。對於這一塊內容,面試官問的比較多,他們往往更想聽聽面試者是怎么回答的,有沒有看過這一塊的源碼等等。

針對於這一塊內容,我們看一下 Spring 5 中對應的源碼是怎么說的。

public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {

 @Override
 public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
   if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
     Class<?> targetClass = config.getTargetClass();
     if (targetClass == null) {
       throw new AopConfigException("TargetSource cannot determine target class: " +
           "Either an interface or a target is required for proxy creation.");
     }
       // 判斷目標類是否是接口或者目標類是否Proxy類型,若是則使用JDK動態代理
     if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
       return new JdkDynamicAopProxy(config);
     }
       // 配置了使用CGLIB進行動態代理或者目標類沒有接口,那么使用CGLIB的方式創建代理對象
     return new ObjenesisCglibAopProxy(config);
   }
   else {
       // 上面的三個方法沒有一個為true,那使用JDK的提供的代理方式生成代理對象
     return new JdkDynamicAopProxy(config);
   }
 }
   //其他方法略……
}

從上述源碼片段可以看出,是否使用 CGLIB 是在代碼中進行判斷的,判斷條件是 config.isOptimize()、config.isProxyTargetClass() 和 hasNoUserSuppliedProxyInterfaces(config)

其中,config.isOptimize() 與 config.isProxyTargetClass() 默認返回都是 false,這種情況下判斷結果就由 hasNoUserSuppliedProxyInterfaces(config) 的結果決定了。

public class ProxyConfig implements Serializable {
    /** use serialVersionUID from Spring 1.2 for interoperability */
    private static final long serialVersionUID = -8409359707199703185L;

    private boolean proxyTargetClass = false;

    private boolean optimize = false;

    boolean opaque = false;

    boolean exposeProxy = false;

    private boolean frozen = false;
.........
}

 

簡單來說,hasNoUserSuppliedProxyInterfaces(config) 就是在判斷代理的對象是否有實現接口,有實現接口的話直接走 JDK 分支,即使用 JDK 的動態代理。
所以基本上可以總結出 Spring AOP 中的代理使用邏輯了:如果目標對象實現了接口,默認情況下會采用 JDK 的動態代理實現 AOP;如果目標對象沒有實現了接口,則采用 CGLIB 庫,Spring 會自動在 JDK 動態代理和 CGLIB 動態代理之間轉換。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM