目录
1.2、定义bean的常用注解@Component @Controller @Service @Repository
1.3.2 properties文件中属性注入—@Value
一、IOC相关注解
spring加载资源时使用注解的形式替代xml配置,将繁杂的spring配置文件从工程中彻底消除掉,简化书写。
注解驱动的弊端:
-
为了达成注解驱动的目的,可能会将原先很简单的书写,变的更加复杂
-
XML中配置第三方开发的资源是很方便的,但使用注解驱动无法在第三方开发的资源中进行编辑,因此会增大开发工作量
1.1、启动注解功能
-
在spring XML配置文件中启动注解扫描,加载类中配置的注解项
<context:component-scan base-package="packageName"/>
-
说明:
-
在进行包所扫描时,会对配置的包及其子包中所有文件进行扫描;
-
扫描过程是以文件夹递归迭代的形式进行的;
-
扫描过程仅读取合法的java文件
-
扫描时仅读取spring可识别的注解
-
扫描结束后会将可识别的有效注解转化为spring对应的资源加入IoC容器
-
-
注意:
-
无论是注解格式还是XML配置格式,最终都是将资源加载到IoC容器中,差别仅仅是数据读取方式不同
-
从加载效率上来说注解优于XML配置文件
-
1.2、定义bean的常用注解@Component @Controller @Service @Repository
- 名称:@Component @Controller @Service @Repository
- 类型:类注解
- 位置:类的定义上方
- 作用:设置该类为spring管理的bean
- 范例:
@Component: 用于创建对象,将创建的对象存放到IOC容器中
value: 设置存放到IOC容器中id的值,如果不写默认为当前类的类名称,首字母小写
以下三个注解跟@Component注解的作用一样,只不过下面三个注解提供了更加详细的语义化(见名知意)
@Controller : 将web层的类创建对象存放到IOC容器中
@Service : 将serice层的类创建对象存放到IOC容器中
@Repository : 将dao层的类创建对象存放到IOC容器中
@Component("userService")
public class AccountServiceImpl{}
- 以上注解相当于xml 中的bean标签:
-
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl"></bean>
-
@Controller、@Service 、@Repository是@Component的衍生注解,功能同@Component
-
@Controller(控制层注解)、@Service(服务层注解) 、@Repository(持久层注解)只是提供了更加明确的语义化(见名知意),精确指出是哪一层的对象,但不是强制要求的;
-
-
相关属性
-
value(默认):定义bean的访问id
-
测例:
<!--开启注解扫描,引入context约束-->
<context:component-scan base-package="hhyZhuJie"/>
/**
* @Component: 用于创建对象,将创建的对象存放到IOC容器中
* value: 设置存放到IOC容器中id的值,如果不写默认为当前类的类名称,首字母小写
* 以下三个注解跟@Component注解的作用一样,只不过下面三个注解提供了更加详细的语义化(见名知意)
* @Controller : 将web层的类创建对象存放到IOC容器中
* @Service : 将serice层的类创建对象存放到IOC容器中
* @Repository : 将dao层的类创建对象存放到IOC容器中
*/
//@Component("ZJService")
@Service("ZJService")
//@Repository("ZJService")
//@Controller("ZJService")
public class JZServiceImpl implements ZJService {
public JZServiceImpl() {
System.out.println("JZServiceImpl无参构造被调用");
}
@Override
public void add() {
System.out.println("JZServiceImpl的add方法被调用");
}
}
//springIOC注解开发测例
@Test
public void t1() {
ApplicationContext c = new ClassPathXmlApplicationContext("springIOCzhujie.xml");
ZJService zjService = c.getBean("ZJService", ZJService.class);
zjService.add();
}
1.3、bean的属性数据注入常用注解
以下注解它们就相当于:
<property name="userDao" ref="userDao" 或 value="基本类型数据或String"></property>
------------------------
这些注解使用在属性上,用于给对象中的属性进行赋值
引用类型:
@Autowired
@Qualifier
@Resource
基本类型+String:
@Value
1.3.1 基本类型和String属性注入—@Value
@Value: 用于注入基本类型和String的
作用:注入基本类型和String的数据
属性:
value:指定数据的内容,它可以支持Spring的EL表达式(SPEL),写法就是${表达式}
SpEL表达式:
作用: 从IOC容器中获取对应的值
格式: ${key}
属性值来自于properties配置文件
加载properties配置文件中的信息存放到容器中
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
注意: 当我们使用注解注入数据时,构造器和set方法都不是必须的了.
测例一:
@Service("ZJService")
@ToString
public class JZServiceImpl implements ZJService {
@Value("迪迦")
private String name;
@Value("18")
private Integer age;
public JZServiceImpl() {
System.out.println("JZServiceImpl无参构造被调用");
}
@Override
public void add() {
System.out.println("JZServiceImpl的add方法被调用");
}
}
@Test
public void 测试Value注解() {
ApplicationContext c = new ClassPathXmlApplicationContext("springIOCzhujie.xml");
ZJService zjService = c.getBean("ZJService", ZJService.class);
System.out.println("测试Value注解 = " + zjService);
//测试Value注解 = JZServiceImpl(name=迪迦, age=18)
}
测例二:获取properties中值
1.3.2 properties文件中属性注入—@Value
配置加载外部Properties资源,将配置文件中的数据存放到IOC容器中
xml配置文件:
<?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">
<!--开启注解扫描,引入context约束-->
<context:component-scan base-package="hhyZhuJie"/>
<!-- 配置加载外部Properties资源,将配置文件中的数据存放到IOC容器中 -->
<context:property-placeholder
location="classpath:springJZ.properties"
file-encoding="GBK"></context:property-placeholder>
</beans>
springZJ.username=迪迦
springZJ.age=18
@Service("ZJService")
//@Repository("ZJService")
//@Controller("ZJService")
@ToString
public class JZServiceImpl implements ZJService {
// @Value("迪迦")
@Value("${springZJ.username}")
private String name;
// @Value("18")
@Value("${springZJ.age}")
private Integer age;
public JZServiceImpl() {
System.out.println("JZServiceImpl无参构造被调用");
}
@Override
public void add() {
System.out.println("JZServiceImpl的add方法被调用");
}
}
@Test
public void 测试Value注解() {
ApplicationContext c = new ClassPathXmlApplicationContext("springIOCzhujie.xml");
ZJService zjService = c.getBean("ZJService", ZJService.class);
System.out.println("测试Value注解 = " + zjService);
//测试Value注解 = JZServiceImpl(name=迪迦, age=18)
}
1.3.3 bean的引用类型属性注入
@Autowired
Autowired: 在注入数据时,是按照bean类型注入的
原理:
被注入的对象,来自IOC容器
如果IOC容器中只有一个此类型的对象,直接注入
如果IOC容器中有多个此类型的对象,按照IOC容器中对象的名称注入,如果名称不匹配报错
属性:
required:
true: 此对象必须注入成功,若不成功则报错. 默认值
false: 可以注入不成功
@Qualifier
作用:在自动按照类型注入的基础之上,再按照bean的id注入.
@Resource
此注解是JDK提供的,作用和@Autowired+@Qualifier一样
作用:默认按照bean名称注入数据
属性:
name:指定bean的名称注入数据
type:指定bean的类型注入数据
注意细节:
如果没有对应的名称与之匹配,则按照类型注入
使用@Autowired注解,如果某个类如@Service("autowiredService")指定名字,那么定义时名字要相同。
若想取不同名字,则需用@Qualifier注解取相同名字。
测例:
//新接口
public interface AutowiredService {
void add();
}
//新实现类
@Service("autowiredService")
public class AutowiredServiceImpl implements AutowiredService {
@Override
public void add() {
System.out.println("AutowiredService中add方法被调用");
}
}
@Service("ZJService")
@ToString
public class JZServiceImpl implements ZJService {
/**
* IOC容器:
* AutowiredService:
* AutowiredServiceImpl accountDaoImpl
* AutowiredServiceImpl2 accountDaoImpl2
* @Autowired: 将IOC容器中的对象注入到属性上
* 默认按照类型注入,当只有一个该类型对象时,直接注入
* 如果发现多个同类型的对象,则按照变量名称注入
* 如果没有一样的变量名则报错
* required: 是否必须注入成功(当前属性需要结合@Qualifier使用)
* true: 必须注入成功,否则报错 (默认值)
* false: 可以注入不成功,如果注入不成功,则为null
* @Qualifier: 指定注入的对象的名称
*
* @Resource:
* 默认按照名称注入,如果IOC容器中没有对应名称的对象,则按照类型注入
* type: 指定类型注入
* name: 指定名称注入
*/
// @Autowired(required = true)
// @Qualifier("autowiredService")
// private AutowiredService auto;
// private AutowiredService autowiredService;
@Resource(name = "autowiredService")//等价于@Autowired(required = true)+@Qualifier("autowiredService")
private AutowiredService auto;
@Override
public void add() {
// System.out.println("JZServiceImpl的add方法被调用");
auto.add();
}
}
//springIOC注解开发测例
@Test
public void Autowired注解() {
ApplicationContext c = new ClassPathXmlApplicationContext("springIOCzhujie.xml");
ZJService zjService = c.getBean("ZJService", ZJService.class);
zjService.add();
}
1.3.4 作用范围(单例多例)和生命周期相关注解
@Scope: 相当于bean标签的scope属性
作用:用于调整bean的作用范围
使用位置: 被创建的类上
属性:
value:指定作用范围的取值。取值是固定的5个,和XML的配置取值是一样的。
singleton: 单实例 默认值
prototype: 多实例
@PostConstruct : 使用在方法上
使用位置: 初始化的方法上
作用:指定初始化方法,相当于init-method
@PreDestroy : 使用在方法上
作用:指定销毁方法,相当于destroy-method
测例:
@Service("ZJService")
//@Scope("singleton")//指定当前类为单例模式,等价于scope=“singleton”
@Scope("prototype")//指定当前类为单例模式,等价于scope=“singleton”
@ToString
public class JZServiceImpl implements ZJService {
public JZServiceImpl() {
System.out.println("JZServiceImpl无参构造被调用");
}
@PostConstruct//等价于init-method
public void init() {
System.out.println("JZServiceImpl的初始化方法被调用");
}
@PreDestroy//等价于destroy-method
public void destroy() {
System.out.println("JZServiceImpl的销毁方法被调用");
}
}
@Test
public void 单例_生命周期注解() {
//饿汉模式 调用初始化
ApplicationContext c = new ClassPathXmlApplicationContext("springIOCzhujie.xml");
ZJService zjService1 = c.getBean("ZJService", ZJService.class);
ZJService zjService2 = c.getBean("ZJService", ZJService.class);
//判断单例、多例
System.out.println(zjService1==zjService2);//true 单例 hash相同 false多例
//转换 调用close方法 调用销毁
ClassPathXmlApplicationContext c2= (ClassPathXmlApplicationContext) c;
c2.close();
}
1.4、纯注解格式
我们需要使用配置类代替配置文件,将以下注解写在配置类上
@Configuration: 声明该类为Spring的配置文件类
@ComponentScan(basePackages = "hhyZhuJie") : 指定要扫描的包
@PropertySource(value = "classpath:springJZ.properties") : 将配置文件交给Spring容器管理
@Bean() : 将方法返回的对象存放到IOC容器中
将返回值存放到IOC容器中
一般将【第三方提供的类】对加载到IOC容器
测试:准备配置类
@Configuration//等价于xml文件,同时是@Component注解的衍生注解,声明该类为Spring的配置文件类
@ComponentScan(basePackages = "hhyZhuJie")//指定要扫描的包
@PropertySource(value = "classpath:springJZ.properties")//将配置文件交给Spring容器管理
public class SpringZJ {
//此时读配置文件的代码不能使用new ClassPathXmlApplicationContext,改用AnnotationConfigApplicationContext
}
//springIOC纯注解开发测例
@Test
public void 纯注解() {
//参数为配置类
ApplicationContext c = new AnnotationConfigApplicationContext(SpringZJ.class);
ZJService zjService1 = c.getBean("ZJService", ZJService.class);
ZJService zjService2 = c.getBean("ZJService", ZJService.class);
//判断单例、多例
System.out.println(zjService1==zjService2);//true 单例 hash相同 false多例
//转换 调用close方法 调用销毁
AnnotationConfigApplicationContext c2= (AnnotationConfigApplicationContext) c;
c2.close();
}
二、整合Junit
作用: 简化单元测试代码
junit 4.12以上版本
步骤:不用创建实现类 直接注入
1.导入jar包坐标
<!-- 引入单元测试的jar包 4.12以上 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!-- 导入Spring整合junit的jar包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
2.使用
在测试类上添加以下两个注解:
@RunWith(SpringJUnit4ClassRunner.class)
二选一:
@ContextConfiguration(locations="classpath:applicationContext.xml") (配置xml文件的路径)
@ContextConfiguration(classes=SpringConfig.class) (配置类)
@RunWith(SpringJUnit4ClassRunner.class)//声明spring提供的单元测试环境
@ContextConfiguration(classes= SpringZJConfig.class) //声明spring的配置信息(配置类)
public class SpringJunit {
@Autowired
private ZJService zjService;
@Test
public void spring整合Junit() {
//不用new实现类
// new JZServiceImpl().add();
zjService.add();
}
}
三、回顾动态代理
方法增强: 在【不改变源码】的基础上对方法进行增强
继承
装饰模式
动态代理
AOP: 在不改变源码的基础上对方法进行增强
底层动态代理:
proxy: jdk提供的,基于接口
cglib: 基于子类,生成被代理类的子类
3.1、基于接口的动态代理(JDK):
作用: 方法增强
要求: 被代理类至少实现一个接口
public interface Car {
void color();
void run();
void jiayou();
}
public class QQ implements Car {
@Override
public void color() {
System.out.println("红色");
}
@Override
public void run() {
System.out.println("正在跑");
}
@Override
public void jiayou() {
System.out.println("加油中");
}
}
public class JdkProxy {
public static void main(String[] args) {
//1.定义目标对象 被代理对象 final代理过程中不能被改变
final QQ qq = new QQ();
Car car = (Car) Proxy.newProxyInstance(
qq.getClass().getClassLoader(), //定义类加载器
qq.getClass().getInterfaces(),//获取布标对象实现接口的字节码对象数组
new InvocationHandler() {
/**
* 每次调用代理类的方法时,此方法都会执行
* @param proxy : 生成的代理类对象(开发慎用)
* @param method : 当前所执行的方法的字节码对象
* @param args : 当前执行的方法所传递的参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String name = method.getName();
System.out.println("当前执行的方法名称: " + name);
if ("run".equals(name)) {
System.out.println("本身方法执行前搞点事~~~");
//调用本身的方法,前后再增强
Object result = method.invoke(qq, args);
System.out.println("本身方法执行后搞点事~~~");
System.out.println("QQ车以每秒1千米的速度在飞奔...");
return null;
}
return null;
}
}
);
// car.color();
car.run();
}
}
3.2、无对象的动态代理(JDK):
public interface Car {
void color();
void run();
void jiayou();
}
public class QQ implements Car {
@Override
public void color() {
System.out.println("红色");
}
@Override
public void run() {
System.out.println("正在跑");
}
@Override
public void jiayou() {
System.out.println("加油中");
}
}
public class NoTargetJdkProxy {
public static void main(String[] args) {
// final QQ qq = new QQ();
//强转为被代理类
Car car = (Car) Proxy.newProxyInstance(
Car.class.getClassLoader(),//定义类加载器
// NoTargetJdkProxy.class.getClassLoader(),//定义类加载器,当前类的居然也可以 有待考究
// qq.getClass().getInterfaces(),//被代理类实现的接口字节码数组(代理类需要实现这些接口)
new Class[]{Car.class},//自己定义接口的字节码对象数组,不用传入对象,等同于qq.getClass().getInterfaces()
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
final String name = method.getName();
if ("color".equals(name)) {
System.out.println("color 运行");
}else if ("run".equals(name)) {
System.out.println("本身方法执行前搞点事~~~");
//调用本身的方法,前后再增强
Object result = method.invoke(new QQ(), args);
System.out.println("本身方法执行后搞点事~~~");
System.out.println("QQ车以每秒1千米的速度在飞奔...");
}else if ("jiayou".equals(name)) {
System.out.println("jiayou 运行");
}
return null;
}
}
);
car.run();
}
}
3.3、基于子类的动态代理(cglib第三方):
导入cglib的jar包,但cglib已经被Spring整合了,所以导入Spring-context包即可
<!--基于子类的动态代理(cglib第三方)-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
无需接口,直接对方法增强大。
//动态为目标生成子类,来达到方法增强的目的
public class UserService {
public void add(){
System.out.println("UserService的add方法被调用");
// int a = 1/0;//测试异常打印
}
}
public class CglibTest {
public static void main(String[] args) {
//定义目标对象
UserService userService = new UserService();
//获取子类代理对象
UserService proxy = (UserService) Enhancer.create(userService.getClass(), new MethodInterceptor() {
/**
* 拦截吃了
* @param proxy : 生成的代理类对象(被代理类的子类)
* @param method : 当前执行的方法的字节码对象
* @param args : 当前执行的方法携带的参数数组
* @param methodProxy : 当前执行的方法的代理对象(不用管)
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) {
Object result = null;
//本身方法调用
try {
System.out.println("本身方法执行前搞点事~~~");
result = method.invoke(userService, args);
System.out.println("本身方法执行后搞点事~~~");
} catch (Exception e) {
e.printStackTrace();
System.out.println("方法异常搞点事~~~");
} finally {
System.out.println("方法最终搞点事~~~");
}
return result;
}
});
proxy.add();
}
}