Spring笔记(二)——注解方式实现IoC和AOP

1.注解方式实现IoC


除了使用xml配置文件实现IoC外,Spring还提供了注解的方式实现IoC。相比于xml文件,注解方式开发更加方便。

1.1 需要导入的jar包

需要导入的jar包
这些jar包中,包含Spring实现AOP,Junit单元测试,日志需要的jar包。

1.2 applicationContext.xml文件

需要开启注解扫描,并且加入注解的约束。该文件仍然置于src目录下。
需要引入context的约束,具体的约束如下
        <beans xmlns="http://www.springframework.org/schema/beans"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="
                http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- bean definitions here -->

        </beans>
开启注解扫描:
 <context:component-scan base-package="com.mq"/>
base-package表示要扫描的包结构。com.mq表示扫描com.mq包结构下的所有文件。
applicationContext.xml文件完整内容


1.3 代码实现

javaBean如下:

@Component(value="myService")
public class MyServiceImpl implements MyService {
	private MydaoImpl myDao;
	private String name;

	private int age;
	
	public void setName(String name) {
		this.name = name;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public void setMyDao(MydaoImpl myDao) {
		this.myDao = myDao;
	}

	@Override
	public void saveUser() {
		System.out.println("业务层保存用户");
	}
	
	public void printInfo(){
		System.out.println("age-"+age+"name-"+name);
	}
	
	public void testDI(){
		myDao.save();
	}
	
}

测试方法:
	@Test
	public void f1(){
		 
	ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
	MyServiceImpl userService = (MyServiceImpl) applicationContext.getBean("myService");
         userService.saveUser();
	}


结果:
11:07:44,255  INFO ClassPathXmlApplicationContext:578 - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@4f4a7090: startup date [Sat Jul 15 11:07:44 CST 2017]; root of context hierarchy
11:07:44,382  INFO XmlBeanDefinitionReader:317 - Loading XML bean definitions from class path resource [applicationContext.xml]
业务层保存用户
@Component(value="myService")   -- 相当于在XML的配置方式中 <bean id="myService" class="...">
除了@Component这个注解,还有三个衍生的注解,但功能都是一样的。
    * @Controller       -- 作用在WEB层
    * @Service          -- 作用在业务层
    * @Repository       -- 作用在持久层

这三个衍生的注解只是为了通过不同的注解名称让三层结构更加清晰,用法都是相同的。

1.4 Bean管理注解(DI)

包括String在内的基础类型的属性赋值,使用@Value注解即可。
引用类型的属性,注入时需要用到的注解是@Autowired 和 @Qualifier。 其中@Autowired 按类型进行自动装配,@Qualifier强制使用名称注入。在开发中,使用@Resource注解进行属性注入更加方便。@Resource是java JDK提供的注解,相当于@Autowired和@Qualifier一起使用。

代码如下:
@Repository(value="myDao")
public class MydaoImpl implements Mydao {

	public void save() {
	System.out.println("这里是持久层");
	}

}
这里MydaoImpl就使用了@Repository注解,本质上和 @Componen是一样的。
@Component(value="myService")
public class MyServiceImpl implements MyService {
	@Resource(name="myDao")
	private MydaoImpl myDao;
	@Value(value="曹操")
	private String name;
	
	@Value(value="45")
	private int age;
	@Value(value="45")
	
