一、IOC(控制反转)的注解
ioc的注解 – 要使用注解,就要导入spring-aop-4.2.9.RELEASE.jar
1. 入门
1. 导入jar包
除了导入基本的4个 和 日志包之外, 还要导入spring-aop-xx.jar
2. 给指定的类上面打上注解(打上一个标记)
@Component("us")或@Component(value="us")
//Component : 组件 spring 把我们这些托管的类,也看成是组件
@Component("us")
public class UserServiceImpl implements UserService {
}
3. 在applicationContext.xml中 导入context约束 ,然后打开注解的扫描开关
约束去哪里找,去Spring提供的html文档中去找,搜寻context的关键字快速寻找
<?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: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">
<!-- 注解的扫描开关,也就是告诉spring要去扫描指定类上面有没有注解,如果有,就解析这个注解 -->
<context:component-scan base-package="com.itheima"></context:component-scan>
<!-- 以下这种写法不常用。 这个开关虽然也是扫描的注解的开关,但是只能扫描在这个xml里面配置的bean 这些类 -->
<!-- <context:annotation-config></context:annotation-config> -->
</beans>
4. 获取实例的代码和以前一样。
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = (UserService) context.getBean("userService");
userService.save();
((AbstractApplicationContext) context).close();
}
2. IOC注解创建对象
spring针对ioc的注解,提供了通用的一个注解 @Component ,但是由于我们的web项目一般会使用分层的结构。 所以spring为了迎合这种趋势,也做出来每一层各自独立的注解。
@Controller : 标记 web层
@Service : 标记业务逻辑层
@Repository : 标记 dao层
spring做出来这三个独立的分层注解,也许未来会对它们进行增强、扩展,所以大家使用注解的话, 尽量使用 这三个。
- 如果注解里面不写内容, 那么默认也有一个id值, 就是类名的字符串,不过第一个字母小写
注解类:
@Service
public class UserServiceImpl{}
取值:
context.getBean("userServiceImpl");
- 默认生成的实例还是单例的,如果想生成多例,那么需要配置@Scope注解
@Service
@Scope("prototype")
public class UserServiceImpl{}
- 初始化方法以及销毁方法的设置
init-method: 表示初始化这个类时,会执行指定的方法
destroy-method : 销毁类实例时,会调用这个方法
<bean init-method="init" destroy-method="destroy"></bean>
(注意,只有在单例情况下,才会出现销毁的情况,就是关闭工厂。多例情况是不会执行销毁的方法的。)
这两个也可以使用注解来配置
@PostConstruct
@PreDestroy
这两个配置是直接写在方法上面的
@Service("userService")
@Scope("prototype")
public class UserServiceImpl implements UserService {
@Override
public void save() {
System.out.println("userServiceImpl中的save方法执行了");
}
@PostConstruct
public void init(){
System.out.println("userServiceImpl中的init方法执行了");
}
@PreDestroy
public void destroy(){
System.out.println("userServiceImpl中的destroy方法执行了");
}
}
3. 属性注入 – 侧重注入对象类型
使用注解属性注入:
因为这样注入普通属性,几乎没有可用性,还不如直接在声明的时候指定值
@Service("userService")
@Scope("prototype")
public class UserServiceImpl implements UserService {
@Value("张三")
private String username ;
}
注入对象类型:
* @AutoWired
自动装配, spring会找到接口的实现类,然后创建实例注入进来
问题:如果该接口有多个实现,那么会抛出异常,无法注入;效率也没有Resource高
对象:
@Repository("userDao")
public class UserDaoImpl implements UserDao {}
使用:
@Service("us")
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao ;
}
- @Resource
根据指定的标签, 标识符去找到具体的类,然后注入进来。效率高
对象:
@Repository("userDao")
public class UserDaoImpl implements UserDao {}
使用:
@Service("us")
public class UserServiceImpl implements UserService {
@Resource(name="userDao")
private UserDao userDao ;
}
@Qualifier (了解)
用来标记 自动装配的bean在spring IoC容器中的具体名称
这个注解标记比较少用,主要是在xml + 注解结合使用,并且在xml里面针对某一种类型bean , 声明了多了,存在了多个名称。
那么可以使用@Qqualifier来强制指定使用哪一个bean来构建实例。
<bean id="userDao" class ="com.itheima.dao.UserDao">
<property name="name" value="奥巴马">
</bean>
<bean id="userDao2" class ="com.itheima.dao.UserDao">
<property name="name" value="特朗普">
</bean>
public class UserServiceImpl{
@Autowired
@Qqualifier(name="userDao2")
private UserDao userDao;
}
xml和注解混合使用
1.使用xml来声明bean
<?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: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 id="userService" class="com.itheima.service.impl.UserServiceImpl"
scope="prototype" init-method="init" destroy-method="destroy">
</bean>
<bean id="user" class="com.itheima.domain.User" scope="prototype">
</bean>
<context:component-scan base-package="com.itheima"></context:component-scan>
</beans>
2.使用注解来注入属性
public class UserServiceImpl implements UserService {
/*@Resource(name="user")*/
@Autowired
private User user;
}
public class User {
@Value("张三")
private String username;
@Value("123")
private String password;
}
二、Spring测试
早前我们测试业务逻辑,一般都使用Junit 框架来测试, 其实在spring框架里面它也做出来了自己的一套测试逻辑, 里面是对junit进行了整合包装。本质上就是让我们在测试的时候少写工厂类的创建代码
使用Spring测试可以不用打开注解扫描开关, 当然如果要用的那些类上面有有注解, 就必须打开。只是测试类有注解的话,不用打开。 只有其他普通类,托管的类有注解,就必须打开注解扫描开关。
1. 导入jar包
spring -test-xxx.jar
- 在类上打注解
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(“classpath:applicationContext.xml”)
这两个注解就是完成工厂的构建工作,就是指定了由SpringJUnit4ClassRunner 去读取applicationContext 完成工厂构建工作
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringTest {
@Autowired
private UserService userService;
@Test
public void test(){
userService.save();
}
}
三、Spring AOP – Aspect Oriented Programmig面向切面编程
- AOP
Aspect Oriented Programming,面向切面编程 它是对OOP(面向对象编程)的一种补充,其实它的核心就是在不改动原来代码的基础上,可以进行扩展或者增强。
![]()
1. AOP的底层实现
动态代理
代理,不管是什么,一定需要先有真实对象。首先就创建一个真实对象出来
* 使用jdk的动态代理。proxy类
针对真实类,有实现接口
创建出来接口的实现类,然后创建实现类的对象作为代理对象
* 使用cglib的动态代理。Enhancer类
如果那个真实类没有实现接口。
创建真实类的子类
- 使用JDK的动态代理
针对有实现接口的类。 创建出来接口的一个实现类,然后创建实现类的对象,作为代理对象
@Test
public void testJDKProxy(){
//1. 创建真实对象
final UserService userService = new UserServiceImpl();
//2. 创建代理对象
UserService proxyService = (UserService) Proxy.newProxyInstance(
userService.getClass().getClassLoader(),
userService.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//只要在外面用了代理对象调用方法,就一定回来到这个invoke里面,这里面要做的工作就是调用真实对象的方法。
//userService.save();
System.out.println("增强了日志输出功能");
return method.invoke(userService, args);
}
});
//3. 使用代理对象
proxyService.save();
}
- 使用Cglib动态代理
如果那个真实类没有实现接口, 使用cglib 。 创建出来真实类的子类, 然后创建子类的对象,作为代理对象。
@Test
public void testCglibProxy(){
System.out.println("执行了cglib代理~~");
//1. 创建真实对象
final ProductService productService = new ProductService();
//2. 创建代理
Enhancer enhancer = new Enhancer();
//设置父类是谁 表示要做谁的子类出来作为代理
enhancer.setSuperclass(ProductService.class);
//设置回调
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable {
System.out.println("增强了日志输出功能2222");
return arg1.invoke(productService, arg2);
}
});
//创建代理对象
ProductService proxyService = (ProductService) enhancer.create();
proxyService.delete();
}
2. AOP的术语
连接点:Joinpoint
被增强的类中的所有方法都可以称之为连接点
切入点:Pointcut
具体被增强的方法
切面:Aspect
切面的解释应该是切入点和增强的结合 (这是侧重结果)。
织入:weaving
把增强应用到目标对象上来创建新的代理对象的过程
(这个注重过程)
增强:Advice
要扩展的功能。加入到切入点的方法
前置增强:增强在方法之前执行
后置增强:增强在方法之后执行
环绕增强:前后都执行增强
异常增强:只有方法出现异常才执行增强
最终增强:在最后执行。可以获取方法的返回值。
3. AOP入门
- 定义业务逻辑类
public class UserServiceImpl implements UserService {
@Override
public void save() {
System.out.println("调用了UserServiceImpl 的 save方法");
}
}
- 定义增强类
public class Logger {
public static void log(){
System.out.println("输出日志了~~");
}
}
导入jar包
a. 导入 spring必须的jar
b. 额外导入: 4个包:
–这两个包是Spring提供的
spring-aop-4.2.9.RELEASE.jar - spring本身的包
spring-aspects-4.2.9.RELEASE.jar 针对aop联盟做出来的实现包,但这个实现包并不全部实现。
– 这两个包是额外的
aopalliance-1.0.jar 联盟包,其实就是aop规范
aspectjweaver-1.8.9.jar - 实现包
面向切面过程中,Spring AOP是遵循了AOP联盟的规范实现的,所以需要有AOP联盟的接口包
aopalliance-x.x.jar,接口包依赖aspectjweaver-x.x.x.jar导入约束
要导入aop的约束
<?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"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl">
</bean>
<bean id="logger" class="com.itheima.domain.logger">
</bean>
</beans>
- 让spring托管 业务逻辑类 和 增强类
<bean id="us" class="com.itheima.service.impl.UserServiceImpl" ></bean>
<bean id="logger" class="com.itheima.util.Logger" ></bean>
- 配置AOP
<!-- 下面的配置核心,就是把logger里面的log方法应用到us里面的save方法中 -->
<!-- 开始aop的配置 -->
<aop:config>
<!-- 就是用于表示,要对哪一个方法进行增强。 其实就是定义一种规则,然后spring根据这种规则就能够找到对应的方法
后面将要对这些找到的方法进行增强
execution(* com.xyz.myapp.service.*.*(..))
execution : 固定写法,表示执行
第一个* : 表示任意返回值
com.xyz.myapp.service : 表示这个包
第二个* : 表示上面这个包的任意类
第三个* 表示上面找到的类的任意方法
(..) : 表示任意参数
spring根据这个表达式已经找到了具体对应的方法。
-->
<aop:pointcut expression="execution(* com.itheima.service.impl.*.*(..))" id="savePointCut"/>
<!-- 还缺少,把什么方法应用到上面找到的那些方法之上,去完成增强
连起来的意思就是: 把一个叫做logger的这个bean 里面的一个方法叫做 log 的方法,用到 一个叫做savePointCut 切入点找到
的那些方法上面去,执行前置增强
-->
<aop:aspect ref="logger">
<!-- 定义前置增强
根据上面的表达式找到的切面,其实就是找到的那些方法,给他们应用前置增强, 把一个叫做log的方法用到他们前面去。
-->
<aop:before method="log" pointcut-ref="savePointCut"/>
</aop:aspect>
</aop:config>
4. 增强
<!-- 配置aop -->
<aop:config>
<!-- 配置切入点 spring根据这个规则,找到具体的方法
xml方式不好控制具体哪一个方法
-->
<aop:pointcut expression="execution(* com.itheima.service.impl.*.save(..))" id="pointCut01"/>
<!-- 这个是对事务操作时,才有用 -->
<!-- <aop:advisor advice-ref=""/> -->
<aop:aspect ref="logger">
<!-- 前置增强 -->
<!-- <aop:before method="log" pointcut-ref="pointCut01"/> -->
<!-- 后置增强 -->
<!-- <aop:after method="log" pointcut-ref="pointCut01"/> -->
<!-- 环绕增强 -->
//环绕异常也可以同时写上前置增强 和 后置增强,即为环绕异常
<!-- <aop:around method="around" pointcut-ref="pointCut01"/> -->
<!-- 异常增强 -->
<!-- <aop:after-throwing method="log" pointcut-ref="pointCut01"/> -->
<!-- 最终增强 -->
<aop:after-returning returning="result" method="log02" pointcut-ref="pointCut01"/>
</aop:aspect>