	public void setName(String name) {
		this.name = name;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public void setMyDao(MydaoImpl myDao) {
		this.myDao = myDao;
	}

	@Override
	public void saveUser() {
		System.out.println("业务层保存用户");
	}
	
	public void printInfo(){
		System.out.println("age-"+age+"name-"+name);
	}
	
	public void testDI(){
		myDao.save();
	}
	
}
通过@Resource进行类属性的注入。
测试方法如下:
	@Test
	public void f1(){
		 
		ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
		MyServiceImpl userService = (MyServiceImpl) applicationContext.getBean("myService");
         userService.saveUser();
         userService.printInfo();
         userService.testDI();
         
	}

结果如下:
11:32:40,947  INFO ClassPathXmlApplicationContext:578 - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@4f4a7090: startup date [Sat Jul 15 11:32:40 CST 2017]; root of context hierarchy
11:32:41,116  INFO XmlBeanDefinitionReader:317 - Loading XML bean definitions from class path resource [applicationContext.xml]
业务层保存用户
age-45name-45
这里是持久层

2.Spring框架整合JUnit单元测试

首先需要引入spring-test.jar包。
代码如下:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class MyTest {
	@Resource(name="myService")
	private MyServiceImpl userService;
	@Test
	public void f2(){
         userService.saveUser();
         userService.printInfo();
         userService.testDI();
	}

}

运行结果如下:
业务层保存用户
age-45name-45
这里是持久层
需要添加三个注解,"classpath:applicationContext.xml"表示类路径下的 applicationContext.xml文件,名称也必须要保持一致。
3.AOP

3.1 AOP简介

1. 什么是AOP的技术?
    * 在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程
    * AOP是一种编程范式,隶属于软工范畴,指导开发者如何组织程序结构
    * AOP最早由AOP联盟的组织提出的,制定了一套规范.Spring将AOP思想引入到框架中,必须遵守AOP联盟的规范
    * 通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术
    * AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型
    * 利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率
2. AOP:面向切面编程.(思想.---解决OOP遇到一些问题)
3. AOP采取横向抽取机制,取代了传统纵向继承体系重复性代码(性能监视、事务管理、安全检查、缓存)
4. 为什么要学习AOP
    * 可以在不修改源代码的前提下,对程序进行增强

Spring的AOP底层采用动态代理来实现。

3.2 AOP的相关术语

1. Joinpoint(连接点):   spring中需要被拦截的方法,因为spring只支持方法类型的连接点
2. Pointcut(切入点)   :  拦截逻辑的定义。
3. Advice(通知/增强)  :所谓通知是指拦截到Joinpoint之后所要做的事情就是通知.通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能)
4. Introduction(引介):引介是一种特殊的通知在不修改类代码的前提下, Introduction可以在运行期为类动态地添加一些方法或Field
5. Target(目标对象) :代理的目标对象
6. Weaving(织入) : 是指把增强应用到目标对象来创建新的代理对象的过程
7. Proxy(代理): 一个类被AOP织入增强后,就产生一个结果代理类
8. Aspect(切面):是切入点和通知的结合,需要我们自己来实现的。


3.3代码实现

配置文件的约束如下:
 <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xsi:schemaLocation="
            http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

本篇AOP的实现采用xml文件的方式。
定义切面类:
public class MyAspect {
	public void writeLog(){
		System.out.println("正在生成日志");
	}

}
需要被增强的方法
public class MyServiceImpl implements MyService {


	@Override
	public void saveUser() {
		System.out.println("业务层保存用户");
	}

	
}

配置文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xsi:schemaLocation="
            http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
	
<bean id="service" class="com.mq.service.MyServiceImpl">

</bean>

<bean id="mydao" class="com.mq.dao.MydaoImpl">
</bean>

<bean id="aspect" class="com.mq.aop.MyAspect">
</bean>
 <aop:config>
       <!--  引入切面类 -->
        <aop:aspect ref="aspect">
            <!-- 定义通知类型:切面类的方法和切入点的表达式 -->
            <aop:before method="writeLog" pointcut="execution(public void com.mq.service.MyServiceImpl.saveUser())"/>
       </aop:aspect>
</aop:config>	 
</beans>

运行结果:
正在生成日志
业务层保存用户
13:19:19,589  INFO GenericApplicationContext:960 - Closing org.springframework.context.support.GenericApplicationContext@1810399e: startup date [Sat Jul 15 13:19:19 CST 2017]; root of context hierarchy
定义aop需要用到aop标签。<aop:aspect ref="aspect"> 中的aspect是切面类(增强类)的id。
可见在执行saveUser方法之前,执行了writeLog方法。
那么如何控制增强的方法(切入点)的执行时机呢?需要通过设置通知的类型来实现。比如在上面的配置文件中,before就是通知类型。
AOP所有的通知类型如下:
1. 前置通知
    * 在目标类的方法执行之前执行。
    * 配置文件信息:<aop:after method="after" pointcut-ref="saveUser"/>
    * 应用:可以对方法的参数来做校验

2. 最终通知
    * 在目标类的方法执行之后执行,如果程序出现了异常,最终通知也会执行。
    * 在配置文件中编写具体的配置:<aop:after method="after" pointcut-ref="saveUser"/>
    * 应用:例如像释放资源

3. 后置通知
    * 方法正常执行后的通知。       
    * 在配置文件中编写具体的配置:<aop:after-returning method="afterReturning" pointcut-ref="saveUser"/>
    * 应用:可以修改方法的返回值

4. 异常抛出通知
    * 在抛出异常后通知
    * 在配置文件中编写具体的配置:<aop:after-throwing method="afterThorwing" pointcut-ref="saveUser"/> 
    * 应用:包装异常的信息

5. 环绕通知
    * 方法的执行前后执行。
    * 在配置文件中编写具体的配置:<aop:around method="around" pointcut-ref="saveUser"/>
    * 要注意:目标的方法默认不执行,需要使用ProceedingJoinPoint对来让目标对象的方法执行。


如果配置文件中的aop标签如下:
 <aop:config>
       <!--  引入切面类 -->
        <aop:aspect ref="aspect">
            <!-- 定义通知类型:切面类的方法和切入点的表达式 -->
            <aop:after method="writeLog" pointcut="execution(public void com.mq.service.MyServiceImpl.saveUser())"/>
       </aop:aspect>
</aop:config>

那么会先执行saveUser方法,再执行writeLog方法。

其中环绕通知比较特殊,需要手动进行通知,代码如下:
切面类:
public class MyAspect {
	public void writeLog(){
		System.out.println("正在生成日志");
	}
	
	/**
	 * 环绕通知:方法执行之前和方法执行之后进行通知,默认的情况下,目标对象的方法不能执行的。需要手动让目标对象的方法执行
	 */
	public void writeLog2(ProceedingJoinPoint joinPoint){
		System.out.println("生成日志前...");
		try {
			// 手动让目标对象的方法去执行
			joinPoint.proceed();
		} catch (Throwable e) {
			e.printStackTrace();
		}
		System.out.println("生成日志后...");
	}
}
需要被增强的方法依然是saveUser方法。
配置文件如下:
 <aop:config>
       <!--  引入切面类 -->
        <aop:aspect ref="aspect">
            <!-- 定义通知类型:切面类的方法和切入点的表达式 -->
            <aop:around method="writeLog2" pointcut="execution(public void com.mq.service.MyServiceImpl.saveUser())"/>
       </aop:aspect>
</aop:config>	
执行结果:
生成日志前...
业务层保存用户
生成日志后...
13:40:25,462  INFO GenericApplicationContext:960 - Closing org.springframework.context.support.GenericApplicationContext@1810399e: startup date [Sat Jul 15 13:40:24 CST 2017]; root of context hierarchy

3.4 切入点表达式

切入点表达式就是pointcut后的取值。
一般情况下:切入点表达式的书写顺序为:方法的修饰符——返回类型——所在类的全路径——方法名
当然,有些参数可以省略,书写的具体规则如下:
1. 在配置切入点的时候,需要定义表达式,重点的格式如下:execution(public * *(..)),具体展开如下:
    * 切入点表达式的格式如下:
        * execution([修饰符] 返回值类型 包名.类名.方法名(参数))

    * 修饰符可以省略不写,不是必须要出现的。
    * 返回值类型是不能省略不写的,根据你的方法来编写返回值。可以使用 * 代替。
    * 包名例如:com.itheima.demo3.BookDaoImpl
        * 首先com是不能省略不写的,但是可以使用 * 代替
        * 中间的包名可以使用 * 号代替
        * 如果想省略中间的包名可以使用 .. 

    * 类名也可以使用 * 号代替,也有类似的写法:*DaoImpl
    * 方法也可以使用 * 号代替
    * 参数如果是一个参数可以使用 * 号代替,如果想代表任意参数使用 ..












评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值